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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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 zcmV-z3zGDSP)4*Bbt2V+UCULEJzD1zd5%y@;aL*s6%J_)O5c zMH`=0TcWmr#>6Ey#ywUOHBGv>)C~+G&jQL5P+Ursry}?e*})NkVU~CPgLj+-hM}U3 zPk(a4%vt{P-+R9M{ogrvq!AGjmKGKk2>`xer3*gVTd~-*v<~$bajZk#y-h0-L3FIL zu5yD^CX*#-G@3PkcSQg1#_n+k`|D75Z_~PHpjqo=fNZ6RKvN_0bT{2VfWnnT?YML+XoCbZVYh$Ah3BDHi%g1!vHokJRD-NSX-}F zYan;3hupg!h5bw6mG1_>Y;Q=LB=0ir_tHLeurc$H8r}pk`~LZzJLrDx8cN5HhrP42 zwoUp%3YmpW`@Jlv1`Qqx1fT5%$w$DMw2xq8*fOjb_vjIZJbMPW=g%SCy&IO6maw(8 z{fhxr>Vn*yiMoFXE#y+OYD#fq@5bcj8?f9()*{)}Gq7FMEE8 z%VRDhd2}+OyS1vCxLJrTaANog_}lwy>!x~4Mc%|bq>N5MZ@b>w`t-5s7~wL)>=^Z! z&85B`lZuKUku2n;rQ3W`@ib${yzSScZdxfxp+E2_P3jFW_YHNlZ?kjBopV z%Yip7V;Uy8PeN>;SbTQvGswg;oE~u+1$6~Tt1#XjJ6ky8(BMM|>l}vf^1jo?9N#w{ zk^hcFh*Jnw`L4p!n@cg!aUk3*-Eg(ys@d0|2ex61(JVzwu}NO1#6kP_qhRo0_yz|< z%7E4q*vKk}*1`T!+Z%=@OP0XN$qA7Rz%O3BU^-BZ$&)9;-rin&an{z>oKXpk<{E*x zQ>RX-sHiZRvgko_badqNl7GX#&P+BmHa4QJuFhyI#jQ{%T5M~}4ChH_*ZU@>>u!SE z1k|hQk&u@_LL9MoV-e~S%IEa6^FwY;E^?}Kc;DRGTm;w$;E(b@^!(XKY;b+Tb^KWH zBZfH-BSo)`+1Jh&g>{ASu=K#ET|VXYKM(sEk(rV0k_UQV3uZT8Dv=m=fYbNiL;1@u ztc@|7tYxq%d{j_NG!Q9UpUigs9^K=MgIr+eb+>WcmBK}$_dg{P+{)~#EI z=ZuZq+}x0zosC0>4w-$E$=;@B8{jwqSyfs1#{U~k?=l^kRhfv-i|2EmEmabj9qN=J&%M3z2fs8;* zfT2FKX3fI<`SX#Lm4$ip=Ao>t4A-w;$Cxo=czTOf+8bPk^kftkn7>@D3k7oHH41 zdM+|eN__(Z1NmG6+X~|;?e^=}53^^_=Bb~I#(8;pp`f6k)oV4nHwM=`_T@{n$C6#K z(bvPe?g%`0Ywu#ioelhZUTq$VrWL_a=7?><+mKwEjJQAJc-!Wn&Dh?5JEE>dnT{8) z*Rry)5}Wfj^Uo9AC*rp!zoA}PkHj&F*nWRIlEx%q*rj2pZK^ftQx9x4R#xcB^opU? zvXrwkt}CQ=CXP4MY+VmslfAQmEk^ytw8;o;#N z7{VB0q!TAjG~XoFmo%65X=46_vqECE(9lqxIw_SdSg=43n8dKU@$06Nef#!trX-{E zF3e%B8TS2`^_v}t>$5(x1`LI18YRs`jI?UiD*kptsHgNusVO%%7o$gy=5ta#eZFzyMsqq~b*E09;;D?7 zic%ug=j7yI#E20b0HnQWj-EYxwg?(Zzj=9iTn~~a^Y!)Bw$nT_X3XH=>)W?4*LXa= zuvB{c_H7;u`KLncNxuH>^eL1x#b@Ach$ViWF8s}jvaXT@FCY= zB)$m{4Gj%kmg zmKugF{kPy?$U&U`?`cG5M)Ns0{ke&!#H6uFP-#>+`}8cf6YOhlj}rX%(ukWu<1_8=CaBM~7D zQp%({>PrhQ*h{8=2pHs#07U&sSJ6(CKqd4gpio}|BrzlTqq*q4FMSr`qA|yhALjt0 zbW7tXE-NN8C@w(*N?OskEt%o3K0bK`WOFUWKr6rNUwshRY$fcTD&>0hbeR0C;h9-J zj9ynAba6p1c5}U%KG_!M1F?q{OrG)AYM|Wl0-7eZ*?|)f$Y|kdv4u?s#X@cLpY)L+ z>V+7I8J%CfKGThx06>NO(Rk`d?F35Qz5)nq8G8$DLOX35=&+f<4xhj|&d00000NkvXXu0mjf72v4t 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/301] 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/301] 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 zcmV-z3zGDSP)4*Bbt2V+UCULEJzD1zd5%y@;aL*s6%J_)O5c zMH`=0TcWmr#>6Ey#ywUOHBGv>)C~+G&jQL5P+Ursry}?e*})NkVU~CPgLj+-hM}U3 zPk(a4%vt{P-+R9M{ogrvq!AGjmKGKk2>`xer3*gVTd~-*v<~$bajZk#y-h0-L3FIL zu5yD^CX*#-G@3PkcSQg1#_n+k`|D75Z_~PHpjqo=fNZ6RKvN_0bT{2VfWnnT?YML+XoCbZVYh$Ah3BDHi%g1!vHokJRD-NSX-}F zYan;3hupg!h5bw6mG1_>Y;Q=LB=0ir_tHLeurc$H8r}pk`~LZzJLrDx8cN5HhrP42 zwoUp%3YmpW`@Jlv1`Qqx1fT5%$w$DMw2xq8*fOjb_vjIZJbMPW=g%SCy&IO6maw(8 z{fhxr>Vn*yiMoFXE#y+OYD#fq@5bcj8?f9()*{)}Gq7FMEE8 z%VRDhd2}+OyS1vCxLJrTaANog_}lwy>!x~4Mc%|bq>N5MZ@b>w`t-5s7~wL)>=^Z! z&85B`lZuKUku2n;rQ3W`@ib${yzSScZdxfxp+E2_P3jFW_YHNlZ?kjBopV z%Yip7V;Uy8PeN>;SbTQvGswg;oE~u+1$6~Tt1#XjJ6ky8(BMM|>l}vf^1jo?9N#w{ zk^hcFh*Jnw`L4p!n@cg!aUk3*-Eg(ys@d0|2ex61(JVzwu}NO1#6kP_qhRo0_yz|< z%7E4q*vKk}*1`T!+Z%=@OP0XN$qA7Rz%O3BU^-BZ$&)9;-rin&an{z>oKXpk<{E*x zQ>RX-sHiZRvgko_badqNl7GX#&P+BmHa4QJuFhyI#jQ{%T5M~}4ChH_*ZU@>>u!SE z1k|hQk&u@_LL9MoV-e~S%IEa6^FwY;E^?}Kc;DRGTm;w$;E(b@^!(XKY;b+Tb^KWH zBZfH-BSo)`+1Jh&g>{ASu=K#ET|VXYKM(sEk(rV0k_UQV3uZT8Dv=m=fYbNiL;1@u ztc@|7tYxq%d{j_NG!Q9UpUigs9^K=MgIr+eb+>WcmBK}$_dg{P+{)~#EI z=ZuZq+}x0zosC0>4w-$E$=;@B8{jwqSyfs1#{U~k?=l^kRhfv-i|2EmEmabj9qN=J&%M3z2fs8;* zfT2FKX3fI<`SX#Lm4$ip=Ao>t4A-w;$Cxo=czTOf+8bPk^kftkn7>@D3k7oHH41 zdM+|eN__(Z1NmG6+X~|;?e^=}53^^_=Bb~I#(8;pp`f6k)oV4nHwM=`_T@{n$C6#K z(bvPe?g%`0Ywu#ioelhZUTq$VrWL_a=7?><+mKwEjJQAJc-!Wn&Dh?5JEE>dnT{8) z*Rry)5}Wfj^Uo9AC*rp!zoA}PkHj&F*nWRIlEx%q*rj2pZK^ftQx9x4R#xcB^opU? zvXrwkt}CQ=CXP4MY+VmslfAQmEk^ytw8;o;#N z7{VB0q!TAjG~XoFmo%65X=46_vqECE(9lqxIw_SdSg=43n8dKU@$06Nef#!trX-{E zF3e%B8TS2`^_v}t>$5(x1`LI18YRs`jI?UiD*kptsHgNusVO%%7o$gy=5ta#eZFzyMsqq~b*E09;;D?7 zic%ug=j7yI#E20b0HnQWj-EYxwg?(Zzj=9iTn~~a^Y!)Bw$nT_X3XH=>)W?4*LXa= zuvB{c_H7;u`KLncNxuH>^eL1x#b@Ach$ViWF8s}jvaXT@FCY= zB)$m{4Gj%kmg zmKugF{kPy?$U&U`?`cG5M)Ns0{ke&!#H6uFP-#>+`}8cf6YOhlj}rX%(ukWu<1_8=CaBM~7D zQp%({>PrhQ*h{8=2pHs#07U&sSJ6(CKqd4gpio}|BrzlTqq*q4FMSr`qA|yhALjt0 zbW7tXE-NN8C@w(*N?OskEt%o3K0bK`WOFUWKr6rNUwshRY$fcTD&>0hbeR0C;h9-J zj9ynAba6p1c5}U%KG_!M1F?q{OrG)AYM|Wl0-7eZ*?|)f$Y|kdv4u?s#X@cLpY)L+ z>V+7I8J%CfKGThx06>NO(Rk`d?F35Qz5)nq8G8$DLOX35=&+f<4xhj|&d00000NkvXXu0mjf72v4t 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/301] 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 zcmV-z3zGDSP)4*Bbt2V+UCULEJzD1zd5%y@;aL*s6%J_)O5c zMH`=0TcWmr#>6Ey#ywUOHBGv>)C~+G&jQL5P+Ursry}?e*})NkVU~CPgLj+-hM}U3 zPk(a4%vt{P-+R9M{ogrvq!AGjmKGKk2>`xer3*gVTd~-*v<~$bajZk#y-h0-L3FIL zu5yD^CX*#-G@3PkcSQg1#_n+k`|D75Z_~PHpjqo=fNZ6RKvN_0bT{2VfWnnT?YML+XoCbZVYh$Ah3BDHi%g1!vHokJRD-NSX-}F zYan;3hupg!h5bw6mG1_>Y;Q=LB=0ir_tHLeurc$H8r}pk`~LZzJLrDx8cN5HhrP42 zwoUp%3YmpW`@Jlv1`Qqx1fT5%$w$DMw2xq8*fOjb_vjIZJbMPW=g%SCy&IO6maw(8 z{fhxr>Vn*yiMoFXE#y+OYD#fq@5bcj8?f9()*{)}Gq7FMEE8 z%VRDhd2}+OyS1vCxLJrTaANog_}lwy>!x~4Mc%|bq>N5MZ@b>w`t-5s7~wL)>=^Z! z&85B`lZuKUku2n;rQ3W`@ib${yzSScZdxfxp+E2_P3jFW_YHNlZ?kjBopV z%Yip7V;Uy8PeN>;SbTQvGswg;oE~u+1$6~Tt1#XjJ6ky8(BMM|>l}vf^1jo?9N#w{ zk^hcFh*Jnw`L4p!n@cg!aUk3*-Eg(ys@d0|2ex61(JVzwu}NO1#6kP_qhRo0_yz|< z%7E4q*vKk}*1`T!+Z%=@OP0XN$qA7Rz%O3BU^-BZ$&)9;-rin&an{z>oKXpk<{E*x zQ>RX-sHiZRvgko_badqNl7GX#&P+BmHa4QJuFhyI#jQ{%T5M~}4ChH_*ZU@>>u!SE z1k|hQk&u@_LL9MoV-e~S%IEa6^FwY;E^?}Kc;DRGTm;w$;E(b@^!(XKY;b+Tb^KWH zBZfH-BSo)`+1Jh&g>{ASu=K#ET|VXYKM(sEk(rV0k_UQV3uZT8Dv=m=fYbNiL;1@u ztc@|7tYxq%d{j_NG!Q9UpUigs9^K=MgIr+eb+>WcmBK}$_dg{P+{)~#EI z=ZuZq+}x0zosC0>4w-$E$=;@B8{jwqSyfs1#{U~k?=l^kRhfv-i|2EmEmabj9qN=J&%M3z2fs8;* zfT2FKX3fI<`SX#Lm4$ip=Ao>t4A-w;$Cxo=czTOf+8bPk^kftkn7>@D3k7oHH41 zdM+|eN__(Z1NmG6+X~|;?e^=}53^^_=Bb~I#(8;pp`f6k)oV4nHwM=`_T@{n$C6#K z(bvPe?g%`0Ywu#ioelhZUTq$VrWL_a=7?><+mKwEjJQAJc-!Wn&Dh?5JEE>dnT{8) z*Rry)5}Wfj^Uo9AC*rp!zoA}PkHj&F*nWRIlEx%q*rj2pZK^ftQx9x4R#xcB^opU? zvXrwkt}CQ=CXP4MY+VmslfAQmEk^ytw8;o;#N z7{VB0q!TAjG~XoFmo%65X=46_vqECE(9lqxIw_SdSg=43n8dKU@$06Nef#!trX-{E zF3e%B8TS2`^_v}t>$5(x1`LI18YRs`jI?UiD*kptsHgNusVO%%7o$gy=5ta#eZFzyMsqq~b*E09;;D?7 zic%ug=j7yI#E20b0HnQWj-EYxwg?(Zzj=9iTn~~a^Y!)Bw$nT_X3XH=>)W?4*LXa= zuvB{c_H7;u`KLncNxuH>^eL1x#b@Ach$ViWF8s}jvaXT@FCY= zB)$m{4Gj%kmg zmKugF{kPy?$U&U`?`cG5M)Ns0{ke&!#H6uFP-#>+`}8cf6YOhlj}rX%(ukWu<1_8=CaBM~7D zQp%({>PrhQ*h{8=2pHs#07U&sSJ6(CKqd4gpio}|BrzlTqq*q4FMSr`qA|yhALjt0 zbW7tXE-NN8C@w(*N?OskEt%o3K0bK`WOFUWKr6rNUwshRY$fcTD&>0hbeR0C;h9-J zj9ynAba6p1c5}U%KG_!M1F?q{OrG)AYM|Wl0-7eZ*?|)f$Y|kdv4u?s#X@cLpY)L+ z>V+7I8J%CfKGThx06>NO(Rk`d?F35Qz5)nq8G8$DLOX35=&+f<4xhj|&d00000NkvXXu0mjf72v4t 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] =?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/301] =?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/301] =?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/301] 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/301] =?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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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 zT8xm`lSqC~a+qPsK@)SbEr;r?bxGS260lH^&KYwV8|a7h_x@QOXY=f_?N3{#x3QPk zUKPyHb>gU8ZV5q@5#NjbN7v)+{i}B;CyC_Q!G$ff9dD=OIqz2e*y`j~sEsFb%*rsZ z1FcN8mBJV)XSVA46rXb|kC! z%oZ+6RJ-($ZM;{P0xi(4Nq2Lqf16%=ouwSM#lDZ1w5vfJ{b0u@7;ju*rOqyXEU&6} zR%0`Xr>`*0tS>a9o_dNmZ|;(Rky0{d%Y=+VO7UBy78o#&C8YYRmcu3LmdS&?RW9GM z_&zC$qnO*ixO)2Tu+yg_^zG^z&9;*4E$;=&no=leKYH6op>{Rdzt&=<9(Qx5b}7zGUD%^F^RtU8vw~*lo7j5k@C&D}o<;>sKX|Zi4pr#! zTJ0TjMB11PrELf9jrNKqLeV-Uu4TQ$%3=%G-~|C!dbi5X&Z$+WFUpGLnHMyhGNQX; zX>C$Z1E&lxe>%JD(WPVAINz7w__J?-e?GO=*N(6}7#*~bv=!F3dqsh$VB@$10Sl7ap^c%mG5v7ZsOBa))3Ua&sUM30Iy3g=@mY}O|?S*yR zQhn6;IYigD|8d@_k0%;E=GU*2FH{rAQmtnt@DvpSNuN1Zzd8@7B zFkWMABymKDh(BEoHUiJf?ki}c1lJ%j?h58HQRs}+0Y&3$s5GPvdI$%_%OhOTLr+Pg zF0niBA$)#rXg`gW{-N@6!a|R0pDZ@*QP;|>v;dFWm~-yEp3WC$!iicru3M>(4ZEbK z+(iLNeAk2yei(cAf|$^k%SHJItA|7WtqBYM_|0-HQQyy(5YVP8@=J&b+41(WA&ch5 zqY0$hkT+w_nS@pcaqWjPSL`Y+zO7q{4DHD#l;^Y?0uRN~C_4%cEl^*)p;xgik^xwx z>J}_9wBX{v;0M1BX+M4}dfCX{4_aI~S3obc*tgSfE9A75oFQzLkR0o(YirQEWZDN` z)(^@KELwHM9reXj*u5AV6LRa(S|`SK3wQGr>WnrST@N|O)OHqntG;T-YfhFoCw8YI z!Ftf-8PsqlNo0au+x!ZD{%Rc5pg;#1b?CERa+VBCn_TT9 z8yX-s4GSl0dE#vIN*4}9JX^&Pn;NTw=t^f%gV!PC9D|I;Zd%^Ud(mN$U(D6WqLo#u z^rM9xklW=OExB(#IJ934q4*^H)ONt!qrWgA)-WNqhc_GBXq2Jukb`XLu*9S-d?JVX z3=oOLraE&)NkpeZ4oAnw(SKI~{yPyzzTlp|&gll*5CT^S(U^ZA#}yq>Egb_^;5vdt ztKNrkr08GDqp+QMN|h?RgZ7c%=Z=A^nN*KQnl`*RKL#EH3wZ#$1Cbn;bwsst4CwGv z%Ki^iHa3U0G2NWs=PC}pGARZhbH0-c`a73H4>vbtkUt#*Q}GmMDm!Oxvfcg=6ufs1 z9Qob}tfSw`2OhyR=FJV@5)e+Cy#*D@($RzVq5ZHQL48ml(!@rx$*$|`5!K=`u=0aC z9x3QRiH&EG58EgXJ1c|IxmFIp9x&H&92d<+CO zk7UxbI?T=LHjkcQ1x^h1FMO2vxJ_{71?~7zvL5y0%H5L9n`q@VPNP3bt&o)F_q0DU zK>2JAQzsIX!oV|uPHA#n;lCUrpODlMP-$x$M6sm|g3WXIwEK@)fd7h{OZX?K>_4~# z@zMWgE~c-Qt8u3@0)i+&8aGabnI?sAR(Jg4iLQSfn0Z7+I&mN{B1LLQJGaK^x`$9< z>Zn|eo`p5JZ*ZS)qs6-oqTkVmr4`_d!49f*V`p3^;JUg6T1f6^g3_*c^ET{DB#6r%MyJet-_|`a0 z|5)e`CgqwFuzwu*ue%NSV>&P-o;wMNbLQxX%D}B7szWed{&`vWw@6B1eqybEZU~wY z5P8rz?THqYg808S!KFQ%N3tsPqANXHCoB0*xW_p6zB1s^?AdpjWsqnws zO8H;kNhPn_z4LtRHd&-O9z#)A|Dly1uo@%6K!%5pXJ5q@3kCD)W=g>*Y>pT3uocX*)g)N*0X^nimf88*U;^+1B_+$iEhoa$aiKSV1mnh@w zyEdjMUyNx5CgM`-E{n}m`|sK5dsfwnLxJBJQLdlIZbI}Joh;x_%5xKrzQ^8Z2%n&R z3P<(3N5)==eYKuHd*+olf|spThbQ&ds8y4UU|hWQXjSC&%uYNHu^)c(j($8ZyA;9Q zVt8LLz2gc3tJz6Y=Q6g0P{hk0F_lTlKM?`n6g@h5YfY-pcnX44glHFqg{qBr=P$N% zD@ErAri@H1CE+t_eXCb1gl-sFRAt_k>EUl$Q~QNAAVGj+?C{2c;y)tl0mkATbsXb3o0sV$KcYf4zSGWQM^) zr%*F-5u(zG!x}~e`LcHM?{}b(Y&^U0B#rV|azoth2uy*HOuhOi#j7s0Nn(x;ekL7vwQPUMgu%{W z=^}THkFNq&`~oxNylPG7w@&Dy4vN;Xne4LD!^eAZ#vcEq1hcGD?4g&`f-bA>(k{cz zxF$_O3iw+jT-%FB)LmSy%@Ti#PTh{VryA5?$M?V{U@{BKl1qRPx^rf6BF!z{E@YF@ z+!PdVapRB+eA5P7N+NXav%p??<%Nst%w)c>-1T68S*>*W#?Sqo>Z_qLe5cjYpOjST zsEuvTK)hWbHN)Ps`f<@-DR6PoSl8^vgEHiJU$XRyH}P!U&_q>rZG*Qh2iOF<#jUZ7 z7OW*)RHOKETKI+Zs*!+!f$8+oLHwnXqUD6K!Px=Aj)k(XO!m&)(X?eoc78Ep@SsAQ zuwUU_Bs2bXFR=L_&{Ea;el&A1C~bVYFfDE&`h4l~HBOi4~LaCPR&* zyhs~|wA|b{*Tj|i*jdzk>`Z8i&#)#zU23^;dfVW&kD+;0>_RE?6N^>Jv7JeyJA$bk z`17azq$JO1dvmo5tG1?C;7RnfAnw=OrO z(Mr*^p?a=UM2opsB-Vl}Q_vSErH2bZbQI%Zdq%R1+L>|MxyLf(Duv@f?N zAL@HQ*7=36*_`@5CG(xzhvYjoO~f0g?OtkeL3+<1>PP&|r2|V-xE4emhD3H<`;r$d z4p)1t$%ymalaWezq8Q(+KD#C;_UnhQl}caVynJ0FhSAD;FV4<=)ghK#Yvhw8Ex5RA z|BV1YfV2(xmh_Qb#(E&_TakST>7EUO%>-iHUt9TQsR(i6&c`99+^7c^)OfihL2{VT zw;F=%qZVV;&dLhOGkI(f=j__FZbs-OwP3fIwJ z8W&I|QQC8QjQ;}$4X?Gu-qT9$REW`J5+~hY9bW4y&dOlZD-XS6H>D9obQQyN)A}l*LBY|S8?|A5Q3D;2(ns0*iEvfuG1~u?)JN! zbAk`5e-EMObTZRA=sxnvuw>NDT*?7!sAlCG%NW1<$Mi%25nCaCY?aF(WV>;ojOZil z@H;L~Bxxt){S%0K1U>4m)&`7+;nkH1wb-6Jr1-W=iHyGQAZj8O&mdy~Wg;1kG}8XV zd>pp?IDA*Cfy+={aon(K|7_r;DuncG0sa!B^`gGV%xvU7yN(9?j~AEJ?86<5&qmxw zMaca8&IXY=oA)N8uC=J#9LiyfB;}Ei`EjN*3FUFWzy>|N2SdJs8O#a zkUV*&ex&foi-NU_#Jd;RdZZD2QywIG0@C0ZT&o{X=D?p*U~3zsS6*D{_1d2T=U=dy z?b5!WLJQ)g1?iKs-7aA#^vP8sC#>lYeG=}&A@>6q`7nY5Cr{i1aHV|!SlOhhCh^Jz z{#k4f%47jVNiGGuKY;q$g_AY%;Xc0jzE!@>*kIn>s*yZ^7Okm!4D<$5)pHl@p3oP!JBruug|_G#40%23Dh}?BnmQ1eTy>CqI0&}wG|k~vZPct zMG_8Un^p)y=-zq}^?Jp)B*CWS;pQ}qiK$P#flfOMxGYJ$&$coUI+2UTzL1&rNf+RL zz$;v+kZ&XL!Tuw~*@F@dE3+%LN2`l4#J-~FNRlvb|cWiJI22A6&SyYOA93-zKuBJQw?rT^whxU#}}th z3BvUa;(kBM;p6Fx%*~zb)7iY4J)KV|xT+6fge^yB+_M9ug+bjAiNWBq*=&BDG4p%<&GalQHOJkgJLO3o{?C6bqW zzkwy{HjL-{R)!EGiItKb^LkQcW38>P)qGOD12L|L(2z*BK-FVcSJ*|Hwufvb8|=c* z$1VH)C353FBjYm4oSSBS$426u=v}VWm$bJV9^xosyqc0A*v2NFo#iQSe`z-LjRO4T z4E&70@4}OE@STT|r~z4=>J%^@Xu7emCv#U$i?P*yV+<~Yi1GO7f@`R+uAvE8)#Ks` zp*@$tu(z>I1`Cj~JiAsYRgpuITX15TX77;)bfKiMx;A0(LmVurJULfxe3B-CSZ@h$ z;aQD-|K&_;nnKOSbo{sb(!KWznFW*%ikFR^YfMR_GBgOTaCM%vu@of6H*e;_t63Kz ze<4(bL_D*NtPsw=e75dMey=9oUqA1l&Yp%V?PtkHJOeK0^T}Mlr^-IYn&^ccfxuHt zcP#BC#Wd-PY%ob?#zqtJODyBNn}*NJ8h01@SCB1Bj+X=%rXpE?X@Br>xe;r7YEywi z|09}x1W!mw6z`xPyDQc~f1R`QaDG!Xk!*W2gPeoE+%81WvC)AZAp?^fkAc5r@*C1p z$#8Jviv4?SBXmAW#^ zI?4pK!FXsv=x%5tIx7obPV1%ZUiCKS$U2;}8xlF<yP)mk`xYy zB}{4y)#rt0Ys6k~IH;;}Wb(Q1Lj<_!|Y2l`CF zg@{)Vxl+LypqFR4f;_K&4A4JsplBL#^>7OvS~YiyJ)WMF{QJg{iAe zWBB#!6BB-&FXS~75C_a2b1HYLS#~UrpSG_fKqQB3MBxPvlcUC84~d+cP^EIw?rx0% zk|TS$U3!>~*2DjPwe6o092)_xk>wM;3J(%+qVk5c!NBF7Wh#_$3!`K**cr^-Yzwk^ zxT3-&pycVMH0eW*BLvIHD)70H?{vS_Oe-o|%tX;xO=w-Ho2b~*SgF--mwFH%-x zEtES)|7($NT}S2XO`L*k#X+5Q2N{p(2ppg}211I}JeuS51Da6QZ9GR82D}PIKCm|S zwOxyJ(YG+Q5&t}Szdk5u=Aqpi*(AiR1lcGRs%veA$rW-Yvt=g5^_7;tA*%T+IT4fb z9F1h&HAPPO=$c6q$g`J5Od{}z0T~;D#$vlfZl8xm8rseGRC;|Wz{562=XW+;KR54`+N$_1?}v(%UHfZqh?>h)MDwwk=IE)f}fq81*LWF z##-QOix3DUJ8Z8&ZvjMp*wa(cCrwtpg{UrH?pZkC1#h&l>Ym~8zj(f$Sj9eCVmU{4 zzQc>r&yI2aSRAMyP)zi!mzPLp2x?(s!_`;2C)$ee8Ew3VHi0>Pg$=VcPGv{+N2vzzhEMxva z;{ZOb_Th34<{@1Rq018c&~OZVBzh_#orwl<^|i@kmm7OZjg3f+Og~kffAGYHrn4lo zU4s#W-&~$?>y_0z|DM+}`-8kuKysd3tOtV7U!aj7P(2oDET3e)9{VFWIzRPteG;!A66L_9vZ-bofbj6~ zNvR+339cc4!Sd~f!y=RgsC2E#OyG@cwMKe`BAJMojcNoOxtlFpPU~4A>Q=Dvyz;kE zSyX!Z{`vPVSn&(K0c#?&`eKN)iF?}h6bt@y4`FYGG7S?pIq0)*AQh9X!1B!(&A_9B zt;Cu(3nihuq${L;w+&=+{%V)J8Dzy|&TtL}g~a(IE3Q7~=kOtDVsk5;`U_urifxv5 z3vmj_*{i_`bxG+@?bR1~JuLFse@(7Ol+3BK=u#2Sk}b&mey1 zY^nK|Dmp!KP13xlB1ENK0aSbC7lpi6zEel_qupT%ylpG{L$x2{FV9O1ebtcW7>B3* zdcd~xj(CRa-d34VxsH$WZgJE8@x~ja`@-!M=rY{1T#8x>fRK;*$g6uL%$rsbUA3!y zDJ$q|}L}fAg1+ zA9bBjd=S>~|CKMz;L>9d6&7KGO9+3eF_vLezhy%GE3J;8`L z&xg4bW6E!<`NXJ@CMdA2oLte=ppO)k&OI1fHPV-n3E!g9`4rWn=H}g3_8yLT2ISB4 z?WQ*1w9pqa;c+uxW^IS-`Ah6Z4`S#X=)JGbleM#@Rhw$V);hV_?^m_4&c}^awJ?=W zEKaG@>87R-Lq4LNDt%v44Y9~<9KsW2J)U5c&6nRjSnJd~LYBj2SJgc*H}m$JVwVW$ zFLqb_IBfLA^SoPd;h5N+qH9qU;;qatr6TxmW~a_(=?}lDg2^4Xj$+ihE`UNMZND`O zfZ7L{2b!UumPb&ntBeQI(^Jy>+%yH2(Jq09%lWIhCVA?fh}~2fId1bV(m49dWR2f2 zg{>C6;^8cL`?Iff|M!n1nNmx2CPAs@TQ5J8Pg@_Ku!1(aD23Je=iwR_aXE%|=J^iX zy0l&UU88^mH!<~F22+0Fi+*Sh{v|Vg?=4U5oW#f9G3q_+Pok?`)AtnSQ~k1@7(8-- zy)8smo4jp3P9V~~A@V*!&`?G=Z8`j%67dyMoNG;E*8~AMDQzbpjK4h`zELcQUTycP zvJ@m?EMqJ^`b+blB)hPPyM)~6`fUDFe3X*`S+<6oRj`5?g$xTwigV;5O-($w9|>D| zTxpY-XwGn|=Ve|g*}2u&qQ4W6LL%ZyEvq+Cc5V*~nrQY9l0CFV$>B(^oj2~JH~z^*`v5!BCvGTiGASZR&bqH95nhSu3wp6f2XjWzNf7-?$ie|uR&e?gunH1H4TF~FXA zpl`ra@dsoXc|LPO>Ea&{5D7^I`YneX0M~@T6FetYv;KgVkC;2HYrrS=5C=N1uNWM7 z^v!Mkz6M~xTSdo4J@4)w{o^>jeQ5Icu(s7@ps}gIRn?>0&lwc@PEG>2Kce^QLD*{? zNF#r@`_o?ZB;|;5A`qxgX*zHN(J5RB-uVq^iGVw2wzgFUkFk7K(mt`;F?oOk2s95f z{0<_14}!|{8BP@bV{Nq}L+?Kxz_U*aK;~ok{aF~1{FEmghz(bc7(9PH0^s?ej4ah7 z&j2JKkO*7@#upvEk@{?hr>Q<15NQ(NO|<<30xV$9l3!g~21wENVM+;ye~TVxv-yAV zcoX&j2FyFei%0QYMMoJjna50a48aB!G>j9G(*c(&6KvCTw|{Ym+12jyO>U8RHB!nFq7Lg!u&et^A!75F)9gBBBrn zI+>B6YUcnrNY+ePFO_{D_i+^TBBTH1NS$l_{!=mdXc0kO4)6aNsgV4Yi&;mD^U5et zyJHO?Z~+L$(JpWUwXF#O0Q*{Ea&QDtgZO4Gvwp{%o~rAAPSZuf^9Fg*)Pe7VaGr7j zaWwt$4W*Db`D*ENlP9EPjcu2gEBxTL;Tr=5)m6Sm1?3f7xUld2)W)oX->%7g>46Pp zv$aOlJH#64N$Zu*dS+?h>j6CT-l{DIy+t~EnS}WYlul^ayQKOml zVWO#aS%&`{Mqu4!!7@M(>}zQ_Lk0*y({@oi`SS=hUebk(O7EuI5p8Vk>TLzZC?!zOB{2SVe;-9q~dk~}T6d?4E>?}q8YuuS=h4pSj5nJe4~bIZ>%d=mzD5v>dymn=U# z@5`hBniWu1@Vw`{HoB}6S7wM4B2H^_*Q{R^p|HXvI*a`GdHy0~f4-@RU}61|zCr;N zCy8)I!i$|8*2y*j6?YlOsk6T1ujvp*^H^P^4L{wd=7D;J8+i+BV=Wvt)p&dw28;h_ z%D|_0Jw^A$9kwTLqc5rnUSXdIgs|2*CsU2&uwfX5n{evE07f<7;%{(~`tP>lwngOR z9SQbkJ*7R)Xv}P;PJdKTeul?@hY_SdCT^l4XK-|1W}r+UVZkO=ZRr}l3t9twujaxw z+3z;UyA#a(fwWF{Mrpl(QknwA1SC7L|ezY@;-8lk2)NzWe3dDZMr;0OXO-#-jQ(g;N7EoP<=NGWkwU-Cg1~ydvOQ z7+?K#hMQ3D1URHJ%4jxzd|biL=ec*=W1o@r+UExx^Zp&k(&x(p#2^clo}#W<+-=P& zl9P&gr`pXAx;s)a6Mfl)gL!r99*E_hg^6b>lCS~m6;Z!V8+?(HfxDKG`mh8}1$Vf4 z#TKC4t@QBh+B=yEiJr>QFzpAN8NSWcYpu@D#Q6kCq2CUB(Rr~<;Dxc{2&lhv(z0FB)}`Rq3h3E?JkP9)-3+530-2z-c zW`s?qLL!)ci9;ZLCX)@cZn+T!AsNBN3P9(eQE<8d3%ACbLJLsCdfMQDWYgjP@>SS$ z6+us*Y+jFdzUy1u`z9(WQe%nlLs2pxf;iWG_&O@?T3WwDu62*t9!5Ame_$2?=`*g3 z8)S^j;SUdvFWRf$95$yKFJ=VUFCXj)k@ZC=FVa)1GB|Y+6l7U?ZLTXmC3w)>2rRwV zrBz9U23~3J=Jm;i)^E7weKAQSqE4l#U-;s!DH<%H;-Mo>Z^OG zXWl<4dHb#XyBgU?q1{bUWECI{k@ZTqs_L4oY`XwtF(4Tp){g)r*v5zg8xypk@TRGt zEkbPiWYxD1a%O!U+YR#LmjL$%i7rQb`_30v1hJ}zv1KjubSs7|-tPk#`PicN`u3P5Lp#`^=$Z!sQM?{@6UKEz0@*qcTpgFL_$_$7WS`mkvIczgaM453DL0*_!G)X3 zU=DY<#IU=&C6kC<2VmyRNt1|;b)H?-KcH)Oex)Vu*V=eRG`Rl(5v4;61b#)l3p4x! z>g|jG*a7>IJWP`8dn_XdTmQ>-x#)LsVKGaGzj1#+vnb_4#J=hm6`%lrepYoo;!w@* z7kKP2w7hAx)b0?RM-EbL*>71)JPg102P6PAI6Jk*+rgPrSxYM72_E^-vA^H9awmK0 z@bQ-RACT(xKOg|bT0b$;`X!IYz78NOU?O&JCPD$j7&aJ6RCjk7_5qja?XhWRo^`4} zK*3@GyhXnMp$pKR`z@+J%&oImErAa8xCe-bJaat%ISsd-CbhmiZ1a6yqOw(C-*Ffo zL1zAc=i(3O7dTx3m<@yfr5m1~2wC54k#be?|2!L7Yf2aOGQXb4Zg5`!eAEA!ZoPj` z_cQ#}Zw=KkROYC6_int1i(HO7_L*1y&iACOt;#@fdEv?FDWSWotOIz&M}NSVqu!6S z_jpQoFfJMYiF z%T{=LcxH185FqB8^>Q0l%2LPYmE05yyWAp93KQn2-7wrTxPs&$lO|_YF?*uOeJ%9H4 zogirA6o~uL`|~z-ttr8m%<7q5hR3sD1{diby}xbMBAny=h*`m?b?PoUEGYO#%I{hw zzN~IMI;9RHV;<0dz%sUOXb#&3y9X8|R*7c1jf##LBEG~>&XZDIR*?nlfQWORt~#CI(YhChLOXdzk3H%W1YoQ zvA+><7r1hK%IE372gIe{=$4^69`QW_V5~Vu^2yOBwe0)>rTpy@G(z9;w-10*=Nx&u z#!h=tm6{Gj^aQob<2?le>=8ASpJqNDUF4Uee*rM(Ch>rNm@9~lJ|XcBD17n{C>qen zeEDhWcSNt;-z!!xJG2)g{-cvN#aD>eR6v(!hv@(XdTT9iz%T~P^wD-dLuL!q{sBC0 z?+>WzUw;AZ_Kx;@mm)3k^;9p=j0b3zRL++0cW{R{0ULp3&HHCrLCVeFe;%m*^MeJg zVkPnb`)XBxp+K+5{+F+{{EXda_;Iw{j8kfWsPqoNas;liPKEIRi<#oNbhrYd_sYAY zYPUOg_yTxNyo;So0uN2#r)ueuNsNCtM)pSc-)0w@XT1&>_NyX1w_QO;lL&<1S)M;4 zMgj6#CM1DpwGgm&w>D|aNn z^A#YTW@|0Gm?_UQ!SkE*>nR%to9eH_w|(oe>m7`uZZ^?yjW?1Vbe!1OMs49dpCbN= z%NgdI|%1gIs=m~;0* z_swzMMsR|hCSQ@YPG3AaU#wE@Mqp^MyZd&NQ=sd?3O}JT@G6E)b{Sc}1Gf@sU}}t> zK(nk$$GJb4s;|^NU(t_Af|9~fK03YZUPvwoY1V-PR@fKYb^myx@*!()nYs=K?T zQSc(%&`mEwWM`{STzgVK!N!u^r5b-jZ)EICg!n|Gb@<~AmhHN$^)Qhdx$~^qV}8Gj zNS-D`d5T^EZVQCLcuXFcu=2FdFm4N*pNoY~H2a$CcSFyFEM_9{SaZ&;_@vYo#67nU z@^SS8>M`JWz8NW}aC08fXIBosqdBgor0DNW8vMku?s@e0{UGaielpGstQ_R))Vr%u5S`c)ZQ6Bch)A8-$LhKds zV^ zmn_QVq+^9YQ*~0!WPQ}{mzB_IEoC@x5W@BcQ&NIWl4n9DpVoOSi~%5`?DFz9wOC+6 z*gJ}nFjDV~3fw2L{sA#AmrH0yMn!YLB@ff{5e=Dd%i9JmqE9j;ogn!p3SK45iRt-j zPvK>TOH;b>G|B>hM8I!%@{Y_hEo&)FCHq1XYPeytH4p!*)-MOx3Tr{pSBai7EDn48 zb33oJ5RPl_t0jb`4e#WU%t4@XlD?)zxmBqh>cCoztK6xM;PnC~SPUVO$Z%^+Ia$oS zejo;A;5_qX0s(8k(-kJtI|FshUj7}B9q9Fn4e3z;7Q@`;fUOaETi08cK90EGoeLtE zyw}<=m)&(R^668T%)*4Mc^lP>`Awi%ky|^o8RDuuTHtKK!H(dMYy#i75pu=pDQRW7 z);ee$F5t;*(^6;3Df?MNyTvy;x< z4A8$bf-5Iu1EH3`M@0|};k}Lvdz!eFRaaWS{u*tke^oszetK`It44HHwDm@S|Awi* z;3ttQ@bmQD;={M!zWbEXw07#+6~WTNz_Q2TDC`WIQ%L@Jd83FJR|tHfntT{65b^|7 z*C)Gc9AbfE`dR|{-zrG|Ys3K%T_4rGq(s#I6Tmz=1v>XB+y!9bl${PgVj@9idOUZI zRNT1jihtP|)geq$gq$CGl=FH!7Yu5K93LCY|Cq*kGm9GeIt zeRo}VjC?nYe|Anus@hOj_#j5j(lw{{u-pDDkKb6?vpXHJ-6ukRr5iyEMopn2>un*{ zaw6FB@4LzA4}9XRjGxD4 ze(tdZkLq9Oxh#$F0N5m+Y^8NOZu;EaHX)=nBkG||KKw~{#HmGuwnx<%v06U!TEgJM z&w~^PuXVZ%>w_9gj>}1DSp3^&`!QxK4V^koa&AT@dauFQLs-(Mo@E3ZkSOd8YtQpw z%ocSJxHz6bXDL_gw~SV5z8vc&iw<`(tlfiKFS-dE7o}o;#gN>WPI!Vg_@u0=rSh%| zM32v|ToL}blJV<M>b>1tN?SJG-C9(QZJ~h1r!d_A|v!^cSOrkOX&syA>B9V-=GP37aZrHuYPK!y;Z8BQ?$>6xg?y+>#m*zqekE?)7aHO0 zIVd`hycqEP6}D~PZohtwS9JMSTkvsQIUnwhmV;4+E9(t=+i0?}hn%IJ<|NX$wr$S} z{U!5Z{`kyZXOC2($P1&V#vZo}1M~+yhl;cqjq>3;PF;q@f?eauFd>L(IR%H0@_>8$ zi>f#c-d=DPqCBLgIYeLAZLWevCz#qe-H?y}bYjPaYG?>G@$D0^tWSrGM15GiR7dC= zB+G?XSn(If#(F(4??=ioPwYBf(J1KufhrgxPlVL>yKbNB{BL4I>S{}B=gmOj5l701 zpP;J$P#be1Z$@5dQH?yr9*dqf;~7nj*jkZ0jC5Dnm)dkOW5!RZt`h%% za)6u;@E*c~PJ^;HzC8f3J09`?5pT8kok0L1fL+8FL2=JgHa^4?%7oG>mTAGXB zNd?^grlj|Dv2mQB6LAlYi-3+^YYe?dqL=a)1pb4!cK>6fi8hTK0C_h{5P)* zdz5zB}lug{30o*-6j>s8cduBWW&I$@VvMlmUet>o~UCY&bAC(i!3Gy(9<`8HSJ!wRmE%&cn9Ms3Dqtbiot8xAh_6U#8^7 zY^25-KBuL2Zd}ij-}VgRJx}tjrcrjpQ34e*sbZ_7UH@bZ8)@7)C7KKsQBF_vFO+r? z_pp-V(rhQ<15mma5~_WN+&1cq>uO?_hT?pCDv8&lNh=JBEuJvZvswZ790Gl7@V%nK zWqf4W*w}^)Cyr9vo`HFH)Oe49yZriHaa*^f%3E;bID~$GJq*1()8$t-mJidE3^7H8c{DqCNJBA(CvV>v{0Z?3v zPSb60icTBVy*`#!m|vSe=)4;Se`({iYAaMqD0j{ljvF+Y8nzO*sv)*3wi_pBr0SN9 z+D%wLocz{74|a2Mc{S(!Iy!eW{%4Dy-1R$==Q6xzeT`i>6Cgb*J$|bt6f{Qx!pbt; za&!?pae1v-uFlOnGqcXP^mMvD@o-I992;KXsKV8dYGebMwJQ8=SmWBSjc`@;A+wmd zI*e;XW+3mk`?fG9uh445v5PJ9Mrff5_yf3S7n$UFMH?hagmVsh@xqzo?GI)(?DT@~C;DH(%r&&i_J2_0NWQIO<=XYi zv|Ee`mt0#kHem+2T`Hu^Nq_`|CQD;}Y%5!yi-;UjJ$~#zOz4vt%Q6D%LxI~j-9n4=*XxA5# z9vU!&V;g=99?UIoHfZA-1-Q*}TmTkh4N&2U&U%#6Nay-*7W776{QQlomzQE0_csr9@X$X5RpZg4fYVkz z;{fWs4;-7DyPGBPD)hSV*WtN|eE*=SJ`A$2Bjsa6YdMF2_(vk|ZEP&l9q?Yx zT=-PmdSS_MU&`;ct~8Q}<4hNqkbK1{{uNl zjpU@wm?c3gu2gzWLucYz=S7lEXS+7X`*V{&(_9q*fH?lO!R{V_W&lNkJRGhHJZq94 zI#kyI`RBi-bShF`eN?9%9s{(~fWz!9;H8}2Zh6TNZ}g#Ob|LVL-ct51bbtd;LeZf} zef4h7`+Fk*;P^zIUQ2oLB3`SdaR1;4-ywnQe%;6N{670F;=2t1d|Y{d(vHJbo@4Nd zVnpx6-~Rvr@02JL+F;r3+TRnO2SOYUE&bXh0m%-DxW5hk0l8^H1W@G$T1$BefZt3a z4Eyvi1`g{UfbKZuI0D!ZspU#U*Eshd&}M%md#Fp!Zj36nOm^kT9}xO;998lUNKA37 zV@8bu7*(a}w9}EXUJ-z43~`&{lEn{?9#EsK3KRogm{F#$Mn4`r>a7VFHHT{4rX&|~ zuuKm2#GZD3{tWxUyd~)Eudcft`%z`8Kg9tA2&gB#hy__t~`2$tFoJJU8C80_usP+WGq9p>!+XkGZN22+lt zKHZd;>15G;v;T_QDNfj~EQ^+l=Yi@k>E3unH0q4|bm^>o=lsblem81_y9Ww|I-a)S z!m!Gnnh?X-QEcP3{iJ&pnf%TAM!V&2SKhamz8!y8@dX+pF7e4P+W3CQOln z%MFcthcp1DuBM$kG{jV}qysC_+aetD5I0NlE8waH8e`99lQ?+b~C zuDy`GS*B!xPQiAp`Es$aho|cs-2U>E?Ja&mF3;$&*5E)75>c|Q;uuhO3j$0diQ&qbA&&&W4edy3~$U+Q2HhRMn0*rLyy4KsECh(+QPw+RBzE)50IC=mOQnAQobvI_JkyCWb3H{P56WIbl6ChDTH^G4eSHX5X^_2G zpfBwFXJeumNP;T2iZW0lnFaVKb}B*2(%VT7tbZEy@8kIPj24=n;zO~IaMivs zX3=;il~eahe|`3?cq9_6O9_Av^};3fwDiSqg?_CI?>F~#%P=JutO^z@sA1&5z9ND} z3ce(ousnRn~s|#KH*`OAKMMaIk2Sm2NI^Gzm z4|t~SvIgJhykEc(9~%Iox#L*|^7Sl=X4(zO&g+w}-*nH#BNW+F_>(5jJ02H0lkyPc z3)A$Kb4=cy$Skh&+J+lLtz6iPwNV$1qfE*-i@MT6KElWs#KdQeO3`#;etPAe!QI+^ zXxckjRc1d7_$tQCB9nW9RWPgH9I;}=p7WS$qo~Ev&LPgtVc#j$9^-2%2p-e3g1aqE zcozy|6a+d~*}(#I5OHw+N--RBy0(J0hf!woM(P;P%^vav+2~ZG&vq9KItUfA{LqZ; zsCgb$4#{wI^iu*`C%itHsIDz@!E!-qw{&!O$z>$N(haZZjwZ@p%6MWdQP{kCQrA)lS@+Ng4qT z8Rd9gwD?!&zJ(aBJ?=RRXpgp>Nm2tnPRQGMe^Lu{Q5z`Kj=ZXE45$SNzVkpN?Q|SQ z0@AFH5uGb)r>6)-NA+r4uV{HWM}be7oOb7blvBU+?py*M(_uImp$NMAih&m>S@YWB z$pq4LEku3oal2W;#6nOy6 zf57mg>M~8Q7T4J@ATHXJ0aW#00iSY?zrBl3{k7Tbugx*m9*GAV|CG*sn^{t0Z{+XT z20m=GMBuD|6Yw>GH=cf0tD8@Urzv78(~GXCyKI+;tQK}8WLK0k9S_%j-4kWF?ymUoSOpxE^Klz6w=!nFj< zX|zgY=v*xszPdV-Dn*g`tJ@%ukZh{P@5bcLHC8Vyozzw+5~2 zVCH~<=+2a>DQt>P9PET>&NURDXq?OXP92oLbKI}q4U$f%tOK_d%XkJ6LTHz*%h#KP zlX!xKbM^aQOBLRbU3vs|7=EV2tufAJ;`a5ob zT7c#cl7Nbr8k7#UYKi%ITYu=MYzCp+QH;ahxLPaoV&adX`(hkcU0MEi8BUHP1i>zz zRyxhfaXl9t?GZL>=thdg4rbJ=R4`@;9iR8jBI?#S)yrHA$tJTr_v4_ZHwgptutexD z$c0-P*Wr@k`qr3kC$^`>4L1hsN-4JsPva`_G7m>5(>l^8=CDp7#%uf=zk*>#s^Z;8 zg~@W>dF($&e;dl)fO-f&d)QCW<@=!P#SL!ku9MaIo-8FQwZF$=gNzlguSjdzd>%*38W&pC-IYd9j%jnrk9ln9-1U+|#58!N`@ zEbp{BKlmvqrH;l4K<8y1H!z>4@Cby1-0prh0mr@XcksXH)&EFkES#(y9mR!eB+$S zF|eBz5N|z_Txgx8UYW!%gYxrzHtf@Td;$1=gDXrN8AOlaz2mR&36B07>e9FQ>q~mC z!6ha-=G;?P6z1XZic)r_6l;g*>nX3C zIpyyGo6zB#Umn12)!P@OTCF@qd#oD=1X72Ded=B-fj_SZ`M_Mmufcac#mkKz+CO{z z_AXxte-xKViiVSL*+WUsrrv_nr-X<`FCp<8g8IP~v+=^lkgke)8eVU-fj-7JLkM#v zk9&L0->rFZLB%YO%eB00*Z4{N9dOaR_Y)4B)^<|#PI5snkvT!%x&`r48f*DaI9TbD z30jQWr^w0~){YAV-!{E&Kgh+)^)?MlBsbwj2Cyt#H=Unvx`on;QB%nBj@N&b72vkTo@Pj^Cxrp~bSS;+2^ariiYs<9lAqc_|< zI$+d_!FhRS2-2sg+_0cbs!L2P=O*v2U*n~q!mw%MB?~%Qtwg%FCitiST z>F*aeuN2WDnvI{{JnWTC`U7&6jw$9LCniy`61S4(S=$DmaplCrW)J4RvbW!lHS^df zbY!RJ?kFR7uQn|01qaMM*fYlq&vk=E>{bNj7fR>1Q|1;}k(}|PL@0B*&j7E%n$0#w zjP#y$;rfS!QJ)GvPZdC~iq)81OR_{~zdriSvlAy{P%u3$@Pr~BI1!a!J%JawM5|Rd zYh3id;xWi8R7;V{kfr7iR9BZQ4!zGW$JzFa_@(mk-mFm2lI369eHHXrc8&DLn3Tdn z^-Hp((VKmVhhE{Ix&3^}Q;d|C*T$O%(k)zF-<-=7+_fx8Hv+Ii^@UL_C`Bs38DS0g zUO6|g(I6-!kHe%l;6nTQY8I1_(~0bEW&<|b_Kas&#AJADDhX*Y_|VK--ez;^?MK(c z$~e+CKAL8#2KT!yklRInFWgMk zX%woL;<^mw8QWhwcfIz;@g`?B6^AckT{)x4g4OHcJ0QupwBxp0yRfGl8<;1xSEUEJ zY_|?@D(3tbu2i@%qhmrp#-{&DRtUkXLf6FdoFI7tK6dBy@J@H@-NmL6rwE#yVUeA4 zj*KEU<8MN7_fB?xhSVhCg#@*e@5%mHWX%@MUFCp$Z5WaAgAT{SDMIbJb$U4dq1)QD z+Ph;Y=|v)WTR$B`XR#0w44;Kr6pZKAnz%U6w2chY-5wupZB}r=zZo;|_NwezzNe-B zP4wGKEhpEuafvpdbS@NLWxBfm}|RFdYvBTIzLy3S;PF<5GvTGdZZaiA?czX z#EGX@&duwUyYRF4p}SArv~Xvm+)NWo9(VnG4VvF7GOx7Td+yEQ1T35PtfT05G;j2c zIps=TBXb|*@sYvsl33)?NnxV5% z&TDXOR!tN8{9I>ReQoz{j_7RiLyCRps8{((|L2H4f~{YUpthAQ4kDR9X6Vx-rrDG1 z)YVPf(~9mZnEBbB7nc=%NXvwpfMxSM+ya2yUEtLqd)n{bi|)SehB`Z}{T4)~W$Q5ZC;y9X+Vuz*ew~b5^D)*1oZN-Jrs%R0&;zBLkIE zfdr_Z4DWv?Tu-P1Y`(WIC-+h&NtU3A`}UEiIu;``LG%enIn^^fp67jjrR;q%4TYkd zO_y#B=oXtJJg@l4vQ0a!ZTVGIDX&)dZl z8%|Sg;`u4DGXj){geZkZlqMej0-7BwTlpCg$YVg008Sl0NSNeV1d;SOOuqq&1`;U!4sd3Yk+6jxXwg0s7f(w8P-I-ZbeHIoDs8Muh zHo<|HeIiZnr^Jysu4T{UnH?agnLK)4ZqNT^CgcBYCcTSi;+FT8>Ze9P>o!J2DXbqD zcBAUEh@->O1*`DHgYmGYXA#YjoKN2KJ-gzNki(QvANTzB*2$0uXHxF|>`?z1o*m@x z&Onx+J`YUUo@caHs)T>xI_5wWc!NA6uTVDsv#T%jVZ;moQ(cLW_OP?$Kd7&+WMq6w zkrj#jYy*MQopd<^^f<^lox!40=R*b;!nTx)h@Xqa1`KElgb2DP3G5!8o)Ww`xX^Wr zm#Ae)k=#hw$~956-5llNe#u?C^niizp$%flWIe%rgsg5tMXzAT%yg}6m+oaTA7q$H zPYTag4j8z|>noD?J_`hvcw$gG`z#KZ2AZ*-7A|8SRBxTuaOFO)fBaNfDX-L}5<{ZM zOBXYrRHf3JaR1^xg|S_aeAGs){#Yr*QmNDpTWKR4V!HHYcIJ?EnY`3>)sva%$8erF zwfTyotiC-xJk0+}DJZ4mk;a2J^es;3XWySV*+t}$rMb-;4ki~$XXKB0nwAE_`1(g+ z80XCfRI_vgkUYuHW8K_&vKq!i2(g*e%IqH=(Zf?NJ>LfFWX*B8w@>;*nKZ7|J~?Gi zoeG-SxNORA6_jHE>N@^qdGy|RC2r7>%Df$yQA@+Pmk0KCiL?<3ZN*?^es1>~Je@8W z!U=(pWIe4^G&>8qEn{S{15sD(YkuBL@>?I*JqZhv&T^x$Psu%~TUoDIw{;2WJbl=;}bt( zF%^4v@`QD9c_c*tx*d~8H)A13al83!ac?*Ca)lAoOE45OSE8U)ohJq@64KKRm~4^V z?$;I!vY4(z{bUvTQq{gD|EQ?x<|um+`}L10yw5e)#UBEw#Kk(HVbknxuth|mc3_dB zo1TJP_*0Wc6p1-3W}zyfe0T?EW_f;aUs^`N(S=Z-K$%kSb;X<*BfWaZHU0k2?i z^8;_7=E)e9;ZUb%D;ZCoe_)-IVNdoJEVdm1mr7#|c07=ib}v`L&G`xiYu%oVKMm+# zT=S%k_qPM5tLXBw7R=*Sl^K_i*8fta;s-J4~w`=IS9Xc8o=_EaL zd`wLS``(D;_I=qNnvjnxJ<;jeeiT-3)W`_>MSfCPm#0O*(w{R8L#zU{vNov~2+ zvU!$|FE*t?_4bjMy`KSca2Fb7vv&Ya`d8p>YV8Z0NORutAmaUdkb}Sx&Z6;cBlV!; z(EIDjLJppI(1iq*CgA*-ZwJ~N!09m`XPfro_v1nLzkLSoX9fWBJfL)Ph9??O;$eO` z@$YoCgTT-CC*mi8Y&^)D@bjoQ}MP>wstf zqcH?@E3SHk)d02~olI;U)D370DhM98l?!JEU2zO!*nAI~;r)x!c*o}*{0pS9V}F0@ z$s+)|kzjv+()v-@DbPLP_#!y=3Y3P zhIE*2zIEBeZ}-luhU~PL(>8J~zunS2sk(HYSsDq@o5%Wg@R?v$0fR- zYTu7l*uQ>W%#&r&x+m*_GrqnyWhKrqGxytP=~P?Ge0ca=Uz}knDP3*jl&4YPTvmy= zF=C=ANZduPKF_tS7P7X(oZaG|%uC9QjH~ZRkb&A?b%wtB?B_Sy6lj7*g7H&Mih-06?|3EOvy>++uwU2uk@Etk zlPDiOqK!>Qn(Gi|tw#z%ES4uqFH6@Nz1D>g7g0qcvWb48r; zluAo0GG*<)c+qHw&O2!t9vh=^pWyf6yOXyn$B>`f$v!nIt~KOgH`@(dZB`}W;(-S$ zaCp&>3Z?8=H!d1za`8JRrOUnC1k>^pOeee*!Mv>=g+#D}Y(MG4?l%KTt}eKVrsy|k zhihg`j)O=f&S0;^cN-Jo5ApV?tc0mV$E%ogU^syiLJA;DoPOxSjxDDx5A84+|6FPO z=)pHYVW-@q7oA^WyzoJ!%vqqi9tW2@HX7PF$CZ?MMczZGnw$xMdA4J9Z{w_%X9NZJ zF!DGT4~(XkQ2Usc^hy{~p_XsJ;9*xfHW?qj5)&K{pR*tfyHcfgXl)pqhVtf|CQqB2 zJ>nl@L=W7TOnP-%zt8BAH%Oo1dQkAr>A@wiEU9dLDP5>gU3tXnn5jPm@e}+)J2~Um@}sr0YBk&@!}J; zKAk_)MUx)NFvHI!@>Q@Gu4$@+<=dolXSn!DWrMXCL+OE$ytoQ(E8I`g8ccSe-idF_ z2)X-8g(IHKny?sj9lA2kE~Av1JbniJf)S3JzU)S_i6ipBb1Y=hTo_sJ9~jqmh?|+2 zcj$&uydEF*=3x4hQhDXTx~zqXA|Ledb`(a?4YQ`L2Wq^s{#Y;dTb4Jq$6@I3Yt<{+ z9Lwbj@->eSZ)Q=FACmA6KIA$0YqRtLO@6-dT2y7$+xo%u$|&oy9M-D-3`a_mb#WmE zL;9Y_7RVA*u5W@!p3Mssvh%$c!7#2g29+8kv_}M;ygmEu9G5j16z)L3KJ~S|yZZj^ zm0;b;SU5IEHRwl1*spd9vXd%{DR^jb zdciO!{zj5d=jXPz&J5Yoz&?w7jFTh4JBSO2M_1G(S@w6e^c&pFcH8du`q*-%dTrdJ{$WjgCP8X~Q@fzd6 zPu9oC$0ra$K>kJ*wp5=&`?7WWkox!6L%+a-NNSTW59YrFz%E2`3BNxJ*5qgLun;QW zC;1eW??Cl^;QDL&MwL}ga@`?-lHz7EufFd5Sfq+RJBGPcio0^>Dr>Jv zC;ESkLYD0Lk z1LmNOFI(Up&7Q8MPjO)CHK-O9_nn<*R*Rn_Gi63FTSvjs1u{ z3;ZmZ=#%43>oc^LIP>0pU?Cy=VPTW8=7GIHtr++E{46DK&Tn`>X28RAb{V<7BLk7^ z^O+!M4bOS5D2;yjd5C0AqKs-~-^gw6>zuoG1AJY*=t`@Fkp)wD|2KO&zVk)_^?5jr ztaKT{fvT8M0d`}&7$VL-C31*@uLlT{1UDmu@$iP5Pja?0Ig`33^7ap?nFIa!b95)t z=AwtV{vccfnIcLGMKa!~J6nDB9TRtR_dBqb@Z~-U7muaU2nOH~G5SW+5RpbI@Csh) z{XeX|XIzub_AVR{L@cNXDordP9i(@$Q3BF?R}mpVD4_|Vs;G1c1e7kJSCv43P^@6+ z9SKcJ=m7~x2j2-W1aC z60a5gl<57yRa8r^+6cPaB(e1B`;}W-iI)ai>EwmIw7=zX2wC()d0X`REi8yeu9n^& zo7T^;4Jp%s-bQ;#)(yT(gRt_7Zu>wBk&~@nwIZBtmo>~UObeK(M&DP9F*CQ3=z15q zakt*_(QUji6O1q0HzBRN{zIzu#DI-sXU)?t%4evJp3A0z?w!n%z=TWEFzeX_2h+(p zzpG?6b61yLm0b19R3;E7tfj#DO(CXxDq^IO~{20BoSZKo1%-tRf*3b6@(ehGpTn2C|D zx`A9{crlnmVbEd<+zsoW8N*jl&5TB0d z3QX)#vWG`!050~*U_qTtC2Y0+(N^8gb$GD<#6BMvn*(N+{fX}5OWv^?a}p!Rs+h&E znY)707E-a$RTI;P;jAeeZ${z`x}Uj@B#D%@6-J<70=CJVs3%^(ZQ5Jn(aLv*5|gHD zd@7?URipjbGB!EF;3{0GB6mN{{yyfSrO&Z-9Gy~mZMV*jL_>Wtlqcb3{1nHK3ycyH z*%-4hT#-EOQ<1j3EQ22%aI=p0(Fk+)R5!LSSxRto%;{32bP0*VWGdq8+n{&O$?6T} zrMS->zgM^v_e9kENq);IiPl^B?OLZ8uErD!`79=xjH!-nF{o|x3{kXvc6j?@&NOyQ zT}t>KQT+Y9t1;q}St)`w;+laPqsgJSpQbD8>V0%ZD`7+B)?b|cMn`={N0UsiB(IMS zR%#1-R35rNVni0sXJTZaAJn^$^2W4)HkNl(Xy$6KX>qcXP?65i;i2{TEmc0gIhLOI z+2r~O%Q4FlY}`-_XW1baw0>Wd+rYz6DF2WBv!B-AI#nkQIPHnzOqXg(L)HSykbJI{ zNJXDOZi948sRhhH^+bj2(IdV`n0;o;C5GLnQVn|oeY1y(iLzdRfLu^Qj*I#hpHJtm0IEho z|0X|Cjyvf37#TE0HbZ6s?GoeyU|P4va50c>94WjeN6kQcbLbvgCJgOWW-=Os39t}) zwu5OPEDwJjB%mc?>!(NFr(Qxnbsf@p)m}$vyKq^L+}13pfQ9XhGEyy#*nLm z*B*1DAv9_VCqW-7%RP6H0xlHqqyn2>4p*ZB(D(oV8h%N&2l;R9cjEuA_B%3{`nqw(p*__+N=(h$Urk*>>fyGV6)H_)djcs@nM_ZL zXAbMp{PfDCCyI9l89^d2he825C{7>y>-uh5!w`T&G4lMla08+r|8ZfD9>}hHC=--B zpW#2ii^0``w7Y)XN04^sKmXHv|9Qw^Jw>L2Dq}&Vq@RI}D}!IcUR>NwJQfE0^Xo89CNSOt`Cz2K+i)t`|) zw8ZD;$4_ew)Y=_Fi@6foc|bTHqJF4sO^Sx~b2KJ&W0Jg2r21`4PryR{hgt+%XrIR> zkvdiyKpS2mB(7v2#pDy3)8k# zL}*s{MzP51ht?Yp;;k#{u#vEUfZCEG>-)Pmp7bnU8%`sv`=0&yono0wxP#zo9Y}U~ zO%tJXxK`uojJsc)u_h|0TLNz5>D%Wt%P5U z#BJu8v$MT=78OUqiK;BnG`?ZvXMcDriB}#kCq$5wD!kcJOF|CjEmcF21Mx@z?UHam z=u!8ziYn}?l(-X|A8%xqa|ZYP?t7O?Z@qo5p@9<>3F2Y2tU{3}&RL?jQ~SvY1j;`^ z>qnF6*|fbuPXqy~IiDdSebwREXQ_mwFXxM#aSb9YiK7&)WYIZ3Sph^2nZ2U>lV|qW zvt(z-uiw%~8C@&GMczG&G)^54he#~l9(*~SP)A&y*0w>LEs|Ro?vvmK3X z+ic%#(9&FfPB-gJnxh@Z)Rv30(o)8dPWD<{OYie16`uK=u{lFY!5duMZ;i_TPRR+o z-pvHK$H>iN;`Wlxs$&yI3({r8b!@fF2*neT7buS|@ytTtt~}Ylv$pz8V8kC`UH=OO zqd@P^J+(nCb(QhfMt?S2)GfEJsSUCSEFv=>s}i;goN$vRyDNV;h(5eQdF3U9geprX z3|a8!*ElDk=S$i?JTWk=Nm?QlCK!e(Qoa#X$C9-RyR~-OkkUGol&J)(Q}hha}bxr zP*Bj=BA$@&aM@rZ;bI9WO>`OUN?PLN@NpF&>I2Ks?$z&|TD;PWdD5rpbgErsaleUO zm~d47O&f8fFlbyO)^!E>QFrtA-qL*kX_C>n*Hqg14%3!JV|GoSx+Lj7X9Te@gLiG} zvg=ZIiDQH7#@6ObNXK7 z9#bz=SN&sYUnzf;7xmp6NvE}WVlQN*7++Ppe?`%6XN+e#PJ}pkOvX$$nTAdNwb)33 zGBHXR6jgRe@k;IVrNk$tvi?>>bO#jKwhGpRd_PNC^i-_O{`lznl-E@kcsHpX$ z(~P2K6Y>CbKhdO*(^8~jSmNe!oW7g}d=h%rO+sER;PcVv@1BztoK~PvLU6fEJgo9( z-PUbYCiM#-Tp}{H%s({Zk&q-+#-z@YncLlc>tQK~XZk9Hmin>D-CDoVrDySbMJ4 zEO|^1My9!|oy_^`iyLR+&Xpnh6r;iJ)Yhpy1gTAQ^Af6G^_z{&A>#kOv zu|tR`R1}L9UqqK$s!>*s@nQ*2uRT9Ot=|*Bj)$=gR<8;)*?mfqm}4GJ!JIdP=e!~R zOx3)1C*Dgxr`&>AfokD+bY6=tjzvsm9d5m&7indr6E%fpY5B5HDz(UH)LR*a=2l>8x{{31~!H^LG&6{dF0 zbspLj4=R?1BBnH~P(f>NhUDua&zlwgu2RFvo2l|!QximVnmK~-sWA0jbQ!DABd9~+ zt%2k8$I~#fKIlR zLS@!mHDcA%us(w;xx@6lmW`XeswUZn&}UHBSDjFog0d^Vo9M_o(04~`FK0}i=rw_NRR!k#!h6#7qr0)KRm0h zi|Qwf7P#cM>-#v5MJ;HeHd5k!^mHUQ3RQC~M2O-^x?OHbhLZGE4)0?YEn3{W-`k(V zqYhUkAr-V3`Hz)Xwv|f0*OnovkU4r4JqC>{OiyAL0ty5#wh0wwVo#LV-}Tbl|u{&0RlJVHcmU`=s5>^HE54 zudVYfrw7foo6}_GM%wP**SEIbdEA&>QR&*9jFu_6T1`5qFQ1%q&Y)v_`*X~?O|yP# z4xnFIJwvhQMQftR@w&$ta z;5W~7QB(V#4&C{8kAGG*^ynnGzY9jRtn==Cs5r;PrJnslqh4XthXJc3~ zny|bJkMVFdgxi+g=*r%nMI{{BY}1~sV`{Kr$J1MV_vEo=*WW%d&04Qf^10EK?HA1G zx?R;CPxVhng^is@Y`erUzCitwYS!paBV9nw@i*BWb zDxq7wNvpIW`SHS?s!Gu|ulJ3K21`%eq|DUpC|zucvY|@dCwR}h3>QL??K1i^mJNOd z3Ne}|5UcVCSskA+Y;S4>_5nuZOurWZ4mHr%9Bt+#H|vgcOS+}xc8OkY=$1AU;fa{O zt0^-y)>4pKmnBH!!GA{nLAD_J<2`-J%JQ##f+tjEm)c+)&OVDJD~eLT2aLPFWc50$ zkwxz93k`a@_H3HOs^$?#-q*zBc>3j9e97hA^}Lbuh}*udQiXD|=BOl(xErl^EXOe# zzND@Q2bbFj??S`DMw|$48ZI~J7DTi~IwP(TZk-O(x{78kB^qbvm5!a1{@t%MBKpXd z=(PTR>l!pbBzxyLj_LFEysCsMf{)VAHi3uSt!y-dU)AO3+EuGFv$)g?8Mc{?yxg;t<(9rtA!k>b1U-S!9a{3o`? z+>L7n%fuVntsY|Y)l*nRy%twE7#GM&aO*{M~^#xTU~gJ zEVMm@6B0eqC~e_S=_56Cpm}~fXS-IXx+J@9y!C(=SPqX~;T??NU;Az`!8L6eX;f7W zOXiE+D3SPd(fq5*90yM7is#q6-wIR}YHTXhft$I0S`VcU&2KM!maLU1o4_-C>FRKj zu-#FUnrGE+WrE%Uqq69|&7P=U&J@H3&zMy42a^^(%Z`f1ni%#oT;(WD7{4r{f&=B2#2GVp&`(6q*JI$2Uo?w_Rktd`$3RN`kh*3GA z!k?elEgQXu9vPFsrbPF@s!N%PR?&U;@;ZR?`N9bAdV|#3>lS)FOw{sj0(D0Xu^>69 z2dP|`-_za22HS4rLoB^#k^rEOGIwWi#SPAq)MKD9UgTHHAINvfyZf@Bxh2iEei8V6 z@=`Q?foZI*Odtt!q2puhcVYWkWf3IIgH04_IB`mqq-vc^s6+WP|mQxoY|jqLY9H3`Rd z-i{H?C7e}^Z37Sz_=bmZ`hWez+xvZx6R!1Tpqoe?2<;zor8@ME4r2F8Fvm7Gn9I<$ zN~vLQm--tT*MA6j=N|&n^jE{v%*-kGFr=IAXd=O_a2NlML2w;4u@->h^(sRWK za-g)Q;F}FVp2!IeRQ_o$7zM}uyqRhtn|7cO}kE8tqu^&vuT>~}S9FuX} z@E;d`%n|=bNI}~*(>r(vWJh<6&D(`6OHYdR?E9+#%&{BC*8gD%v?4V>5df*?KvEi( zQv!x9dmH<*`#ZoEm_=$<|L*C5&4>)}{U1Al;H=VZGXYpl>@v`$TBletMhukOjP+4h ze_$BkYWTZ+>6$3S!CnE)LY%#S1CX4*O5$OpXu%E9dzK{&j>lRzO=hdSWp)1&kJ_*PwPqX&q)a3JnH z!+)M-+KQT=lz;Caq`|@J-`ftkJXFf?pIv7Z9LG%Ut&Z)z038h;F}k)E04G;)QYRyR zLw)}_mgei`n04S~q?A@sigfZ_jR!d9CQ?3(6V)$!)b^n(UiIr?UN_}Ljjk|$l~Eqh z1MQPSGHnnxUM$X$SCdSV#Xp`+U6L0Ysdh>ylvvb^_M^AX)nf?DIsAEEuf`ZRBMth* zc%T2yx^_~kv1{mV{#5;J_p!>PmMXIeJ`ZPSKOVjKJ0CuedUOj7*Os-G4HzGWI z0}7qd&R%N;=b?=f;nE$BmWkU=Ie9$xtX^yJQ3>fQ4H9_*JX?Y6|Fuc83=X45y(VKc z^vdPU`t-Hrg%@vy$VxJ|`oHH%O_tFVdRUxv2hZE(cX58qWNFM~r+&Hh{a=EILpzz)N)(GYr@+VuMR&mroE%TNRh?%#A~$G*fH8GwFw#47WOI1%JsaD?5TX?3OOy zGGXi>4(m_x2elmM;t9KZVGtTWrSuVML{e-yDV~a84I+}%Is74PRC}|v0|P3iV;brp zs)&Z_Al+fteGrKi`ZTrg1)-lT*quG#o(DJuHVWx`d!QF6X*xchidYRol2oR9F9>|5 zU{-O@k+zPMZ1Z)!dx=6)Q~UIZILvurm|ff2Jc&^OVHmRlD>J%B7=Ow7im^24(Z&7E zHDXgw@6ndQGL>5L_J|HRYd-3&jw3x{$E?8FpWd{~zwX?ze6pmMd~%om&|{n7lfMU) z8%XbeZ|g4(WQ=lK77)2Nk6u$!#*|{ zY$z^6VP^jig!|V&$2&HT^aKY|sei_n%YPkD@8q$e)cx(5r{DsQ-TbG(O1CFwqDtyH z+U(rU3~P(@b`ccVphAy|Tf}WlU1Hqdi#IUN0#bTr<3<0x&x?K9Xm6~R;$!k;`}f+{1AKf=ANV`!87%sQghXGkWY6%) zyAHYa;gi_h6*Ai2Kw3ilfVt85tn`A7kEmtR)wO^`j071Hb|}7oAaDTTPBn3m1(>1) ztK7cL^WEwusBYihAX_6om@GSa_c>)}dvn@LbQ+oh* z9`vHwfTe}d+j{nHfG?Sr@s*Xm%RGV(4j9g$LZfFg`hcN-XF|%Z#E&(A(0J9A_^lmw zNW^%MK4E7Cs2c7_YhuNe2gC$3`1^O#lK=hOq*GB?Gnc3WS zZ9?Mwg^9OV^w5X8`oKDG3qQ2O2MZ#2KD^hu7Aj-Q#fKU#j^0bt{CJqnYGf!XDlTrP zUF{|t*C(#Rk}|OG`u)??*VoY^0)&t&{K6V*)bZNo43_B@P{fA|5j)z}%K8d*JJ5=A{+FJMbdivkcYyJlX_$h7Q6S(a; z=IfHHAIdJ&#(fhi!|#HAc7DRPn(o6Uck_L(WpA&7OjFfeCWnK&v)Z1 zPuqLG*e#_qols7zS#}d)1@DU+Z@n6{rRNIafpaqW?9nf~U&lyG93PLHUU4_jVBm7s zxCo#yeK2ddNo3%<&)WTp+GRI%*@m@k#em1!!6kJP@Y6E=#YJ$ij+;(N^0Y?U4 z0;mH=217b>M-M_nnA!3_=>&nuD{cFO59scmOs&nR+E0HE{+`Vl6Bx)3xu+(-Or8zQ zhujN9#!^L9MVv(KW>k%)0|~BP{7DJc1-0*oV)E9gv)*AiltBU&;{0+83v&wq7buIt zD>b8DS>>Sqn}IKbbmK77Qr(L@@JOCltXaPf*7LtTuE&`aw*CG0_z%VZdaUxGYG(g| zMOoe+oooJ~QevR)+&&8p{k!X$h@gpWs{H7iO|}4m_UXd#Huw!h&u^vym${yKQg$Ww z1$F^YcV4Z0q7}Y`-Q=dyQ*U0mK;? z;Yn=a%v1OHh)5{KFn;4)50IO# zkV%0$APcw64E4LTPdk+N>KFiPNaj`>VJCcUfv*Ch^%P&`<^(w+&$+#g1Z%zd9atN; z69A0jqP}0P?Qa7sEB-r>30MwQSrDQ}?|#NO7%-GDM+Vz`{4GwsVytr+xPh1m?(*x}$qKnrw&AH~U)cG8Fuf^V? z^zg}hjRt{d!wzhQRy_u}TL4Twt@ptwp-#|TZU}9JtLn?nPJOg>uNTw$8(?@Ll6@jc z(ZejsJ65H`bJM%GdfxVvMW$a##U9%daSgS%N7=_%T{~+LDbxsHN`=YCa{$7J*(+=) zA%{KAV0ol{CWzn1>ZDi4# z+^Sfn$3X+y>CRc_bdvi=p>`-rr{D3YhvrOk*yyunQxVVyU)06-h9jBo+!I7gVGNT@ zCfR)!3P}->(|ELMhxSfcvW`TOB9xWh|5g2PcM=NL2w((Uja?tcTNN^sNZKRP$1GDi z%F8=EIojBBdwaY$2ZS7W{08Y(-l~Xxso?do`rg`k<^)`NhqxT%AmClz@J<&hz>}gq z*r*>9mz<_09nIImt}JhKC8xA_zQt=mQWI*eDT1$}G;YjQ@YSQt(|dOAesn8g;kci3 z0_bY`jJe>>{Zt@jZeZi|L)@HF>Tm% zDapZE9_@#foW-pYiI%~NW$HbGlSZ$cnoP8iPvOTWzLnm@$^3yh&}YSyR`)xP)O*zR z)8a3(-Fy48nW=dlhn>dBMAxyhlpt3%MGU7&wqyyX4J)FNNsnc>riqD3&2-@qOr|a} z(nGu6U!T-R(2TlpwF+slT)>(}xe)zai{*b#%IoOfu5PEctnAAo^*>qeu=4yu*=9e`@4v=q`F-Jg(SB`pXms_ zsGcXmEKXOKj+BUHqE5Vf{V-*NL2pDUj_`r?T@@0=%Hmp%8oYkro#hkHeeWmJsA&`Y z6Ax4PEu(QheYJT#d;&3HdXh=Y$YyGIC50483cT8j)k@QW8ctg^n_$oAKS|vxIXu<< zOnU;Z<-K@$|Jur_QP+k0^fi&M>Mg&+kJkjWhGE1(Z->UwWy_}(E6;3~FtSA(qHD2= zXaPb#ZfQZ98=;x{^AE&l2(1Bi<8?}{{NUtc;!4nDl;`QiKAoP41o;&*^VHKQ{K%0n z6M^hkdokz2MHeIUOMXBpAqn-Y|x_F~9hJu zkx|(IoCn^bHgmL~zC=-~uPn&2gtsNz&A!+hT66&pbFb&@c`I{*={3d7Z{{4M=|WaGIQQV5^W`!#DD!ow=lEVt`+Q55c?B|PqkZ6ofSF0L(a)DZ|HaKd_IAb?Vugkgr=-pR@-|XFO7EK zU^Hfy(N4+naDV$YOl`D1)hKDuViqlHqLrqoY*nUX;2U<`QB!O5ZeH;+&(h@CZf%@_ zoTfpvsIkMc@&gR7LbRs1$5icaVd*qey;DQS)`YIuOw9KXsz%>^JN}Zc8}`dm(?re7 z;mKIb(CZez+15ggM7+qU$wLzRyC_TTYV$_jVxx=(Ztv{7##0!KL6Iicuu(y8 z0?K)$Zt`3SQ5RiW)+HrkxO1y}Fy{(cl6c#F+$ePC8=E}ohDfTYXNda|x&dvo{N92i zaDc8O%gb#Ww(UJ)o1Tzb@Y!G#x;yOWS9hI%eM~#;y^_a??EYWJ=6+U0I}BeRWK3>W zJN=ywl>G4=Yu}8DSF1;;N0Tc~3UxzEEr})ugMjI6OfxbkA*0+|RAvh0Dmw3PN-5m#%^wq;SNdi0kl-k83 z;W+xr*gF2EQOz!tp+%uro(haj%a?rVdjWKQVv;8dE8(w(h^`xbIBXK*eTiPjs&HPA z)r~z|cA2m$uUMYi?_JcA-78!wX5M|7SLEa%yP}uYEZRV?Q`G}@F)FRgTHXCaA)8&z zXN?yMKByddOU+Ho`sij0Y=6duT9kg8z)dx)jYsj?@{L03nl<_ZZ-}YMHFb;K<*Alo zy0FKBL8P2sCF^3#x}7FZ*AkbcA-Gmi8}p4~_|oX5KRw-Lc|b@n-J zPkviJO_aaPTLF`2yo}Bqjx!h3{bSLZlBB5YVU#w1oxE4lYMz=QXrW(rZbdyIts_i* z6&RPj6)oRfL9IixB9{1%O35tLE2h|rqX#dW>j< zobJq!jiTsg@p$Db>rbn&81D#sFG-!Zs2p`;Mz=O|naZGcv|yZv+e9QHXhYa9Yh7D_ zC1<7LL!O$x!HE0>m%KG!cD2Y7PDm!hlD_HX?;;Mbc(M&FUEp?Y_;IM~#rlZ8QrR4A zz0Ctpo(^HdFC5c1kKQ_NC%w!++BF@+4j)6S_F+sgpsk6&Is4L)0vij;YHn}5Ya1nZ z%l_pPlWDj?(GgFnR7;O%d15}zU8YgV0VDLeWn~?CH>EQxde4v0!MZ2zD%tp#-oEMpas>RS;5@4zcvMvt4HGPbYg3?VQZr{&_Xa8W3OSy> z?8O<~H;yQu+DV>@JbFKQqo}wxa!)%xP_#hi1>7BHGSw7F=Q->FbI~1~K}`>$Wgn`I zImo%&xt&2B^wr)8~S_4hbh1{ zcbj40tuFnspR6DvF=o>NaJ$NNdY{{?$x}#ukDY939m;bo`5|32fY*{M-7RP>a810$ zAAYvPv21-<>XBWgL2!T-aku{F%a(Y{zUhSAx5e2joE^F&a?N=kf~{ZXgLIS&#@zsSw&=u={|rcdX-#YaITB8zjQ6Pr;g<2Y(Qw#wj6a5mFRCj z7g?Lbq@g{eRqv+|Y1I2LjU_GBRObD_8`BH?{TB26d{q@MUz5*Yf}OGnw<<1U5y@5l z3Vhv#L&TSi%+1f48S|a>3xtpaIhVYa*JcNTCBYGN#}er)$uo&2Gques%{H5-7x z4CQb+9#!w~=91$8YuX7D?jkKwEPUNcKvr4WzsSuIVFN9=)S=Pu?4T4Zg_oA6G+CdM zCe_rIIAVJg#=^xcH26l!m8h^Z6B+mt0PNzt6R;S}@xkG(WwHM&Q#6w=JsEy`u}R2;rPPYhp-VM#VL@xDgzb{a@{YcEu<!H}wKY#A2l_-kOwno9IUWDx{fsa1L`mAh*H__OY{`0+M^7`$KZ1WR z$eb4-z03(6`I_Rv<76lP@~g)&3o#qx<{|%zHc)c3^ey@o)MLjhTI&OTptf=J5+>*A6*`1RJ{wb!6u+yy?jGJ5BkZOd_r>z2il z3-rfAKbcll^UUzMd!N4upX7zv!?zc1EjrR%M(4J`l+}low*7;ulvh4ICEaB{6dX^80@t+F&v1Z?}IS<&zUkes#`_}A_mAnMPTKMJ1AqU*FUR@!BRZ4u%JkqE zoiQ>FxO)eaf^i|7=?9ZTK>R>w%+?86*9v3m>!{ymxru{zti+5h z@`0IwTYexJ&C~R{7j9*Bkn{2jKi0RAopc7zN4QlQk-_i4!Vetgze`$}8yZ`oY~sVo=# zmgg3gXAi}!m?@G0^yTZY7jD~dAc~Ki{KC+Iq%;gyGHk{Gl@xHO_tTq3LHOqc)^pbb zM0kE!b$k?zz|W2l60<=TS;58bn1vbMHzG zq;cg^(vE^lVQ7WFJl}E|{OMLFJ(sVQg4*{94N~$AGvRVS{`@i)`;o7RSB0x;=byK5xQ<3xqV9(G2shEBokBLsZ^}(`n>ht)NlC`A)BX-IxcSa zx|c;Lq(qKyy|lIPXAa?u6UL@A$pXiiZ;bo#m&_7-$<^o4x+>S?r%O!}QhAHJ z0|*N|$qCjyeWJ$v_&Iytiti>-Mi~3EeaT(c($2~NZL*|)RMiqxbDW%WZ+5ZoA!lOY z@qS`%obKh~9QHqq?H&b0$uPQXdVp*uNq;OEC ze_g0#Q;QjTW6X4|c+d*G9$;o2y$E6*+qs)-08# z7in|f3z~<|V(dYIl+)bx0gIxwfd^*ZksK+{%b!m$it33w8kiA{0g>_RFXw2iSRSY! zmq~w|i|*?$upq-ri2I^`P$LJHl8Z$DTil;s%ksQS zvPq~9NBHq>M3YyT@iBzXbtC<-&nu}9QL0(`mO?}Oqwy&_q^tWr7Um45qNM|WAh-uv z^-}=y|o`utE39FJRd!GSzD zn}5uUJZE~Hx6W=pcHeI+iMN#d$ii1yD`yVLHM&c7qtVL)&f7L)DLX%wd-8to--fT9 zkZF6CxSrTqb1~ZdV)&70D~{}QJ4^~u1+ty*0ipX3tR|)2# zNw_3Q_)uTvn9s<@1KAgAjcKB7IR#ZO`C|;}Y#z{!-?}~ma4UvS$Nb2~!b?a&4xv@OU{DNXpHDVX8$ncQ0Ad`TwyZHMZ@&jhwVc> z@~sz-25Gq#tHT0W^AEw;T>^UngbnEK?{BSfueFEB zTgoJXo~UKC zY@-7tUUR)@ILV@2Xx;jZ#^CyfMLoKx9=oNyz4hSD5r~k~4;4W#Dp}K{7X+W%G@`q+ zcyKDK!romsnxkS>HiEP|yLH~tjKDvTs6UXvh98-XthqBJorW=$UDpsu&%@n1Q-LH`fRzA)Df<~0N;eP z$!-|t?@KNKOo~}r#l)oMfI>UFV74_CAZ$+Vr!B+%oxfB4NAAl~Kfq%D%1x6}i_tsX{8!`Op2AP#sS&D*(??p$Mc)AK8N-Xs)tcCENnq!ZIM$r`? zoMhvn!nr(sDPK_mf@2cDB<>qc8*Vvwx_jlzLI@`<0(zotcE)D(ggO&iJKzzy&qoNS0z++ z!T|+Alz2fXsvl;dD;e}d{IY?QzNQRfI)9|P?#GwGQuCb>wSewvC?!wFJwtN(2|B+a za6aJ@qZHYpL;t2+ebFd3y(n4q*qA|hjgoy}zMrm}6a3QZVpf&&^T(wIP*SXyGfcU# z{8m>bUANL1`zjnA^W}wP+vd7L6Ay%|occ>mO;JrIr7mqgl^_wRxeEFF0*D?1C%~Va zSSX-%dQt2g?>SMJn#pvOn0BLnE$Mst^k%r9gEJ?=emzvk((-eoWjJr5p9^aHHjj*g z{ntqL&t`tFK3$P2YOB2#FX+Zk)D~TZ@m{UIZ(ua!+JRn00H@!=b=5#qa={%Z6>kz{ zmDAnUZ-ikD+$P|QL^VqNpp?11XtCup?UnCho8cj(r}%M^Uj~_#SsYzqFc9$gleB<0J~#5z*{z@c)npQ*g@ZK|IO0 zgArVM;+{}#RmR_}KOMz_Av6!qd@mye2O{lq@2o~E%orTuP3IOhF+EF`j~O1mO=9EW z*Lhg!eOmaf-O2YorP(4DUKp>pY%N-DY(23eMEEyP!f3Pht$p%HdCqkw&GJ~uB*FOv zqn-*VZ)Kr4v;}QcKu40Q7JTTme=Xy4;`kxas(UEa>Lljul|s0SMsi?S>xBnJrzm%& z7Vh1mZPr46PDPvdJP+5-Usc=1dFQAtzo|HzY60uj$sX75iHL9ylP0j$I|!&&W|5Hy z!RYJ8t!oKZez4}w1ZB*$2ijt8;)IUkTl&`#1{(&2pGhK1xLaTDk>*s!)}ixuUeXD9GE0gK`Jr`TD!s}($Sjx)*zUB zgjiC%VN&GA+D^Ev;c!mzXF6N*Rs5hUJK?_Lm*Lj4mS#PQq7H(yQd6kZ{GHN5fHvTh znOmqAWs3zh=+%X{WFr#NN*DxGj{;~ zv`^`Vzqn2BxPZB)@uhPP-X((FFd6jkC$K?95mb#*k=_bZIo}%|gj4SM6RT%()iD9@ zoAO6KozdRQe)G`m!Hp7EM!`}FO2aNSLlmcP^W~T*Osb&K*iKe}Q06b*Es?T$h2GoV zGuKIpVAy8^%^x*zWqG%|5;NBZnVujIkGNW-7y{ z_;1k1Wk0g#Q#u`UO~}tOIZqHK_#w)$D?i^5rnrEKAG4Ve3}#5j-B}cHu@V1pj^WxBfr(&}HmD z&U2O%qCH`{_2B9Wt{($eKtzuxCC_#_V_)_L1ok1wXkl-4Rs~#gG`WN=!_0ljWxx_Y zODh8l=-V<>-hO0%3 zblq6|eM1ze;^n6`=()EG5|x2}=lYM>r`cUj{zq(9a82suZFZFHd4gh6m@gE_sse5` z4;64T9^8O#lho_#1XWp`!EB%iC)W$&n&La6^J23DwRPD3cltIUIT&`Knv}^G6Y z`>VO#mb2A7dvkOTc4o6Nx)%MC zSDR8628GJt4Z^M)YvazVZ#a#vk=YQ^ymTaqjrc$-2L`xkjsi-%4cYBeAnN@vyT`+Q z#wSMXux%43;iU}CDO|YgQW7jWTW3$o8qnh~xK1SH+V8qw3Q-;|rcb_hkX#aU>D zSDVDWg}s^Gx~Bn!`gmiJV{lD`+jszZ%*H|RRdTHtDQLpLi^M{LQAW>Zs5)d?e`886 zCvXt#JcN5ru_Ab$&s|Hm62DS|U8vs84`@}&VNJ1jA^XD5U>5t(6?^>!)|94Up9nX! zg^+5l$Hsj*Ws)@SfG(_KS(zi=vUwn(baX@h&F+a{UzbEgMJ)`*;?t#sXyr*4_k+7r znoW2dc~1`oL~^vfdH8765c0Z(%0h|D` zR8*otvM@s`)gCh|)p1UZ-2-<#j^)j0-;Nd zQAUEgBfxOIO|4S=nTCm-jBVI)1_#5zKwp2Z4E2Yc161=Cf0?a8aI#uam%E8CcpcRH zP$wvXKvL6HY(f1dA73mu;VmHH;j2lpKZEvHI_a{h6Ldnpt~3Qxi-*t54TIXsBc)J^ zZS2p3+F_obD?nbxCf)u6GV$M;M{&MIc^?OupXeWHA3n1xryruhrK_}-gOn?qPQ6&? zAot0(nPN(*i~k{GkVI)T2znJ6k4uZkW!RnEG5iwA^J{C{G4UG@ilo#6U!R)}xA#l( zxo;~cjFJqy;K;ux+8H-Vq&H>xf%&Di)(R9jb= zV9Op=H1OCe*N*G+inb3~kTq9yBx>DwF*Ul$r%{1tJoO^2?ODP^Zckd>RAO%Ssm%D?J&QRQ10CI_0dfedb780)Sgg0FppojC=`EP6Sm3wxsy-mgFVk_ zjAYi>OV_C?x!B_swH0Ax<@{LpTNQ*yoU0A5l$>K$5-0`@TYLB$dKNq@Em3o>;y8q> z;VygF{kWKlY7Qb!FXx8G4NSHCMpAq$=tO~h{_6DWvMS0V4d@$jrq=l0I-hd7CjM=6 z!Gh>+6@I;aWJL9L3Rkd2Wp+@q%_Hzw?%Cc8`7KVr)fsolCrlzu$|)2GJ**Vlk7+JvDHT z?*Zv@V_2MsT?U=lczh9Bl1fTINLRUw4n-$!Bp&8>y1X(p*fqXY(_minn&6Sl^h$)5 zW8o*R<}&qnC1qHK;c~233uj#F`cdh4i-qlbhcj)AkA8Yh?fbZiM+tt-P<(hTSv+=( z47y`CC-C{=6^iei%3rC89rfoIc$jxOrMggyh_UTNgQj-x-%#vtNa&56YAa7y=EY9t z3En#Uu6pi~^{$$ws;2cv4I0v4hAJ;TLhcuacbjUlL|)L5*pyHn?!nTbzxH15d$Yr8 zPjEJe4Yyh$@v$9!w-=LD);9J$y^>1@-wyjKFm=DC5ET{>AA}#l|BJR<9esZ42^D~X93;n%g zSfqQ4k(@K8XY<1O=HZxwm-pd6mai#$OGzyQNlC1!!rFw7gXr8PkAtEB35n)l`&RqR zaTSOnrFb6OL6(nw*H;xzBcs!cA$H`Gc2{F4Vk$K^W)9hcb5@|sm)Qd57^JDi2IMw$ z9@(BtNQnP>&<$A5;l3bdM0oxW$kG=G_OcA3@ZAk*9AJFpJhEDVpV^jU(u7V#J^+XNY}**uO9w*wN;OZXru>rt92Ep;slXSfc!ujhN2*u~F*(G_7prcz5A!AOPAn#hv|W)9=JHx(}C{m%q$|3bz7 zPy7bhh$Tmae(}r${G$P~l^PbBWxOcFAHbSBj9OJ7uDpc)mEWcw z0ySDohS-d=r9Qm_gk7}s=r-tOKZSO|i+#BV2gw!1{a zpvjZAN!$kT>2nGcQ6Z8buNnk%Zg=C9CyFZ;Hm_{7xmA7f>{yNT!e%=^0CgbQQinLH zHt+2g!O|gY#rQ2X`CiH|!)bKS@rF(H>z?kY>C+~)&mQ+)cK;&V+I3TbQ%4k4HpBCl z?M?O|XVe9yvoY7^i(Xfc86-b@C%H-GkkqtZX}OST;H~S1f~g%&qoR>m2_01d76V}Ytk?%Bb#8mjW>A0Ej znSHl4=zi)zc2k2?iywQm$Rx>ep?BrXcm-CQC~val@9Aj=c!WWUo80nAJq7i&GB(){o#}r-vho|adVqoxV}fp zk#_-u*Q`VFRM4pZAfjO8-fUggoJ6KigkeSeIvcrJA31vCTU?jWsg4Vg`8??YbWiB2 zCJ)rY&SYEPOjQ2#KzZ(ld9U)d{mA7JvxEFu2TKW9KqjpY+W8J;aJfw1wW6e{nB24iB$MlH~Au{%p(7BX~ZR zBmBK|Vo=2zX>CAC;acv6vHTjU(Sr5_7_Ox~#iSyB#D+-`BmHVVudr=;NW?vAzu`G! zv!k)ruX9xZmM|CY)#mz3#oE!`GhjR;>1JLV@_^kdTD`C}!K|aAyd?9Qte}?NpGV@GadQB z%r;ln?PuTO%6(hJHUq#3bm52))SRY#A7Rg}g;6Y4{;OzM?Ga*q^KSIrr=nKZlO0UU zb*m$EQY*{j0QV(_ttM^c!&9X1HflHKx+!dUrxQoVUSSLHd+1`u{2hR^tBcg=G98RN zNWbaK$I_OcJ)=)f+kMwTh}GgW%`zbRyI`%bHQEz3;W$hhEn(Lqop52Xj6 z2j7H#va#}JVsetO8`2&5I3Y2H3tO@8h4Ib9p(2(lo&ALF;zv@tF3!-ndWr71T%R-P zK$Fl{5165qFHme;C042$Wv}zL+hC+ijODelc%Ad3FRFSf0TEZA7BGQRQWA|HE&A~( zmq(Ua%iQpC9KDJ9w}o5^9$VhFlmf9{{(xQ_%pzXVX+&>vakEnJhF$f!`^2UDzMn5x zNZ51avj$>sLa;v0G{ZgbRoJ8wgAHRFA3tZD<$DAufKcl+QEG5C7^>RXn9Anf8|~86 zXsF)wDQQ?k3_kGv@O@oUe*4^q-_YQYF7KL8AcjWEbt?m^;Ru}>o}3OLo{FYf{s9sc z_`?oV$WKZpKr)C??GL!tR>rAE-+qGXlqiV#&;LXwPdIYyI#3$#Hmjb;6^|s%cB&ad!BzHyzPj41E@W2obMa%nBb5JOoSjfJZssK?_EKB$#|Qs%2}^fXF18q zKO`y#VWazj4~-#;E4#9GU)iUO&6nb%$SrBNQ}R9HH9`+5I;)Z>Z7+g&~yS0)%?I`5?(g(=((cjsh#Wb4(<2e@_S zn8bi5e)>{XX8wiuC8bRKZ46e|-33sb<~1>mvvmjrOs!M8ihNTxn903&*v&oeQu^2N z-PMGu?e79IA1IrLFIv&Q4J}-uEgPrlti7`+r?U=Lre>p8H$Ij;||S6zd3*gzvEiha_T#8c2d zlz$S>bp=Zrq)N6@O%Cq>vE&%@+%jNWn=q}S1`es91%XNYHIoe*0I}KJjt?d{XRsbK zx$g*qs5H18RL@OCx5N@q6~6>#hGOT|;LVjp{1U)Z%W<+>hX)3TH|E{}On>%j=-V#g z!apJiTsT5>t@Vb|=SN6X+s zz%p<%$UnLr5Z~9iPPLG4NLc*0%Wbfeda>KBNT8Q>me{LCTK-=BE?xqHE~w7TjZa>5}Y-(0Ju;o|#XLvQDRS4oCy z!Ul^-`^tE91R52WNsrbj3@=zM&hg`Owg>S2UQGyTD~BRl;skFAAMrf$z6@xgQ{D%9 zE!`H}o9mf1InVi|oFTcel|%s*^v0++K} zE4iL_H*9S2zE!Qgr3ZZ(JEf`GRtJ^vlkD4%@4Zz+dN-)us-YMe4mMIr6|YfGz}64- zmkDkc_G8pclO}{5vk*QN&Q&%6xK87ZR*lyeobhgYLkaHzn8(#HdW3ImcKm&Q6}k!%&WN4@9mNMdL`+w7*n+md7(QC7@{T%4 zO;ob=8S29cSI(xS#YQ26&Lb8hTy1=DUASU{Fekj|z&8)3mP&i8+UUVK^_NCNekt#j z*k5!>@q)z(&2W69%L0#URk+1X&488|Djf`0G#SQCvXUl1@eHkNegT! zaPB1RLwX{*n$6@;?D?`iL-*vcN9*8QzqDm5x_zK(&3c7izSAYMbhKw4lV{9AH{lP0+&&^FTgr*eB*{4 z&ck0_-OHf4xkVG^R7DArf6TcW4KM5{x!n@bRhxxn_Pa#v<1D^_LAT-W`Sfe4O<8^x1w(?ZxbmvBs6?52-wRV8 zQ9KOoM6PXzf%;LKyW{jXUnOhYS*Lr-7{>O9#CG?8l z#DBRF<6q>#@$hlkod+DMi0jiA?Vr_Nx4p*K;(sm_X_zSB>1fim?~ZhF5s7=~;5pi@ zTRC^H*h)$(UYdKs$8$(cns+k3GM7(EqYV_NovoZ_e)+8yL0e8~s8?UP9YQ6cM19MH zqI+%Xvyn@>W8)do*Ec!rx#t&j0xFQs4IsU&JKkKT6#_nvpl(P0W}*LJ+5k{#lenwmyeXS3sW z&YHaT&;3kyPNnT6-bKISrg;v<0w*&sj)7A5Mfv;V;?p|uv)ay-ZU-#dT`fDv8p1#6` z8v)(DirFztLe_QL22ASDI2DP%Dj<1%TK9uCiUkFGhVAVVuVI}q^P>xf%KMKi8m*V& zs~UbZp@LGf{V;Y#vl)r{vy;x!EDMkB|5x=aQEY*^Xn?cegu&w6bI!=SsjQFst;y%f zH#@6t(J7GSq&TmwTRvf8u!^E5{vyMBc?VU;gK*apC9)TKNV1uGybDa*Q*|$J%OKtB z9B`aKH8&!U2zz26V1S(8*6rMu;MVjTe9thM&so&1mX_M1A0!31zoeTFJ@G#Y{+u&C zQf#+8<-Za05ywd6Xag2qHfIn-5Nv5!%qjpx!4!K28fSy zE(Zb6Of%uLV2>nt(4U7!oPP5(rJe@lWW5Bn?mvi?>RzO$g1lT`VC54pp3~|}g*VEQ zH-XQW!S^(?tRRz{*|c>rfBMb%Z|JM6-gF4(NoQt$fk#8A8(TXdOiu&O4kV^uj(1n& zU-|>&73t(Rk(wm$xR`&vcn8dNjUGw)Tkx4XKpI^9XK!hR9)A40w_s|(81#_KmGD~{70csJhyS_*4e`-R1@UY;s9O{i2c)S zWl%VmSkhh*3VCKR%NjZzA~i#2?^>#m@N>$T)9-lA&^zDn^mb8n{*pqcjL?&J$R2Cm z`;8(uqB0Fo1}J-_?UB$?552SkkqZCXuD)KAXTDC^UJ*sQ z<+J%<7W7hVHpGf_BlSIfT&Fec5K-SLhBcavZX5d7x!-ofmS#32iWg=l`P465m%E%b z^zrA6%HUbYu;+KU$3$sM$ThYeS8NgV5H`u+YmPxO4ARL*hc>6Lv5OLRF zO$)l$_6vlBHWOMuTXC_TC=6p)Kh!? zzSqsxMvFC9Mo`3#^%*|*Y2|CGsE^=}LOq!FDQdL8zezvYiYH`nS?xwuAo<~YH4|me8p2u00lbuD=#NM$~_cHGb z$ds97Be-v4OCGk*XyJZ<{l{ zR-rTyDJO^jR~|I-x4iyr?NdEh{%mD}6X0aqc(Q#Z_yNQLIj~-na2eS1{<%P6xIFAQ z7;f1D%@CD5oLkatnPvq}WwH}lb+T7TeI$e#Q^;?tNo)~zHeK?Y$HAU|gb5_{&+f9D zetGhDcZpoUw;(^}Md?@wN5tZlp}^(t$iOeSaB%bKH}1in>qmsm|3|u=bn5TVC(?~r z>OW&V5N-TZx{)-G(qM=2hinAjw&_=P1v4Uwmahb{rJ8u-$=|;aPZ8u1oFI=NlQ+u2 z1d#w-?ZjeWHmtyG07=yXd={|%H$FkTC$kGNzj2rI`~iO%0K&;9lT?Z&$S;cj8*dC! z`{EfG(+uAw2X37gm!m78o77$#@~O}rA5z1|jWq5{Xc_5l3pJGd(xNXGyFmj4|yMk=!E zeJ%+1hCVH{_pkH^8T086C^INDj$40BKBU!#FEo9wbc^u{ukPjBWsDYI;o5^x!@IwB zf3~|J>{Ug&eofDN_T6AG#gcl1hBp@QSMgVOKQ<41*y#v`0jLw&1TG$8(e*7QWbreW$ ze3}Ow5>vt>+Qj_SXH@_lDJE>V1Gf$T0V0HjXZ=|0AZii6O#*{Nn3n8|y$p)CFH#y~ zKz>KxEhz3o$GlGl(HpmZIN;5A#M;zOp#8h=cfI2e?_bXiKYECaafO$NHiRT?1mcHo zVdV*&8DYy(Y=Q6}lnQiVOfu0UkPCDDKHFHxnJ+%(?T6Z=x*srX9GmIKM4({nVLL}L zME!*&AZCeBIo1Ix$Bv&>SsY7Zw8_nC{UgB9{RJW=rnaR+n>6p^+4mu*9!L`!IrZQs z=oK%h6*FS~yH*SzjR=uu`QoDku(G~zq=A;8`_>*ahaef{d)QzH4xozu0NE%F)L!&p zcd~-XF#7`Ln*GB^Fh>-{gh@+U=nT`R{o-gFF3edyo_aU^5kkq;Y@duJp zk0X$4|HcIwi*3h;#1Zj6M0TtzkbQ3`eF)?hc$tLPsKkF}a9a)}WDWofu}7EPZw){_ z!l+t+cqUEF8lX*5L!$#2HEwL{#bPfWfS@TO(TFxwL>|0!FaQ;ioxH`nMKX)?L0|Fi z+N2F(8<~HWMhswA`OvY{a^mmmKRqoLaK6h{-Eqs+Z$N+X$Aoc< zPW}bY|MQucJK6CGXcXQrN`M@?crwKLbkGIBBfL0s+f2Z;KYLQigR|x|hoWe*-YNa= z`3pv*ng;^s7hbmLv8rp6_0}&Z>S?%AO*U5d6kfa5W-QXhbcfp7MpN;JAB!SHJhSgy zvbiVh$+q1~fH}hVR$=ftLNCDks;Pohgi;+Y$xpRn`;RKLHsq@yRWJ}ReWfQ8ONl`9 zl5fPGm&6rZtlcFJ;eITho()M81G-L$>wPtEo&EmRN%6Wbk-BX@Dk7GERIk#9OrEq2U6|MVfu< z@As|x_pZU05^?gy|KlZ_>jA*q&|dy;TOc>ApF3uf1S!bBq2q!W%a-Kd5Em#kV;uyD zU0_>7XG=Zd3ls7n;rln_kU3tu_#5hke|u30@BRfKI+=wEz1T&9-jXeF$ieg4kGB7( zD_Fe(GC?NOzZ=6D^B?f;f8V_;qZ=XZ6G%Lv_5A|b2)yf2BA()3lLzLBV2J&jzQ4jf z3UD`9_oCVei4|AHl*^@Q_si^Ojte1v;QO5eUR3=V#H|pp)=X7YAGjP5sFX;2z7d2z z?|(;z`{8ns9rQr>7f^;IPb!ajiT;n*7%eZW2fQ}DPYp^W@%gSmM8fd{JdqSqK@pVR zw=@6b!zC=L4~+7_o%aNYc@h%fE2PvWi+S?*D!;-w2QoiG&ZedJg^(!_&4#d)0m@uO zr_fGK@+pgX;8;`yI^r6QCPCBWNzJtoe2N)6$xpE74>}d-)|l(PGoXx)ITTR2+Qi@` zOM=M3=Q9N2razj-8Uj{`^$CraxDbH`ij2TEb;l*t{u_|SyW$`qNG{`(0bS{wJNl*# zowEjzZFa=mic8l?{_JmF5lIl|cunm_Q0Gk0`X7x;jyR$@(YUP_$)6}dO6`gY6fbW` zW)Nvx<(3k5xnjL0`3&13vGytTV~O?Hjt?DU$XW%SmppHpOOCiK;{Br6Or+%HywXD}KByL>6zELYy-$HIQwj0$oaxt#C<>{BH#r zVigUj#h_dMzyx4|$Ri-{RxmlI%4xVH|JZZ)$Zlyvd9u>!OA5ppOCV_qm~(uOLJ8*F zzo^g($0a=16^ImL+e?A2&LDNeKL*&k&zMz6{vjVapk)QB4oK!~9OPt5U{|9)ORffm zVaSF*{w*I1&iZaDK#IurgO4W@IZP0cp?&7$Q}87gNKM=VW^|hTa~{EF)1a>I9Oa`KhA_o%%HhC|Dvs}TbQdxpSpPKz_q=FK_N(N_EAS$6o^_+2!?Nih^}PAI1! z#Q}R`{St3*`k6zUiO<6HEjil@$+|TSoFi3OxzQ*y3AQ@f>@k^1<4w)3UNow{c`xRx z2Hot0P)SL#=N5>^y0}(uTkA`!XPRK9?shj^bL0Njb%%G;#Vw*~^uVvqu+_0s@D`>O z-J4srkz|yG+4a^$EeCMp7q~o!{Bs>eF^*c{_1y26lbJH>QVlMs+if`A9SYw+MY(9k zsW>{*g^=QkR*H4248JNexlZGU$$39ITE8+nJDTKxo1E2En{rx18^Zu>NF&Oar#Q1G zwa(AjIV-!{l8qJ3@T5d=W8HYI22rlU>`EztXf>zI2|wW7Dsq>eQW0==Lekxi#bz^J zkAT612JI^+C(ZN4mvj4dZYRafG01x{X__c)H#im}tCOY0>&&tiDD@(|{2mHr?~z8Y zJEfW#-|va7Bk2yxrMdL_Yd9!+C@HLC7Eat=*hY`77K~v`Jx#moHS%f(YiDMoCJiD| zM^owohbs`nxGs^ZQWwKs$M$3%)2-;(?{$`;dJQ$z2yaLLc{j3P@@YrMM(M&uvoIxt zdsd0pp1ie6l9@_`P3)n&_JfjPRRP?nYk@nnXrpb6;CUY(*kn`P`vuqMa<&^e zqtZ02cIb>Z6Q-^1f>~7fzXsF-n>wQ0%B74IU7~TuhWEZ3E1kJ8aS6Go<7h8u-n^EK zcp6{r;Z`ZpBI45hHv76;CAvOFtP!DU&{Q&av6tWC;NH?kj0Lm(y1O&A&q{ZVz~Q6! zu~9wU39-eu-xFuOH7$}3et4B?@}Z`nhZADa1=iNpyV?_9sik5&97N^jR*tD2(s3|! zs~&3+JBY7y-EMRZwcQH%a_hyLu^P8ddfG;W>Lx=omGc3qp|0ZN1?-ua%h!qI`PE9F zU%RQh9n-KWj%duvd{o-YYLYYcKGW8&4knq)E7_?xZHOpGeUHpSJ7N%(*2gGrguA`U zw6tM!%}CMEV&DCZ2eGq!0-EK@-~I}T_vE^7P5VjaDSv=ibJ{ALhdKAhSYf?zeSoL; zLGGg_L%jwH5#LhIU6Kcw;Scyq$O-5|0>yniWC!>W!HJVbfv%h=T6zEi5_}mD*#j-e zOEh_LK%4YJhfLhJpjQ0t5H5X203VvMdvfLvO#^N}MFrYxG*Y!}Zt^K{L_(h$avdNl z>S$S@-A_RrF*~6ExB3c!B~$+Rj2*Fo0#F483Itw663e*^p#BIHmZTyX7&hEe-AUF< zh}G(f9cVk)tREb=LrC!4AuJ!@2s`4_)&vW-sXqg#U^4l`#gJ2qhiOY77gw<#H3Cu! z!nOT+@v}a1<={X`2B*;@D5+mqxe<_{5VIv0269vtE8#6{9>;sY@ddFRdjK5+9m{G! zLZ4#rbs41kjBHwZ3bZB}X!s1W4iCUi3qsRN^9dU2H*_A5ds2wN&n|(iRBKic5UM@y zF9JC6geC||DyB65L|Gj~gC4QhDbBaxqNacW@FoLT&87p;IV(`;2hs@0$bTgPn?hcW za9KFnviuB7KNjG`gJ*voAiXI5csg@(ioKWmRn^>MCMHdHCX*|nKa_b%1WdHD>kqh0 zG@qjEYV>1s4PFMOnQU4K?H*9wz{KA!QWsYcoHYBCuXJj(Opv}v4Z%V~dF5*C=lTy< z!VI)OKBXWF5oLnfQ6q%6d(@C_OfUhCG$82*zadV5^$lJys2vPQS5%-YpoZ`D0)@9H z3tj*fVG4km&}EbXstXcu_)kd|9)Jh>76?@Y{J(RSi9Xo=Wa$UFVPtTUDe#tc|J~wq z52n!;D3M>|D!|I`($;63ZTL@4R=aPpLVy;I;=Ygk4q6Rb^QOkL=7bQ3a%^n?U}jz- zw%MRFAWJ?JoN0pPmT;?J-$8^04Gh{t|0KvPl(0A{@@~kb<%GysnIiU=f>RPM8YTq} zVS?VRPj@g8HN0UCG&465;U~hO^xX%`RJ){h&JRZ&eD5h{EYQ53duzc#x59lPXDBdV zLdtbR>2oQw#&vfCWE@B;%49LDA1aS4IbNNnlm@l)o(o&%NQrQ+st+as9+$NQHqg(L zrqK^MwqN_HyHSWpy?Ik{#fG+{2jE05EFqH@%AX}RmKU&8K4S8yQ^_8jZzaVNtQs2=3Pv zd~=&cudb|ImJl02M2_CyWa8tpuF}=Dpo&g%h_4*Bx{$J5uZPod3uqcmt*a|-PU$?D zn%(QA(HrqfeqL?T`8p^=G6HqKy7Fte%A$*X6^=2B36!&5S%`Nbe|xpY@@k1BpE8}p zp%{OQU82~6bl#3yL6wSx-RHnp$O%m6*R^Cr9s7W(Qk4s9nO*NO@q!O#OQn=eJKIdd z)sw4+1xwa;YZth-m}PRdoc8p5E+&2AisqN@boDx&Z}dcZPgm44G=uLq&#$2;y2{jP!YhJK=H9QzS!Ds*sH2Fs!@w;g!^HGXRIlGR2ureE{5`T zX)l{2g>>J}zTU6*b(V8#G_b7iJyQ;nT4?wDQDh;NDV9lDFUoqsmgibxAR=&7HZ!l8 zl1G+4AJ^Qwta&X}EjD-QmZ%RI{&X^q`NxIt)9Q6r!#O6;ubNtS+k7{Z`=QPuHNn;N z^V;cyn5Xv^)7x1tTe$>}AckRob;Sj9AD?OPJSv<*n}Ylu$!$c zxtz^q4E~7d*WMA@qd4iT12dC+%@v(z@07MZ27TUgxmu<-IGZIOFHIl-9xp80*)j*C zL!ImXY=#;@ zZrcyX$i}BC&yT9=>u1W@;kazw{WbX})Wk+hny|WBv9kKk7v4$M)#lYmc-g>Zhbw(z zjMO(jsAcv?JeYY{ugli&M+3YV-_J(OMMm|YXm6Hw3l8T?3+J-gGLWXm$)>$BkyK0e zSEka6pD|?MGwN)PH`>V=tBG4EuCML1n_4gME>Dgb2PY7_Us`{SUZtDrbuM@X<8buE z-My&#J+OAx@6wo=!@l+8B`HOQmm&`>pQ?@ykyMF)8#T(^R1;a{vFnRm2)KpR4gS>1 zC+m8Vuku^wy>$b3ee|?}zmkXgB(G7GhC7e3U?=Z(iEZa;O|7XlrmSHDWtCUGwf46= zVwj&L>~ghpWM@Qze+6UY-lVfcA=W)}VL zD;@iN=Pl5E{*VI8l8o>6T>8>Ljk%-V{is$dfue1j>_O3bjmWE{y>{$AkXwz>wh;k!M{3>5L(? zcpicyjY2#IJGXI?&3J``U=sjShb=chOQ9soD|W<{WjBC!@CEQ>`TkzK3toLz`SJOPP2bfNcZ(k(Ga0 zYE4TXT#V!asvF>)sS@tl*21%goC*h*?a3lh$6e3`?j>vtYEYIOM^AL=>$D?{Fn-os z3mk>P9ovAluYLJP`jHYu#@%r|w=0XBJdS{c@X=}pjPj-Y*$ur-J~0D zXm0g>DV@5GJ(uspGf1CS%a}pkx~1>FwD!Rvkwb>Q%s1}%(jqT?%wtvAD2R89p@TD3 z{vHXcyIi*Eg175X$ay>+ju(crG9TaVHl66Ew<)9(r?~?X>at{mSEJn zgKve@ES=b<_~ zNI><~3aPR?rBBPd0GslwHqnCk=3dNzw-$V#7dn~k9|1fy;7!E)WgR4XV+%!R?E=42 zaZa8uYy-gcv1`KBjAVuri+q>Ghq^w}8qah*JsqTZx2id%_P&8C##;FD&D(9F0!*r2 z2KQd`Nhi)*LgGm$U6Ceq1(MMS#r^$4fpQ_Rcg4ZMAnrJyXGCJ7<&@?qPk>$@AK$w21{l8y0^mT%*G5AG!0074z3NHr>A?Q2^q?9Sp_Mbn+)(CG- zMFOHQFiC^r@?(EynE&p$d}DB0IAOks4>V%-Vh(tIL$nGMW#Z(2uX`eTTJd3AJDc=A zC@90R{2)%q&zrRKF1K*F$UQyHYOXx-1i_#{ECQgut`$55f1aw+3)+CQvAa&hCZ9BL z?vh)VU=U8Awpz7KH!;pKB?}Le?{qL6-Ai_+u|=mXM=n$gR%KybyrKJV)&+CDbYc^1 zP9w-i8mbb?qmVv6L;K!v+w||IDB|Rn*^-?cv zYKC>%=snM&+k5eP?a>_&ZRTt{a&2ja6^J*F=su(W8W)0%N|m;5V&gj#ggueD~Ah=&8JFFLr%F^$c9;#?Pg%U z9J;}=M%QX{5^1vToVs5z9zBFVjrDb2^6`B#Elw1vF2IxUk_0!-Wlo8!!xd#E460|s zN?}}F*qrpEbNQzB90p=X8WjmZxcE5#sUlO1$z81n9p#9VZK}5*d{8%xL*~;MB71yIKv93bP(8@eV~`vuzn> zl5`_bHhxOQ0TzyoTfcI(E=JibV!|&yq=c)In}eNwh&sE#(W>anBKy1wq0U!`&{Vo;Jlyo%Yo(S?02fz36eRWYLC7 zPe9q=O;Uao%P2txY#k4NnBL(FkxW(_#*Q ze*3CTDn2M24&>y#`HeJ!e2HcMr^>kSM;PxDgfVnK8Hi7dySr>_+T?7rwQt07`({8r z(^~ot0D~C^9Ez*GkT>i9148GkiMf&!XSDLwgxMu2gH1O+^5^YWLfbE+iXHQ+j>=YA z;xAvUaO}l9l^y509&a9b0jkg>DU00!Gmr(-f^FGC0WE2_03TNL1SvAVp=l76vH*D6 zneo3NjZ*_~2a~hFP@gLWlQY-_qESzO$X9~l0b?c_M0d`s@Z*zCzeB8Ec+BT#O%Ymn z!-ix<71H^0rSGWV%Z|X%fpu3rceLTL=tTuD^@pAC|8gJfCx1f$zabX*-;YuJABV!= zviY!9OGun6GS<^zj_#_g;pMY_xsGPd{RL~S!5Gw{4>5P=@!UQ^!TOPB{QX|Yt+50H zgNCmLUi7y%pzS_S7K4q!_FK=*18oQ_iQ412y%Xbsj9^q8Mt82_7a*GR5Aa)u{wEsX z?6wJDzFc4 z_+f`_gU5Q0(4y8!Z~iD6$kGWITX!a80nXqboz)!v13nHCufm^#A7(T=kAX%6Cen!^ zc4Aa9yeAk1S||1yQDDx$%!2#>G7Dgef1?rq4Mxm+!rlXFpaBZ7Jcv+}qd?4tz}rJW z$^HBXBo|Nw?QIb9zy4r4E70=Fd3YRAPEG?8lUz2y|AeRqRRf~b6dV8_IOBT3!tmX)oQ(>gU)wct^L{;aBC4RKkK z3ZLK?{u{4F-FU6-=}ENi{M?I@E zR%+K&Dvv(;x>aQN<+76Tk_>7__qXG3;6>K##h&4uIH|Pnjn9pv$lz!J)klAeX zu$cPv=1W%Q;anWoSaI%`TOEUyu3nHGhU9{g@^1*kfL7e`KJ)9ZO&eXL?wFvM<4jJJ zAZssXt*Q!Blv<$kcnDTp>CiW1s5v=N>{^u+r1L>6nSWKRp{g!o7X9tbT-pV?&pql_ zj@L{E&U|}k`RUGg$|(gg&Tn%}+$qJ5IiAflxuZ}M5c3hrM zo!M^N_KtAYC|nt^`w(6d*=qLH;e&~m2Zu2A`2FJdNqXCbbhpoC(5W(LVQ=_jMnu0Go5sm;H*D%uN(8 zaC8WfEGWYmNh1b@yoU^QN3(sq#t<5q++LP@s3z8xS&b@+oXJFXnE1gS))QyGRyn;~ z+$qp#o4F#wdC3dyaE8MPV_6zN|CfKzdq@2w{s%LkQ8aYb%=XeI^vEDllwp>qp*;*O z1Ph8gs4@=d`iYJ)FEcRip7z#?HwPK73oiE_O~P(w+9LpQpOQ-4hbWkKpZ(LheuF8`H7}J(SiNXti#i;+pT^V&P7NCCDSmxnIed_Z%=7ZX5t&sAXEfdoUpP4yo z)X}Bdr)I_$D?EWdmOg}kDQ&Dytj218383DBIbSu1w(+!^Ql(PX`NWHx7}X=);5^mI zUeGHoR_c1s3tJo{#wCp@)@?chMaFP->HUg27Za~^*EbCNH@v1Y)*lWga)dE`i&iPV zn$m>N+y&5#Z=3vteWQ?l#84u=8wzIg?u>;t9s%o%`{FFHD_|!s%upK zr$!-s`wVpbh1R`D_>06~SqhoEbKhiZVlKZzz(w3nmZP>yq3 zjBV{(i(O?5j?Lh}Wl?~OS+4c-SM^S|E_x=baSvu~@&WkIr17O=gZ$xt+)o7f?TVw} zqYfZLfALSND7MkjDrV0(7f)H` zidW1aQ=coA<1vpWS~I4175?HRj%ieEIpb$N9#xg9kl4 z)pb{R1qyyXi@i6Go{^wS6jZ&}fHe(PPoqPsy96i?x@ycGPiRQ%Vofj2n5+iTBTOb* ztFwl+?zgG+@gY~zn1bBCnRGsimcAEHT8&D)fTFvodb^|FPB26|;r15sz&wYu)C3c6 zz80FlwN^jsJ?8y_?pg5$2ZPA-jl&L_)7y<{j~lP#H>f9gVlbs#mpmd%ZCDMKqywrO z?`<+~3sG%;Z_B|}8w$%P2|QJ%sEx9T>8_2PUX*h2OuaonWzG@SK#MW1=*VWBef;@u zZHu3#w-(lUTS&joihfy{FF$~O_Tb^QD9Mp{DSi*N_bGazxfiAp74aogRZFhZ5qZhw zNl$Ayc^FvuzL@;=Q+6SHw$HLGMEL;WYyG-EaK=8&%EL4LQMiG(>Px5Y$Lcp&y$6l- zVz{_8ejIQx^KVZ}Iw@Tg^TsUQ8jLITGm^8{M;P++7`WJDT#{SFQvDh3NpqvPcy;6W z8X9uytY98IFMNKMukfvLeZa{$+LKc9s?r!0nJgoP3V129TN-p|DnQQ}%3GS&81}U?A zu4@jIird{2+g~Oo6Z&;gAJ5K!yZT$1zE z+;ST#uTu2InK!SRzRtZU*^JVu9$gojuN$K4^Qw;)eQl;8N_O#;FWuFM>7{-HL#AiP z>5f;~trpV-jkddz0vm@s^lEOtin~hlbW2x#BA=_Dxgpmz{Op(>tS&Ea8>1`|;gN(b znqm{3PhLo}&xtd=l>f;*P=55fm(d=NxZ1EuKT(prkVGvVy75X+VeWB0ahH{4Og`o_ z1CgqBmP5U+LrN~ck(zy8vA*tCy@IMrmjk|u9c7I^&EZH-4%T}kSU+QSS@8Rt%<8hgP8Xr@2j zDWnn2oz}+kP_B1hf^|ytI=DroidL#I6nBd!H9GfGg)8%oVDoX3o86KV%WT1^l6+oc zXXy|ITKDV~tHoH0{p2{D;zafuE;Gu@qMd)j?q*?Y$+mG1r`PCqF#FFtHp zXTdN+ePUP}RH6^K2u;08V6a@zZko8Kmy4_7ABqv3DwaexuGB|p3Tx++Vv%}}r#F=| zHI%BjTl^$6^t5%-He$a86rQeiHFCBLV+pY@tB_4$YfQQ$aE6muOK!5~UiAa^5T3X6 z2Kxmoos|~XTm7UP)NN0#+3T6sul8gt>w}d$GLqP$=GQCbkV#*x;a;5Gsp}p%7Gdmh zZLsp*Dp%hEXW^xKe0YI*a<{ZtRe+%RRr>92nKCWs`s)=PTXz$NRXlLTB`^MWR%xk<=VDHQ_ed!6(fB0!!!9^6l)dkKb>~h zkREnJsgEtzFFK;tvXD8Gjn;K>=8cVreEO0(-o@D@w^7dg?a9)LY>Mr^u|^x-6|DJM zF=w`#F`hZpusq7C^m}E*H0k8!0kdvV#Dj0HVe0%|ulPGBWdL}O3!4}(zg|QdGLo+~ zyC`U!w{u6%u-*XcrDbB=+dYSj`NB66KON8woVhg$ZUdEe_sHvrxE@bICA6{3?nuNLZ9EdT#eH|mQV5zmi_00*OpUE7nGIZ|M zn=OE}x)t~~_jH%J#V>kz&+8Q(_^^4)&SzJ=gv!{mPa^(wK2U})$`cdur`UFGRy{9A z3FHo!UN92en6=BYU^li%alv;U(_RB@T~sOci)-Z-!@FQhNJ*0lYGj`#kCrj(X4L8ttobok{!}^(-Q*uv#Be%YR%O4Y8+VEH+|TMa zzP9C8wN;$@MB_HS(&_V;c8uuHF}dORo)RD^JVcKH6C~xB#$$Sh{iO4D4bU znGN{2AYl*2ylm4FnawQl(^{`s-e#90SLWbjW7>LnQE9@8MAGzje2MKb=N5M3+CN&a z8kf*8-acH+O#6zl z*uB`vpS*lutt_f%_=dl#>q1Xen@H5KHrK0$)Q3L;PsN9JU9}>S|6^-K3aE|fv+W6W z{t|Zt0%T_&dxB(HV64x}k)`}}0d*ySRJ@=dHjg7r4G;=|tDiywY_>CFAc5Cq;Nvo! zp$CYrK=F2&pg#svTT+OC&89v|0Pg@zwIx6qbdHGy*p9?M z|Ee+;WP-%H&+YqeU~waL`Y z0|Xg}d5{?)WC6p?9fl-*cMnkI^}sfEXgVt46!wFc>n2<%(S}6@7?=Yol+wHU;C-^% z^%~>y>pmGSNJ~K%)!bwG95wx3@;l|)gXl!-&I8v>-+5HYNb^UTr6Q4$TiZKS9jlkG3}VHa&#IqSpl;)&KX~d;I_g*lHn$yA zu~WtfYY$dtTJkC@NPOcJFq+cTR5nV8%)Zv^`CiG|$ZyFm!NRuQAXmBVT}@_jNPLY; z;>6Ebae?bw)e6r#QZunu+8K=uAuZl6d?ufIZuLmqBAzjHP-yQb8Rls1ba*b$pC3kD zzC{xiRhX9EI>!HcME|&uOJt1jYrbM#iJr#}!kJ|4k`bBB@g-_5lA$UlE$fZZ!x9fN zroT_Cd2?MDi`g;`t2!UjZ?~o9cU|;Oi>-&PaD?heY`{sj+nKkgm6ukQnAC*bs6WM8 zbEpbW)C}Vlen=8-FxyQjM zej(GrgV$QXg2$T|;nb?8qpWCB6j1!DKKK5HA)|xPrFdkmKfin-8M|%n{h({&4AqI# zxeE{6%C8J_+H|b?D{J_42dVTMShdVEQ2y*t=5CR2mRCkwUYA!%F45FwzOHa>zTPeG zwNYelbp}6eF&4p6Sk8eDqy2A(g?8 zt*&BdYkS;jb1xOCjBRIPlLQ+d&zwIjfeEO`0Hr*ch) z#09Vk2Q9JYtSIt)y;NfAJve(ljlU2z$)97-nR%{9y(J6lF|F;q!<3ef&1_-Jy|kHo z-7uS{joz2r&{L2(wJ+W$bb!Syo7UQe#DxFL>8JPS{aP+-Q1U71VLXT~Syp)%Ms@Qv z4g@*P_HC%x(6P@ld@oSzb9z59Rrl>2pPp8hs?UH;cjUAa>Y=bn?JwI=b>|?bnW@-S zq3eBF`6K5x!)Th`58VNq!glF5saeKZ&(AK;+;nfxANnjhb5%D>(l3|YK~FVz>a~f9 zV5a_%C`P@5mvi7lfJtodbZkXPrrRl>_Jv7x2io3xvK*9XDT>-5&!;`lEH3b=1%sBo zBK3|AHu_Few~xBrgQquh84k2s7{#4EdxT#Hmk6zBo%=4%~)mSQN9*Yj8E&86pYR?uY5;> zOqh0ARo#amnZQo!L1lLS74qp}dCp3YAp`cjH|mU^emYz)Fz3sCf_78Ub1#pvmJ+D8 zD!-DAdf3~SHLoskX{?UVS+Az=pku%0+hGRV`1s7Kj}gM&wB=bt z&8FE=_Nf|{n#bdnr>i|nY6J8d`OH@Nr&Q=;ug=7l?RdUdI2fH&soB8t8K4tG`9#P2ZuXd ztgH%WHO5SOdp{MftEK+zQ@-~6m4JZzo{RlS!6&VnnraPe=&K=@t8yjExWySd63s1_ zITIdc%~~SQhuZ`N#ER*5jNGd7%xqJ`#05OoER8BseW$H86YR#@_HJmPRJC11t+Fys zHTU?}W5cgjrjmrkv3IYKrFoiNmwVZao|_A5C5^9Yh-*!*QM+NzQ1ZnwZ&qg}&%0yQ zuyV{;{1LOD#H9s^OM9M+^Jyl~AR{v<&_*4ES}UBcL`!0v}y zPLG?wua-77!Sokjkulp|;$&mpGCdN$$Ztbgp?)piZka}Hv=|Q4&f{O*F|+~wRt5K8 zEQhQOe-||1Wneg?H>w!aZxS7*V9PsLcVSB3C8y8u^MPn&On5-GM$XrK=( z9L)mbw|~7%X~9JkF)vImTv8KQiF(i=498xJ;EE?&MI z>oUYkm2Pq_Zd>7H`i+f`H)hjHJuOc;Y3=M?(07WuJK3q3d%a0lO&Jp%kx(Vz@PJ3O zYTC`pCLy+Zs#k}D=UdBQZroAZvpn|vIE$MVl=6zUw3Tg|Hkt z#kNs(G4fa1(_8b-(D%{oyegk-JIJbR*oc<3%$O$6Kzp>k)X|rmsc{M34mVre8DFX7 z)_kv*lQz4Om5bQlTc#5idgNz5zR@Bjnfy6%*!*f%yY07~INIkuzR26fwcd%(itb0V zMISHA2wlss&PP2ms2!$Nif?znL1oZ7L!+K4>7=fV_T+w;mJyYa`6$0kCwIN2O3Hk& zA@jC&57V;E&jkDOk^-wTdrz$wGR94}aQ)LN5mn=>ETcA~7n7CVvE5GherE7BCfMox zjjx>FlZUiMzQ>-Tzt!>1?qx*!$*eHD;$a3+V=pl}pfR z{fi{JCV_)Klaa?Jzo=b7XC~NrQ0m(aKKoo=38e_-defWgYPaGK4szV0Yxs|c=)3Ss z6W{;v6r&q=;U!n#Y~0*XYcoX$_p5^5&u4u;&ScbOR6U$!D5|qIRmJ=A%r6aT%%~*b zz%j59weu!DTJEpG2C2qzQk(tc7200&vt2HG|}2`Wgy5J^Wv~BqU?!p zp@L2Y`g#U7D1KXIw1=rAgf?qHY+{mEZRpgsR6UH7&*p}mAIBJWbnpjji5gw+SJDVu zgZ7?wZ&5SLj0(vF8ro;q-cCGk+-)nnXUHL#U!!H#rrZP#?HEBxa_1UP7#|jvQhv+WT#)VkAY1s(p(x>y}R41*w43&s6Za6G= zx$hqUja?+-VQlt(Hs0K5>g#XlMD{M%o`?~O$ya~oYHeT+&wt7jpC6&@>hTs1tLXgk zbAIRU@=DUyGVl)sA`j03RH|kZBGh~W^)3bb5eu6h1Ilj;YDlvQ76j`xDL->CkN;yr z51`gO$qBLCiG*b+PI&fpCryy+ddCMV46m`%wNXWfs9tIo|L}RpviX?E-LtnzG;H*R zC~8tm<-|?oy5D68{P9$||AB&rWHN8d-79jFPAZ+FQ00C@@t*Z_NL0uy#Xx1YF zCp+m5fSg3af0>~QB znF)U!!A)02SdsOA!c83}v&~`C>NL0u0lx`sCqfH(v=b~dVsiUY71x7cEX>%<>U$BGw~eM0Ub`2P~Fcdll~U^RUqc@=WH(Dlnt|BsCn_(=ecw z|20+#iDq@&1_V+rU?Re{ACa*;<-B;zI~rI+h0ldxEPH)$s9ga6B7*&D#}tQF0)uVo47?INlFimCdrfM0AbI>DgmpzoDDv zHc)hhMFl7avi~6?i2@g)!3aX(Y@qd3@;Pc`#*YOfJM?`tee<*J)#t8SSc~cx{4;!0 zOKwkQ!ZRNGSA>IC+@xfx=%@=0v62patCby)Pw}DX_^E$wacpClM zRr!?yx^k6>-UeQDYHbRPbr<#3F2bs*(H;cpnQQo=Y~9Qqu=O# z@_t$I$@7NTv=+(c?}Og2xFXoEJ$-O0{26EB4?c0oYvaj#vwepQ3D)&S#Jm`!~O99Bg7@RXz==_!u~~KhHoc_o36zL!`m}tZS3GsGm&97*QyAVhTc~E|~8g zm%yu75s`-6H=+EvU;@HZf(%N`$m?O*=k~+pD8d@498jBTN~@H6k838Uko%TR1<-Bj zd-TZtOPRnYc!0)>$&nsjqI!gv5fe<_qy&4qNgW8)aE~4}iO~XMQjTy5Rm&t<1^51? zG+A)GLXg4d5Onn&^m*vbZi67ATTBrB=P!xcpM3fHam-o-TTr%Xd5DfPmU@|fE=FWS zsxhbT%2h>mXzC9+mOMEiW=W*AvQJKPHnH3{P4~I%xs~uhIurjJQ@o$;1Rsk;5b2wA zzZi-Y=UZ(%scvR;gF3cN?dF5&ZX<9OcQgI?POGN5)Ux|SR0A^W58;ivs%pX{)gPd_{%j@2|dEIhvi8Xh}1~-m8qd$w1+8TQ@_pTiVjf4y(WcuzD@kS>M982hSL#iEi5PR-_rBZ(W!Ry>HHFt!bd} z-ZMhk)`dIrZroqRq)J{#a;teNBWXL!jqZ?0)@#cLq9Q*0U+*d@O79SCv zVAmUAohqP{d|OIo-*{9*uYN%*%G0PiBFd6}0>g>%G0d_le1lcNyB6?~Wd=-~)E(FH zq&LV8lDK#`qVcCnjwv&-!_)%S8djXNMF3oqxV@-|IP zGKy~Z4Aj3+0tC+E(1Ry|c==e=)*=EQWCQJan0M8BIC*56w6WFxT9=8;r*~?e;5@-W zRa^FYva*Wm4U1x^;vL?5(}BhQvO&g<)Iaa9-T~uAmH}~CoI;jTz9xtXt`k{BA6A+G zQKJ%Y{!_>Z3b(IS?GW7TKyQq@0Ouzkq-j6lfa=-oK+HFQxhX~m@{RvG*fjJnAABKw z75MbP>|HBJdkrH``lf_Ch8{{G#l?U9G`Z~c2oHhVuebV>*iV3UP3XUTz<8PE(rZTi zK$g#Y2Q|4FDRX@u`>*NIHqp_-m`?tlPum+GiL3%E^!Z_%Aee1afq&rwj=c{ zUUsu189X^i3D@L<0#?SplGDToC(2BaVf&@{pE|$Y3#hR{zq(*!AE8N_4x(mh$jCI53Zo2$eW=2B}Pk z?ers|W3#^sBd`29ITO9&eraFcxqU+@vxKMQrj4GV<`kDM<4vgGjVh_-kCd8aGb`c- z+0@J3Z{B>dxp#a=MC?;k;5wAjKqqTbzbBsfi3bva9vSVrpIq)|R>fa>&O2)9UG}Q^ zjWG0{Q}b&IRh16gsUP>6wIE4_kxs&TMBo`KJAY0 z1&&a#sBa7b-)ray8WaZmU=M+a$7(!w1MD@x!8~kj)I1Qh0HP()9u^^R>BmQwfg>NR ztFQ+al>#J5j+S(k1Sr)!UZxse`@jMzMJVmeQfTrND=2l+y&FR~!Ch1aset_!T+Fp4 zmPACS4P+kwj|ZB^9B!wa>S@KM;4F#u=dV<6T>9GwkM+XCKyUcsklmea2b(k(xg&Z|!LD-h#9^nqe ztz#SQ`$_YQcpOB|?{ZMFxBwCYtD)DHj$&vK4{*BP>V*cSa>rrOeP|>Y(Gzpf)pDhk zvDB5MvE?qv(k^TSZLYJ4SO(IBtyra%VW0UCL_K;Lmg3WE+OIgoz*RVIZA`l3i0MP@ zRzv1uAMv1eqVT;)B7bq_i(X*ka5;Fkl7uhhA;}8RJB0!z`yOQPU_niksT_z5?`|+~ zKc~8K7aSFE@2~X9HefH58udfN;n868iRxYE!Xr*NVKN8T(Y! zC)}oAC~{toxs0F<&G5=Dqwa%S#ZAs%rTS-6EN`r1TO%9)c>BI^JLKZ!*2??mo^ke8 z@LcbRt4-^V^SN5fCtx(1kQI}sr=?=j6%Ce^3oWLKYTl97mvAlX?V<^36Pa}1!)Gh% z$$K+GdFR02U_8#`L(?o)p zuCjA(y=P8`@_J>(gSsv_uABnd$?tDN#zY?m{?|1UCcbEaz%MVG0M}fFOjd; zDAJ?vfM-aGYe~ySU0L?|K*QB}ue!`7XQX#bZOr<@cnWoC#AineiQ5(dSJJN2iy7S2 zB@(|r`h98AO(r5BRg1jC<*U~F)xPqmG9V;7pT@Mx)3{vzULKoq@4W7$WpRa!YFSOx zw18Vhnl@}zUX_*ysYh+-{jKERkb)Sb|7$Z}v<5MPKJ@xLf|!i}Q_T5A{<~n7@*>PJ zRPy}C5jCiK9OhsdKLXt@t^nh|KKD2DtqbC^OD0SotY9i3{4GBs0|1dJWJ7uz`a6z0 ztRdO&;VFyJm}AFrtQvmfL<^c$#){s1v2M>n*2Z{GwC0EmY7EFJSz6G92{bj2V8GlC ze)0~UagJs=DEgl>4iUB9CmeQj3w~vf!=L{-`zg`ppGS_M$Cxo;;RtNX$zuhP2NlaB z@PsAZKLRd0GrtOIuhpAr;mGsj%MP+PJG&(A@20oFU+c1`L-ZQQfvTo0rC>9ksR{O~ zLJnimr18UOzizRYrAWaO!o}S2G#ODX9(Jav();GkyK!*Adp@JDrvx}R84996IAZYIK_7K3ZA_L*#)GOz zjDAUtW{BmbblVnr6+Zh*soX8HhE`@M`gy0Xn0LP+4<--lkL%OdRShY$r;c?@y`dX? zy;>H;f1FbVkPKN=ad&Vk01!;Q`V`>izH_dBQv>eo11ob;IMzSgW z&S>@)CSB`c`hKhf{gA?-tgiLswN^IA5|JD9ORs{y9?yOI&P)_4eaXn9oXJ_3EKrcQ zgNb4l+>Wx~<+gRs<{vb%&gMGSHxxNy$mi}RxUHVJta&YgxwpqUldCAiwWj4>#dGC5 zJD23%l$G_U)N3n7cy>$kb5-8FY#Fk6?j*Z%V#v9Hl9(Dp&XoicdqHpSmder1*$ZY1 zPTTgpWejIBEYj;%+iayqyWGo)e)T>DJHcW6xt7ADkD|ZF;yVH2?z1ZpMUdtFO ztzr37O)ni-ZoMhZ5X5Y|S>lGMJsjRK2aU_=jY{5WR*!O^KG93Dqisb0c>Jqy>9at7 z>9egL5wWFYUxN-#sH=(yLqj*GiK#DXC|iVGe(8-h?nV^>s-(?-%fu8Hs?Yh%I|gzzJ4c-_q*th>;9IpxY2 zm_ERqu_^`@bG#EctLF;N>gBV`Pgm_RRDG#HJy1_V-HuxNgkCe4PL8TZQ4mhpLA`*h>1k{Qwrz=0**gQc`c1HJgZFBG z?ivdC?}Gj~njXn#$`O4>ZN}dE12l6IVQM5kf*Kr2hgLuKIs&Kwp{4fEKf6dOy#GNj zfF%&$|6o21FfB`7$v{pB#)<*moLdD4Ge2=hntxSuAa&h1{PQYEoB$XxICu3g=FE}K z7=W`7P!B!+3?ATC&VD3zR@bCix5y9MA=m!p^(4x%NU#+LJ6~hQpIb2O0lobZq;IqOX#}LKa8)>6XosiCArbMRglFQSo7bf~3{hW|Z zS}!&xI7w=Ij7sdnhc`-@SIuraTr2n*8xylszNR4T8ndbvFx?lU7?S+)bV=4&+@+1@ zG_mZ?^XGN>T1;Jp9%&_9j;WZ@rA@;Ib$?rU*G=bfu3}C7te#rN?D=Ao_M8k%<|@y| zkVr`3$j@Zkwet&W5oAkL4vy4~#l-w)b%n!q85XTEkqhIi?>=vQ)*Bd@B$HMD5gT%wrG}(t3 zFSMC3e7l}CcJ)hZgiZf56|u#Zw%EknN^R?=v8R?~LpCA_6ANa^Lf2y4bheC!44z<| z>@y!Nr}R`u>T)J{&Nqt8l_BUy+sFBFHs>rcjs^CG@8}J(E^t0G$Otc(E~|~W^+R7V zre9`|uRDOOV4QYABY4+FH=U8&cD=RKx?R>#Wk7$rPc^(zFC;NsZ9}3_bzB=8*U>vX zfw2)U>xqsGii^^YFzLKHske4~ub2IU^^~!UA)8~dz{{FY%)zxG-|~iCf%K~CCqWol@GrTl%s4U@UdU)}~NQ_JgTSIdjt!A^*JAm>^ ziv))t_^Wr*RvE8X(D&F^)_luVsA;rO&cVi9>d@>H?ZcEf)mreIT?>iMd{Q|#F`1t@ zEf|xL{-42O$KB|`U#Q7`N*rwzQDMQSpK#7Oc3PVCPq9^a7L$YQXg-6zrUrB&^7Aq* z{dUB(f~6h7G`5pM1jD6(7L15DOZpy25<7wu!s?C!V3vd~gg*DDGKL;oL3*F&ynjQI zP|&5ljGwqQ;O-&7WIm^mc*s!7gFA<2jm`D^LiHim3-2I**`NZ`K_?smO z;ct@mWuTjQ!IQ@9(>A%U%)VlbLoBg!W}KyBm3Zhgs+o)BSdQC)_be#}tHgK|1-yS{ z%qwIS6i8?}JC!&NN5uj9Z%JKre57-HT+h`+-WFTbh@`W!XkQVZW?gUH0GGYCX2qAi zNMxW|c`eoZ`S5vhhkoq${<(PaPtsfT8*h5-Jr3^J);hdu4K$_6&wiQvD#~{#`l4>b zfl5L)Pob@kP-1T0r5=;wz#31CR$9t9zj}Pm&!sr^^kDlY_la=^R!^mgnUKONJG8$> z3kTQdPzj-nm1G}@UTVfE7rb5F>z^=u=;54Be8=v7O@~I#Wb0V%#DY}KLRN2X=3#wf z#yuX_QVDA1ITdkr-nnryE&;7FgoT7aSt6@5mX}LPWjM=pwP6b#0f0`g*lWL#-7gy? zZ>^vj&fnRdaWAWj5>=&X280P!5@n{s>F{*kUf1z+hT@Eh%4Mzvj5k#HpSUzCx5X&) zep9CkRu(X>O-RJewF+cg4{1iI#y?E`J}i;W%dY?oDU+dkreEi+7{bl-hSHTz4UGUZLcIOrrHCM9mS-jpQ-LG&Pk$P}^)>pThshMz9%t4* zc^33F$p0g6%tg62ad+v+7O~|n`<|@P6)HjyO@@oJ2K{~b;bdxI;1!tQrU9dqQjY2U zeC(xhOz(eVB_%*O!Ez6XZLqjuU6>}&3g(Z&wE;>eih}@a0~k-RZj%-GUk6mtyRN`; z0?X?WqYbzofzlCJo?%MTixeP1XGVZ@ zFZn}U>tjElMt2d2LEm)+_qO;*P;dRw3Eno^zqm25#^cItDoB8Y>2lzB8o=6tlP6?_ z7p9q5|5Te62b&2P83RO>hrx}4hR=XMgY-Ze>`tczt_fB}XN7T1SDL1O0A+{oS5Tk8 z<|Yi){2O`(6d=B|sH2^{f$AxB9uVyTM2))$5%{|N6MpqCfL4~umktNClN75V53%VN zk8vPJeHlnVz(lJr9EX%g%Gb<(hrVGuK-c&oh;A(cQ0l$F0l>Z@dE5k8S)|-QuaU@q z1lkbfKlbCGc_8;J<9C=Cwgb@PdQ3PV9Yo~MeMp&-0$pi2lw$QF$UP6NAS@P{As5L5 zO5j<-*RM#i?vsvmk?MWFzQ4~3KMhU^23MJUST`>kc4z`p6yKgx!bq!g7HGh?0(wJBuS$$-ln&*s+v#U%~uM&bi?$Un6OPaw#puo*1cMttC{P`)ugG71_ycTo2y-X3yY6;xJG*MBSY=v>2j zMfz#up|+Tzk-;Wq?hJ6ij>=$HN(EzT`JCHYyIt0 zdbh5{j}!djbetF?73#q9*pO*PmF=?OOtMjPy0qw}d@Tp*@&oFS^nvJaY6=Q6X>}Q6 zd^?W3D#Ps`Bc2G|ju2*uXr{klMU|d3Az=~vz1jXSH$B?&)#Szl7CVRH>KgtU-fz|t zl3{U3Gf$oB?u`yUYA<7DdFRSQ0aIgrH_DMl(TP~j`xBAMfS9%DT{_9AP*d5;r)i^n zmC=VZ?RzkdapK1YHWTFIf>IShy0*)yapR9zoJWMkRe7!H@2-4*v@B8NhJIK#=H40+ z5fJn`iq=BnMwg~KWq3CEEj>)Eo7d|xmI%Hn9G zgTAu|$I3|FsD6!o`AU^hO-*rF4PEK|C<{y4Xj}9NYw;utWtxnLVe*p8`V439l`OjF^ zq>s$R>Tay@t!lrtW#)qVpC4~Lrcf;n-}aZYqpl)h_rDhV%^5;W80T(+H|q)rCn-P* zq(K>{02ewJ2h8Lc@R=`qe!`#AoUU2qH$**x1ft$AfN(Mo#KQlEhVTjg@Cz`T&1oS@ zx)xwIfom!bKfV~lC-D$pThmFhNnRQO&V2Au9|FFUEPo^io~t?;wP!&I^jtI`AAN4t zHG$rJ5Cu8sONo=#fJXTv@qbh&YXV`Cd%)RW1jJw_ziFLIKs{Wr-~p#wS;1*QBrOr| zn#o&WmpK5xn;!^)O$7nz*{P<(KsUs}2I1DkLP1jyB`6+L5hy9sLg4=-c=@BUi*P;~ z$DE$Lm+K_dITpmF!GUd1{IS4q&JV!R_zG=sLgT1WSbs&gB=4PA@I}mq$btmGH93}x zKT#oZ$uA29U71xHY^wyme_+1RBYlfFB0%-+4((Isj1nad!6yd$`hwiMVu;T|EpeFL z$py5?-21hT6yR6P(H-$<#irN;XcOc|2(ZLr2WOgBjMo7k2O z0Lu`B+IkIE3%^J3l?D_zU$7jJMZZWG@tbfb2rxch{Q{oK;DMh^SQ!j`*ad44mC2PM zN!;!M>nczY|1J!2Jw&1WFQi3)-F9Le$PEJ=oB<>Wvm7Z3Ytm&wgenel-vTR?Kq8=+?h@DnCPY93^6?RB;V}WVK+NT~5uf}MIm-p9);=l%?6{Cn zU$Dlo^;I^_JsLS? zv6@0m=Nec6Q$bX$5P%EhpvNa?Vc}<{2O9+8XSa>mC9En6p|;MW?%NZh`^&*;CP66O+A!4*N&JJPE7#S4txzHsUwiL!fz-& zJ>^X6e~em?ctsI*7S()p6<_xVfPdBG7|+YbAB*!X8@_n4-d^%L{ShjIw5T?Qn zK9qZ;jQy!oW)3<-B>fvo0Ao2M$rlJP-|DD$-Z-Qrz$idBSV51N2&4Sm_lSiG%x@RC{4-dk;#*=GWN05Mx-e8EGps*83${J{}pt} zXF5dWWkc1OlVnC~GTvULRED4g5F zg9$$cFh)DB&jJ~VNoHT*&Z3w+_RycX&KGQ(ADMGW;E%(?t@@A~Xwx&e&JFMn#l{${ zm}=@XGrLO=Mqg!I1UuA8j%0JNcXkTz-1`hR&CKqhsL&(bRwtMf6avs5#H%K5DL`Z5 z44`~)9drW*6Kd1A>sSF0e<0aBRNA105F-c`$PJRW#b+W2lI%J_G4?m44HRX~zzPHe z-MN4yD)3xx0LCL;sKK9P=LHCls6dpOTL4nRUU_gN27kZn7$9@df%Jit^T)Nfuf#$5=t07=^BmlZ>QGAD*~58vQ=7IS+fAup98 zpS=9khi0~Iwh8~sMX1~qA}<1#fwC6HGMv!M%QLdVItXRn<``-vPcjTvdaA(q$X%L` z?U6-8fE5X)>hndzUIv4@R|WPzl{r3Eyt(f$ndlL6j;JQjrAldddlc z#QrXt1b(;uHc=o=jvkzWatGB@dMR48TyxUSW6#X}GumOvKHPj|+9k1aK@v#H31b!e zdmkMbXX7N~@uJY`lBpv;U~G?zbhqo+OQGrRvSJY&h;c_~ z045WOVt?o1x0>WV=RiQgI)AO)1*Bm(yukaUz?{Dg8Ii^vqvT-0T2_|4R|V`8CB8Ak zuO6B``a?g*N+&>FT}6fLg8W^|JNU-_jj&~hv5jd^C=u>;L15Uj<6#7RPswZLWUNCY zsQFev^*Yg(VH+OuT7NeR)fC10^|P<<98r6^t<66xq~J1+yFbJ5+*I4unS-O~$4^GR zV^^B*D#~-P1xTHT7D^Y9AB?`()}X>^og5m)?c}N&)kQ)kxq+_gWm-dvACr7-0hBk$ zKC`||Ipf#RI;(8#ZG2q$a=&uFqpbCv?o@ZH@9GU|3vY$@kQLKBw*oCrK$xB_ReK{E zr$=njB*oXRo|634yzsbr!EgNvg;b~&@%|Rf1%X(f-~a)EEFK3ERVt5ILtCt`q0|45 zdYjDQ9s^l7jsIL*@|`t99iVrbTjB7a52q45ZDX4FeW&={sj%yZ?SLgT@(>?AivxSC z&L#lANJrVAW7jNRKTXFQv!I8KWI@v5EEr3lG`w{W(ZMA5_`8?kP&bZO|p)L|xMVa6QNKR%jlsOZQ5#{WvR8zh z!4AqYeIX3*hd4Y=T4vt1`*G}U?DdT>QkVn#n~N0(*||X98uW; zJ8_^lDYQQWibNzt5ecpN(L$`XvM<2lQ?D@Kfh3}98X-Vi(&_!V{Gy3^9d;OR|o$jP^notmIkQ-SBgpz`?+@-F^JrEmlbs z8Gaw6R=u_-`>TwCe^!cN>BA(j2G&9SsO;~t`Mkm$q#yE-go7Y7H}XDa(6id^`#N>M z1Bap%Hm1TYq7RG9{v*CfI>Dbjb@9prd0%TDQoLE|&n47Py@6{%l$BvDy->RPKnq7l zPwdzd18K#M-!OWg4&pxKh>LDvU)3-e2npurVe2##H;JFoT*(kmCuxU_F^61P*0KDO z3bQ7(KEoy=CTe}x4Y@8`nE6e4(|phnmSd(Wf59=O{u^olRXtJu)B4D(@wKcNw^$x9E5>ih?PqFyc&Sp53;p#+M&t8Qvo6r(Q@kNJ(}ztPzjRyHp(3u z{2mx1XR*e&?%&Wn$%Af?0U2|*W4iw82GHx^d45Ci13~3o8|&QWD`6mAn?)eQhyT}B z*ek9pa*G1d^@-k0jn;SCow)k5XQc~G-W^+{=FIt-&hAgEL0XFYe`9#0e){t{j%gU* z(wU}NMgIz01t=-4+U|&@#x*HpX{{HR*xrUep=5fw(7Z6e9-!~@h$1Azndo@I5b2ZE z<;h}%)jHedU&SVtsuy2|$j->ADz2rUpvapcs`a-cH256isW%v`_Vx^M>Gv&w4M?_y zbo7GWU~h)6bEC!yZ`TcBLYu%TgvK{s%a{RZ2`3%g2yB6diEv)v=(mFLP6K#FKmffVHb0BE^DW*HO+d{Ce6aXJL^fE;k7v$ah4pAM9fqFj_a z@j2-qcqT;1{}R1Hnt4RFLi_>aHvbEr2{FS6j!n#~7s)Z3fm;K{0Dw>d@d#Qd6wv{r z2K;#@cfmdpMk&I8E1+rp1UNiC0I(5z%BRUPNCEJzCU>X66(Gm}ns(Hm-_&}0=&8M< z8#R)KDk}VDZX?;A0o5B%(j4hfE0RkSss(JrBdRmz#t*Lrk=@gFI?I_~In*+%n11Gc z&ZAFmCm!;Y5*220hM(H}(RA#rqE9gi?-$5QwE3 zWaeQ%9pN;TK++(H_|I_)s9eGrf-i(^gqyYTUl>;+M_0(i zA3(4V=6p;Zxy}BQYIQ3|dS>#-9k9eW58MHP9AU6KK#mNc&Ojuaj5rkWr#k?~xB!3% z0H)ReXc#d-d`SV22H*vl*Lx?g0Cz+irpR1djzo~uZv@7;RFW3WSP^0U(C@(e9J|9i zup;CF909s!Quv4d$IXif8tQoi;5V>)zQB-a%NF^Hq zUMk8N=QrsA7&4dmKQ96so|sU^%i1&Gd5H8waR=~KTzeUdJ54UQuK+-{?ggIfG+Nv* z5Z0J?YzL|7z}L&GKND6oCeyhCMyTcd0AR}y#gd>M01D=W3CRGc^)lN0d)G1gwu7j} zKUL<7!1xf6BQ6@qT>-8$-9_*t0*$5UP^V%9HN}{@gV@-Bag`M_s~7-x(GMR$yMWcy zglb>|)R(=|Jj(50Z3h8tifB+4=mf*ZlxBb{a=O+78yo)Z0EMmbkoN?56j#aKX@DYymbWggi>#iyFI$k_v>5{0{olMmX zzE9LC0ucAM-43hF$oFrZQg>Y_Y{zVtzCLy58hOQ%2$7&aM&i9qfhDK&8v?0#tUrKL zRi+S_gM28E^?G(L=To5AAZdVX9|Ar_K#mIq%;;4hGX%m0AiW>ogxp*801j#3?FYGY zC<`_a_836z(h;sE-M&h>XaCQy0~O>G)Z9ft2`b4+!ZRkUm;Ny(w$({^Bp}OKA^MAhpl!)rIMIW95>Bhk+BnOO$*|IVGaU`6W2<8_H0J#Q7 z);{F|$=_@?OW{PgkmQY_e>5D?nV{F7@9C<$foEf&&s?+;l zy5--R^4}Z!*m*vR3EPiJ$bFr|wXoHFA~5Lwt?m9^Hp=4P+U;gqYU9J2$#8r$g300l z|MY)q+5a}=84j2cvvq$B8WP2zAc)4)|2oO?(GdT?Yu0~lbYj%~6p4+(^(UP}*~8j+2+=l_i@Ji8gu(pr#1@kXh zDY6L#F+Pz_N*WuNBd-WO`ar>qlsmg(7i7aMt|uYKBEhi$xNNvAxMIZXo``H<%78^9 z5{nrFo`0}vtou#w69CK6Q_kLSy(uc2xQi-)`9>7W+^IPa^OOtqcZ6CFUeC{g((l*0 zA+muj&+sp05^6*DWWjqMHUfEKXCfnanzDCjz%GouWeR@q6Qf>9fpC$hYqw-5VfL{C z-(mN<-`*yE5}2C#brLeZXy8)-k5&Pbtiejsf`k1v6~pk)&l(G@Xh-faV7h=U^#0Ep zRXs;{}cQX}27#dl5L+ zx@NUW^`PY;GbHyWY=<46TKDz>Ml&h*#JTjC1==|MikC)aH7bQ1R-e2T1dGDnEO}gL zWV}h8pHFhqSIBoVt!^}L1Y_ zCMoAnc|M?e?e*!FnYq-^+@xk0q!2>vh6p;5WR90TQx1Cn!F9w+xk<%(U>Qm*NRHx3rC{I zIFQ!YdhL?Bq{2>ey1U1PMcn)db)GmW|T5*H^jQ&!MMPvo~Dbw!^5;;uB6!}B zUE*Ebwy9!|_mj-0CP*5!@+S`T5{6H0ncSC7O?C`jvm2_7H2J9g)RMvW%aBW>fLcwZ zqKa%^ul2VZ>R0rglZ*z{4@Z}VqO^9V(lT4;8(SpS>yeM1WQ%zs+>gIHG|lDsELl+f zqxEKKfW#$sMhDK&k&N~1je>qF4QUTn%gSe&_rs?6=D>osmbS86*WExy?6TU+9&bk9>J&YxRX><$eJ46$VQsYW}B za!r&u1H{ZPjqV$i(>UcA?`alFycL}oJiK+(sDAl$sa!!J*>l_-WF}BR&RT#;U&Z6!L!;lVS0aE_pEOy&rEf7X^6CQ zbMxKTGlr%S<63gM6c0szoMhLHXRXfh2(c8nptI0BSTwG`ZBp)a&}LlYj4s~3C_2a?Z3D%88$LeXewuuJxNW-&0QM z=BfLO$_=_kMt-<2X=W{IW%ec!k4xOMSphi%2mm#PIG)n48%l5Q8_JkGV>=hUvQ)%t zCLW;~@qp?{iJ@>t=e9uBn0k$+K^AFS-P?v=d5q1Y@v8q9apxV7)ZhMn8&{&E~RT%YTMc+Pdb->-|p!{r`))AN(kSD<2v2-Iy#&uWgrwycKIAt?S~ z+A^;CNu6q7<)UMWZedUIk%Akzl5R?6G!9A2unD~G*mf+TF2r0t*GQTS(RX#uX{LvKo&C`zM@!$H5KulZ^RacFw<7k@hM3 zd7~7%-^PG#&i>G)oV=b^G)Fl-X+RETXlFw(V{w|Syk>BXIW09k5M~fo;}3J=!u0E`&m)?$zBLcj2=Y$f&hXS#`O>HBmhP9vp9{W? z!JoTlxnie%=3-*g&h&4H-xn#!@6sj$l7-lx?@8IZ^8v%4ISL-xVUCk(x!vgt94o~l(bk72t+TeA&evgrpNp!wd0YV3r2c4)n+44u z`NPON6RjXt=J#U5=UF)rK2kt6Fo3D(X&m8U{$|6>~jVNr6(~zp`=;{df$6$5w z3^??7k*Ea-BAr$hwaY~TF0W;NBMu=f;^OKgny_Mb#LL6C3ySuD8vl&p8no#w3w-BM zQk`WgS4JWJB4IE+>va3%43Ehn(u7Yr7~JgBSJkqw@cQjV*sDH^k-m|OGlD=D&W&z) z7>zWZ*22p-H?V&nBv*$USqbc;kjw)621z1%wXmKH&^$`9kLWYN4s<}Orr8F(d&&$d zaIw@}xl+p`M$R_m{}@$&t5S;f%jsz;Wrq0&u-vvTesVv}=};{*53jpYe-$HR0 z$5Hk*g$Bt@g-+(=jDcPq%ouhco?6HxRj_Nu>PNDDJ9WWj(rY9Aq>IBb7vI_==#S=0 ztYS2LR{Ex(#tdNma!h;fopMidSt*yX?}h89ok3M<-EC?^s>RXtp|JZ^w zdg^k2W?><_>8nm|;s{a2DRFoh(v_w>%pg~A9PCr8Z3c3b*R!ZpMDxjVLwyud;=DuM zYdloz5jbpm?ZU$7w?j=!p;6T)`H(}k__s&fY@=gt&{cdDksiCEkJ5oeY=!`g^x5UxdJ?)S<kaat>xZjJ57Tt(HG9Bgt35}NqN9_%LTSp^S&nM{D zYfDbl^_$wk_(|TPm4v+sp&*~GDyZtdmKqZr^$4=9fr=k`>apmUNGxIy{qpyYbBNE^ z+lmmX__$ewcCY2>#3W~_Bq##O{L_4=u&rpOGJH859zX4;n=7sfzW>sSBVR+=xjz&v zhSNMd&Oh5_w{$*;-?FQtj=^%eF^hLKV;`CfhWnUeOd*|9kH?-hG%zo=^g0KF{|GUNMjfX zdgJ$I{D`frX6`VGi^}`IK|)O)c-vJa0gikRN0y9lQY0_0CW^o zD3#~Ckf-+K)7a}9e?bKz1v7jnTRSKV0J*tR)d2rzZ}X3731 zqAf0dZ&nCJ|7?;lN6&O2erzW&3ks;dGH)#c9pTN496DRYH}F}VU)%YQul?Wev%kw0 z&QjWZFPq*0@*^Y`l=t+^fzO*V**-w@cu>8Loxjn4$T~o+@=_Tn=5cA47i`k&%gVMr zRD6gKDDkOVX>%!K2!n7hQNT@d8ypar!T!MSPW*g&6CNJRtnbKN4Qaq>d@3czA#n zt{WP9YGnt{ThuA|Fk1(fPzD>Fd-WH|kB;E0TxJk^B|4;g8y6*VEkkY}Wc2h^*n0@= ztD&+c14pVzbQM*6R!!adSG}Q>rCxP8*B+GTbpf5|x_m`5!gO?|5yNh-NQo#shYkx1 z!oZcexs%-T`AkVKOGCHbud|2Hs>O7zw|gg@U>}R3IB815->9*Jjahx8zb5-U)Lda< zkXX#%Hwq7o^RFOFr98sb}joy8@WqzWprayP%S2-xRys)3ba`l9?opxDr6WO~@ zp;1)#$yRPI2zPg{H^WbuAcY{E_+cWfuQ)k#qr{URz@5KA3tZwLMwk|ss+pd%Xy0Df z!L^K$3oc#xbm*1nX%biC^cw8-)(aiL;Nfu6iVDZF zVx_@rgMq&Cg4R5hj&SzU-*N`<@L6A8Rhob7rb=$R^5PnNy8*K>7XRiYyUUJr`oQZ2==zMN#`4r z3WlqZqnFMycW2x-y7A_-L+y;De99fm=jkTogRj2cI`(SM#FXuo=>3SH(UJ&Bvnzh{ zN>#V9NRT)$5JAn%vMqnsUx2BD74(fdWbtfj&HaP(X9(6|!j0NCd!8Kkt6@VP5S0nW zP=(73Vt3HD*kv+hDQ|Qt5a4z2>(SS0UrS-PE_r$?g-d&7Hs?J_-Z+hTXi5iWl zdTs;QDW)!bDI?|^JG~3-^{1Dr>Hn>Et{ux_;+uZRXUt zK})qu-n!ycj3mXu>g`KFin1Nj_%zP>=^)%s!6`vFvrk2l=)q3uLpX%u-1O(_=D6gX z*$EIZxH1y$;>jF#(T(hN?Q&DckLthA@c7}M`|=^hK6VQGapauz0Z?ZKGF^q_kN8wN z6)G+^##z%iy5;7y{z~zFjd!JGP}9X1_!V6&Sy{eb?O3l*Uz02(h(L; zJ^hLLxYX?)9U{Bib4)#V$Sl=G;zrBsMs49FE&+$YL1&Mb>-Q~xtEpLX^;jrt;#Lgb zLHU1Sx_;~0SDroOkk9Coy09p-r*S`1{=yziM0n6EHSG|m+kVHutX|jbB^LByf05>D zOhy(BCC*{sp&wf-o_U-kmWX&JG5H_p86usMLb7A#Y`Rc26m zyH>S9`)6k{L#f@{&}xjW3GMJKLB2o5dBUG#*=IZ3jOP2Tq|ZRa1(nO0uzv~Z4>hs+ z1PGfli2k+k)yu8Wm#r^`*3TX)KDbt37A~|t_8uDhWCh}RH!%`jD+px%LJm{cDhRb~ zjrYbh{qA)PcOAoN76g3_j(dMUcr7ODMY zcr^u-WxosZ8Rl@3BOxIPGs8K}dL8Q5>Pa!^4DWV6C*T7FOwcyPu@w_&j(HH&^AqOMuI< zrp|VWO|=Nnap_+jtbvY_eddjc>s=5R`U2ZTA)HhhXC)a+8FA%!)cS7!5l4~=zI-u) zeQ0fZ?fvZ+MRTA<{WndA;8zs#7yG@?pR>nKH~p#QOvfP&sjKgD8kEgK_1KAY#ZkH& ze6-hoioS}o7UaqF#STGAhTiEZ-_q;Gt=v8BK2n`PL?gGSXg$6pKAC!TH0YwMLC&G4 zmu4H_55dU%Ivw(WKv1Cgb?r|WR{_-w=yXrhSyp5R7RtiXxO6ZH(xzBpjsCJKWd*U9 z%oN)08C2CRf8vxz5|~D=ki1J!7VWBENgdWGW z|3F3omF0gKqd<$b&U=$={I8ar&iN}V?U`5jbEAFJAz=jc@Bwb@KWI5+A&*ZJKzkUFnTYZG z!Z$VLn=%tj=k(c~K%9VTdE3+n3?K*H?(mlr`xo1>Io1O})V|`>0XBd-Kwh&mcC>$s zaP7RpnHz1JHUZ$AM}#!yF-3rIg5EGa1lR<4YJ&K;);Cz2&4CT#%+i*DwPAkWUlwdU zAP?ZCvDWSYO@Y4yJpM7&xZwcx1i+sSeO=o^5nR_~bz0>HR_ z6w>zr`X7Lk9{374>9=JLj9#wUbsTi~Vr^uB_sHQW(NAmi)U!&@l(v~%l$0|$10V=S zhX#j`of*X{9z{&0C}w9lX8db6!FR|gr_O%LW0KhP zU9nvFC@hBnujyCEMR^R}4Hi6@xyhLmjr5w~7jy?O- z$F9$TIV?&6NCH|Z#sxsoDGf4s_BASfNzsnL=Wl`U^&a#Bd%8?# z2#lY`u}()=t6RIU$|-~GF;VL0_I-u;-)G(%ku=}e-xyVqw0PGbB=jaP_|WPQJY+Sb z6#qSHxTEWe{k6PT$=6JC+xh4@=K__eH7$Z`cr9Ov6#toC@Q6`UrxG}J4L`oiw1YN& zA(5wXAS3+vpv#3v!Jq^lu8Wu8vbXrIJ3EXpdVH`f5#p*vR8fW~QW41$zDTRAr{I7E za<&(t&+~1z8=A||RZuA$EU!1Z;Q;$GfodMj-GR&bFTrq9Zx>JEwl|G<(vpxx?UY70L;Wq}< z!uk1YWL|GEyiBFT!|k4meG)o`fv;J)nj0pl7^gI79kTCrb$kvxg!AMVIOuw?Xc6vY zLN?z|^MGRJN93!HeT|v-nByO|d!2Ia;y=PxrAe;dKH?UdE6!Jue6t00*@bav1M1|j z@iRyE7LHol1G~cX8yGsRQ||x4w3=|&Z53Ls++MlfNQ|0W5=2B2gX!bcT+}zO3;2td z)b@HwO<&81iipVQd>Z6*@=NXLxl@m262a++CTsA%I@u|5nRr_cc#@hY+u|{EqV>(#m@1t0TOk$zBn%;3N>lXYJ>bC-oXa&;xuuZMjYs%p8-CJLsvji|}a=Dh< zTUT%rVNcwgN|UC#(+t!N#0n`0%uXCknNU9C;CfC8*|UcoFxZ&bSsI%13ty+}_AU>h zwkNnK+4J;xPy;cU%b+<5LgBO{2&|{bG z^mnI(>aGOkUM436-Ibs9@gdIoY%fucRQI*~xs1(gZGMr(t@LH`e=VXrhga9QzTIiM zm706&`)bKigSoBNBm6x)X`|H7KvjB2`znw%zjV_k3wV5->$T4_`xldnNN? zm$ibEPk(`mQ70|cmfX+cRYBE8%Ff&Uf{itL21VOw50B}etiD0b>s-n*Egu$+{af8xElD7 z18Ir8e10Fav(qh2XF}4$Fd!2e{wtKU3{#KpdI|-j=ru;2Xd7@@{bFSFj)Ji3)l9UM z2Yt9xW>(|25$GYQ;+d`1;H28Ki_a_$D|BQ%Y_)z|x+kqu3GF-29{CyWy{pZVPcBt~ zoVRJ;4YkiM3f1q<3Y>n^cK%;Ndsn9VIm5b=j*a>A5LPJ9p4YtXy17HZrBK(bsPy4+ zT8>Z6zIaVG-g*k{7kd*ku_Y-AvFqeEP*BFZt^>>Q+`^m&d1qUz>(=Rxcj$r6 zh8W<=5xuea)Tm`S$<{d-*)>ZVYHk1&s&VtzJ>M_c?wGdCAJ*ARIGb|1G4#@4)`TPV zszS0o`>o_1gH-tw!d)4ovkxgb{fLnm`s^Cs+0&PX7(E428zkt zbdK8%bax-t-^C^Ue^X`t@Ac?^i_{K3M(1bgm%KMjHZ`~wLV-}&x8Jg{KvDJDI-p8X zN;LvXrN4@Wyh2&57C^ncLuu!up??XLpWWXTec;39HwWDgPZn*P(~Q3c$WlrSrvG`o z!wsX{jZNX_k=*O^-)BvVw<;+0V0MncHWmQ2M}rX!2UHQi+X+G5PYkuN096dkO1AP9 zaJ^e0!ZwhN1B!qAO$A5mL=jNb9(*S36>=SrE&u5k@_qI3FB<(!L8Pry@Jvybomy7~G-hg0tWfAZh$w#peu$F%@x z{v+Apzvkxw9nA}+@azN4Mu5XBw||SWlF~ov*#Vo&xgQTKK@wQ12>|}za)dnh=$7C^ z{IV>dpz*ps(NYP_UjXtcV`8)lD0zVeu5Yeyw(i;5zl8n(7rw7dp&_Q@g5~3Y@R5+3 z_xIt#O00&>SKR{nn!3^aj(yY6T08!WLi6YN_ouDr0jR;5mKvdmsQ(Y?-MM8VYh|fT zAJ^gY5nx11ZgJbI@Tz0KUf%D69p#!e!lUKNHC!m!Yly@OTrD5WpVqG_=|?Vy-p!0x zBz*tY6XW<3N6FaAmN;?y;y!kFA$F$S=qT-GS(f{RAkiJ|eugjnadN@|b}*Y&Qw+~d zT&pUAXG@d#OkV{?o@$11LxZ&V>BQLn7egHmIkd3zA?4)qI5-c62%V}^3z{s_aLYs_ zSNbF>(rPVJ^{Q@7QW$bA^bTMG9$3CXiLWQB?Hd-dphALN3 z(ZvnX`rW+&LjF%nu;Xqt%`UH%tL%(sn|>$0;F8vP5)BL^RF}9?bKU(|M?E^|qe&yK zkMpFfl~tFWxLT!jnW z;uWQ7*g(_NZMvI?$9hP+MkYU-$|l@xI7VmWwE-5aa&|n~GCqd429W;;CULc9rbBYB zr$H-k{8XqfJ-4QCl3io_oF?X#q@2o`MkYGp4o+WX`Z9?xcqkSeOG8==#wZmY%PF;r zUy0Hy+R2_C4sboL(dace?Jwnd7PI%~dhvsFqhRG~3$0qNe0{FTgCB1jMvq_Ey{8ED z?7PvbK}V*mhVLqa%9j}J+t>#IGtOW~du&t?*k$O*98}AOZp6B0UFXlppx*T$=5UZY zsb0ZSfi_2fwaVehgsih3=P8gvD_nD6mY<(|Wad%#!(fR_bI~0!M1>>ImU2o92fH%F zcZ;VCwGuN^#>e}ni0%b*22fp92Yb&+?jH552<3s|{<t9j$u3)s36)oWTxH3`HTDnBID}A9h(Rg|Vsw)pWL}((;yoHvg zETb)SDtz~2oRuP(@5h%0PbW`ttiL47oOz6(V^o?HdYlPuiwgYK^cP^hHmEvGE$rE+ z>P}G5kJ*&AH;lOmr1$QU2g6TD#i@w{CY(Vh*7teVi|InDzCnXl#-9NRGjnm9_D?OGzF+z#^j`k_Q_{$8L zNL1v>cp@SyzK7?mLQJ56{RR5u3_UjgJeBQsqu7#Jky%(Ne&P48FIM%rEOfKx1smyh zh{x=lFdckB&f^9A#B(bW?e@(HTx8Xw#hxQYN6X-)({B{}(7I<{Hsyoc7p$LRMJf;) zABhv{1i`g48IT&8Yru%F6mnmS`c%!*{9&7jYY{P_CcgQT!#qqysBj~P);Eq zw|`hC&PI(RW{u1e$W_t4A1lMBzUw)k%;u-e&^6k=hwHw+5vt$qY5kTj83eThcRI~4H4 zT>}O)a7dsbFbG++fbQnAQrt{Th7=3pGT<1nGbm?7hER^2c={^cAjRx+4=OT!`YWz5rWjt8x; zKk1NHO9G9Z^Jw|%lLWMa%5T>c4m6HQZJLV^ zfx=TC7R=5lauR89_sXZ{`M(E#spQwDf3lDt>BO`eb1b{gg;y!(4zl5UciGcGvh67+ z589Z>Gb@NQHiZ;ck;B1IR770NcfZWg&>0m|x1f1t5Q`QVSW&|M;A;B?n`C2(D$LM7 zko*gM-WNY=(5F(sYAn9=jEA|J3lW9vt97UEDMxaM{ZBg}D_~j#X8$th)}6PBw8b zPE%a3a08jJQY@TTBcp)h`;2_i2(HQpPd8Z~kkuV}Qsw%DoG`i^>9J6$7w^Ba^#dyS zl&Jb^BXWZFJ=mfCgcreh;`jM32?iS#HFO7osL7EM2q=BX=kLa{k$k4TV@vs&2u**~ zv;&t)L+kjy8PD;HSv&$cT+$CpFu3&Gsa2=H!5M$0`gEbp-7Jz1cD6UZ>?{-O$wZae zwd%8eUw`80;2GQ4Yd^eNMX+hUG*ZbJqU8;&ac3OL3^fL6sWc;t;VTe~+jVZmMBPWTOO$nJpcz zy9XN!VD_nHFVI|uS8vMVB0UNZ4bwOW!?ed?sk zy%l@%zD6s1M238?(ov-XVnIyV7^xU+SfkNAo>S35HXO)VX*jx}AS|{vFoI7_i6Ept z9PY0Hb5D-P6y&}1ID>qojlHKV+SmI zn|^)8CI#wM@$3j_lZe}2gP&$v9v^g?5bye+T;@8Tk=}=eF=P0;wRMR$DmooMYg!}i z*;$exrOwk5!u=brBl>d;D$Y_&<@m7DVVmHWm_w4aw}*n`oB(zOEGs7N=Xq_En-yU! z(P3(8py9lVMv!4so9^)47yF9yxW`8Dk zeeLAkF~)LQf8Jv>{~nBq+Hz8I5`5>7pg#&7<+-|0SmIRXUu1k`jvbmU2i8??cP$93 z(FbivG|9SXqIO3n4j1)gyAlwv)8PWOx?~KF8aiC&&MEI2X~*DRD3q0&5j63aJ}CYQ zAWhu5_?|R=Vw*;&UBujawnG?BkkJGeh3Z%H!UM-f5m3FB)AmSx1uI*mimU2;`Uv%F zBw)IOXCf_Ob8uS9NXYNes7^fiv;w`U+m4tOzR(qXxLdq);e09M!TFa@pFkE?d_$(# zNa%E!az7r*kDH@Co9lP4iyfWyS;4{JFt7gXAigA&=!SzYQ=3Y0bU0*zUjMy!B7;lh>b3YdfIKP*Ubw>YIm z6LX(UyQr2d!hW~?PVT-lNra~sC9cbny~{%$vY#)=YQrEMG;YOuC9f*(zR2_Iv;}LU z_`36z5g{+1EJL+4v%-IL-kK~Nt9}OYd0Xpzb7s#GO224yeIJ%qi+?t=I9;SeI#YX_ zZ4aH%zJwLH&!be}_E7%lQEwZo;QN^c1}e=d42T=Nq26!mTbv4iI(f}l zj0d{-SyMtYR7&n4MkaqG_PO$97s~}m<->L&exS_hZs9LTOQ_WqT+j6A5X#fdFkVf; zK{Q%1#}r4&s&=#^W#OxPhh5D%;~;nlDDxgQz3=^kVn_NOyV$1tvGMFGBmtT%6Xy&3 zdp%Qoin26IA&jc5XH`bqS3_+o6K0khU879{I!q#1*25-2OG&-M>QbSo@PQ3+v89Qh z?gT2U4c+{<_gwBZV?Sr_pt>s{=rI*;u=X6y=+niA=rw9p5PRFCo0usF30NB9d`<&gU}Ji%CC~# zAQQ-=*D#7PPUx~)xB}ynXr9uAFBXO-CM$hKh7h2E1`BkcW+h^JagcIvNyv9iNem zM7j9ev%#TdHC% z8Qu2Ihk+`{TF5=O;wHmq&;2D0W-2B#bl^jw)fV==(uU_0-`NH?8jchgDtM9k`??1b&ua_DzWpMrtPz!w8Vc)3cu!<18>J2N1knWL1VD@6!M zb88D4(w^=E1Wj)UdUYwv?BQA|g&ij;WesNyA&_!u!@yE(v?>YN^&$Q4jACuPZ&y*% zu6LK2j8l{Afb5@b7aA8gNc79B{lK==N}tmvu6QdeThoRrvutV(SA33L4FFn3<8Du0 z7&l@yPJIpG)K{FR2ANZ=T-r7L*Z|DWXFWO`wG>l89t&{F*w`jwJ7<(da%3#ld!ng zCgfFAdL2S}-@6mZWwf~O-RjgMWAJ#<+C+HM?BoE6ePwU(2gQmZHOC~!3jzJ4+4U|q zvzQU6jaw4HKa5V@X_QKh&1|kIa%-@rgDXK7do0M5-+JSpA%~77HN6EhcMGQqUA+3- z9+#P3*#+Y(b?*0R;`{G*A1U2fo%%Y<4iq5uHE|2TWcwAj-gq(Cep`GeK(<__^|Bcl zEmzbT`ipx{<*l{U&QDFX&*;=gBywT-w#td{?Kfd(e&&U_+9)&ZQ#+|mhd=g+V(l{Grei}RgwXIz1y}ZZ2J4zt%MIU>rKE^0P@PFhrejk?+c5@0X8c_T1RD3jnMY& zRI`jtVlGgw+Avr*Yo=}iA5P6pA0i&2wjkfO3(oEr7{=_}9yCd->Z|2bk6IW}=Xmap)KaAoI2Ui9DW5-Ey?k z)EY3LPk#gK*RV4{=XlG1&c7Km>`e5ZXaGldm5VLN4jdL0;x%QZ{2wVelwedNUsfcLZIM6Ou| zV1hnO=qM^`62h{%#51H>y*fyQO**+`ui4gpQo0(ih(3+kuEZ;yb@{oB@)*J?g(J<`dv zRn3NHBw7lpy&zyJd-cF~C*Q=|Yc-=&kcWBTQ# zp6CCyYAb7eN1D^Z;c1TNC~Bxzu-Qh9XI zl?mZeeJ)o2PY$+-AVNlKZM=DniL=3*S%cDnd`pY2^&M_+C z(B9pb$0Zo&wM}6=6e@h*|C)-eq!dnOixPepu21FT*u;i>Bt3N*Q#dGSp&!Z2LdI%& z&V(Tql*t9x%S7G3(SmSuN*31P7xmNA(j1KR7UjBvr5qHcKFHZBEQl}-^)(`Y#@Gh5 zw68ex>xL8UISl?tV*GZ@yf}q{Cc z4rC@4NUErNtweccyVC0r*rw*BXEV%@!BOY-uh-|c>+gM!mx`~V9ZB^_GfR8$Nj3kf z$|{4b!+}D$rYP%}Mc7ea|8iEgw+SD|{#_9H!$y}pU9VHi3vo;~;Zhy5BV5a60DF+N zhwS^73_Bb`^GL|~vg2c_N7PzRi-*Y@)mmTMF_3cJ*X0w8ffx;&A`BaWEwBLg9TCG= z7fp2Zog-78i%SRu4N(=VF= zCHrKCnm@X$Q+QbAE~&xN(oyI4g-FpeuZ_Fi!1ai0n3H>FC|Ku<5NzAI0Ky#)R^dGC zLyTkm_TYf>llYIv%j?iiEI2%NhJ_9qVa4_Z&hhJTUE+WAvS*Z$9`i=6}y=?*-6K=U`E~UMC`boB?0Y4%44hvb_k=D+1 zDH7kM3tAy0B1g)oBeaIF7DFsUAB8etKsvr4XYQZ-(j^t($rU?Km>12+9kb7mi6Wu{ggPADQq7H|}H`0t5ucOC#2#de(&(BY?iNcO{;u5EV zGrmm44o>x7Tte&dW8V+>m@h|+R2iwP9=Z2S64CCJVYu;A*k@#i>PS}XJYU~o8IB}L@xZ|#>diypa&PdYw^CmX``S-h`z`k zQBI;Y$;AUSn_K6OpR!M`gK9$-3h|+-xwAo)xWZpFTMuv0&`H9hci=XIFrB%E{1`*U zqBm?8yJq)>!y;=RsNASR`$Ko4vU(PuJb|R#G2EXAP7t!^u}0z3NbI6wZ6Hk5M%N$Q zS>eZlSD`!{5Of+3V~?B{7gx*&v69geHAnCdV@+Nl)lzEIyAO4o+>vYHoZ1Oh%X0N7 zxav~dn0#RW3rjN%FhWU}l3np7SFVqGLu1mtAyuU7VMgp+etdfRwbo$8?keLDy!ez~vh;hW0T0H{(Y0Pw z<$O`k1n+}E7>HJaWiK>h$@u9b<;)M3869+vCc>|*x6~-wARfOC2SU4fk9!*Yj5L7akvx4&@ z30XGf6FnouxFejZ0d9up(Y{>nNzyR`^y5W!By2Qq8@0CvyZ%JRHHPQmL>4c0(6a28;iF*xI6RI)EM8`Nc+YTnBHj z_Wr?GBYt7VR$EDqVI$wuK0fkTijhOUyEWwIX}|f|ywd`AbPSpDd6I>X4m(%dpVxFe zNa)z%UABaD@-e7!xh_gGN_hpPh;QZ>49LsO z>g=zf3`$QRuoRChDqL^A>+MUgeICA5W_5s!Iqq0PFb+H&DS-+zneBuP|)caX_XiXT&?yOvt2VWtE&UQYE@TbM~s;FFf zYLaQraZ{J}WP2W54rQT~V`^(Mxq7~u_jTMJ3>EpefjR*`H;S-P4!3_v<;3|OU#ohg4b^V}<AN72ZxxNZ2{u#M0oC&?(9*)d^Y6qmk%E_&{A`FO@sE#;&ia z_z+H+1uIuvMxu2G`&^A?H^AKBUi!@WOp}DXD2!NuSHf$p1nwtlneN0H@Cv>g_&9nh zeekY5Jp?Z2Eb2v}RQ`yOSg}++jr>K-m#`QW*iF$QX;}8WHM5^E4B^*4)QIAiJ7(R1 z;|2LF<2^+#ZZ~qjxK6WAA2#aL+xm2SQKvb${FO%WBxfba=fdNh8M4A$`zeIL8A#(O zG0JP6Rx{ZL>vgF5h#qywi*h1S8qm&lpot&!qAtaO$;$YLIl?J9xrs?=k%jJ_)~KpR zJX22i$y3G1bpKsVE>F{SgsvUjf@q=96ld{p#K3SQu~KQ!fsSz)3`Y=V`sg!!pPG%? zaF6t?gSKCorc>YfeQh+y+pimQ9bcMDM4dP=2|1BWbUCh<=D1rz*iN40($46cqD5Cm zVHrc5Ow-wNj1N1~W;6@KUel|gMq58s94uFf>4i;Ok3vi?fy<@d(d?|POGMe$_svDp z|6HEiV?j(o7slKQNs@i?KJDyGqC)uY?UZ9Iba7FX;#icvE@OnoWNIKjFqu9%vy_|{ zSvdPTjZOP& zB3yQwvp#J9ASLJF%AJcM2(L4Szz9fWMV!A5oAxy{1YLkClGK@afuxEk9EDVdf?bz! z7*Sj_bso0Q`g(mKZB(uxQ2(w~sjYulkfe0S(d-|F*8z?B!~M@sN%>fK?G2LHS9BX$ z#8{z~d###O4CE}K-E8JRM$e=(4Vv_m2}zy^+jzQ%bD7z`PnX@-Yj+L%t$*9zb^4^p zQ%`$o8@WV77quF@836Gx6^Ey~8R zx2!PJSH)oM47w8Iq5_T`Z~5{|i}My&#py(cf>==%?vRF)c;nU+dk)p59!z|BGif2c zL#_K0U}#CH<%wmH*kQ4Z;M_sj0)&c(v}-3iCi}c#^ys-aHok(}oO6XJkIuuQP>LJ} zqR)s_Qg`mVOEuN$%D*7$>e)|=(kea^t1b!GA2692xhU_K{6hapWurDIPucm3=cs$JQ`+Mn)pVkBpDxM`-xXmxYG+B~x6E#Cz4- zC=ZRb_mb19Xs3F#DdZB#?LNn;IurTu;qSv`MT@SLCu~d{o4>=Rhbsy2IPZyELziAj zmltv#&bO4?%!OV{m#8vcON&wtoL32Nv5DWeF_+J=0;*i#nt1ENO0MZ^r4(|2xk}nm zm&d0@N_n?mxle+RTqP%qQg)i(VWMMVXLFi@Sap8%gu%t8Mt)P1Muqj+z4#0FjzJ_& zo>1#<6?WbR!e1BK4>8^{NC;Hr536%}<;cKgN)uk1BE7?5m;8y;b332!>?0vknQgZ=#(mbIyh`vrd-}o0bN}F?_I(32C(J z7gDo{+T?nHxW&O}6`9wWdO6p8>e|u%y18sCO~(Fs+$1PFHuZe1$kv>SirT7pl`Et_ zMn;j%8yK9J z*lyozn!0>rmK`5vW6IQ}?XR6FkC>@wUSfH|CaFSp!r*l%3!3zyITL)jN<6j?1gT$= z$}XM!{MldWNCk-y-PHJJdZ8bOS)Ojmuw{}h*~~Wy+;6u(Tz=KBfFJFf5}CT0rWR@~ z)#xKiK2s)kUgDtnmiO}I3Z+x2uUO8tv~t{l5@sUW6p=p!qXid{20D%PY?nQXWEbXT ziv$)z0oG^c9^L=);fEP80XtJDtwKCiwsLJTenau*j(Yd-xcm*5mdaJCkjdN6KY+4L z7T~o7!0c4bd;pi-VD>u`ASXv0I`QsD(%OM?Fq>@HoLMiwQ&b{ofQ8ox~Z~rNai4#qm66?8ZvjZ}XLk?nx$_PX-{%Gc zBUCGLh7Z>Y*zrT^qLJYv~}jl4*;_Vpw|G??%3?d?MWexKi1>_ zy|z8~I==u~v=91Ava=tBG{lwee>3A{0e;--vxlhPr%K+fcWmqgW}XLZH4kZ!Hj7{J z`+L@XA&nuM=lCNF#EiPP^q<6Sk?EfC3cU}Y^Y-u^4F(=N`Xp6N(KaSorPakM$PbKO6BkZFhi2wyf$1{r%xS z@I&3I&KBwFif_N;q=7+Ke~sV36OVnl{?Eby{M=@U9ab~I!hogH&u$yhIB_pmZouJ|07`N&DiF#Eg13MKY1&D8!dAmh3a;n z{`W2cG46-=L;$?%^xuFvc~Y`)GxQHVl^jgWhS{xW`^)%y==uNMC?Slv{QYn7K!)S1 z&u*!A|L5IjzQGi-GM;e)tlc-vHn*t-*rt^KJ74g6_v8P`mpFe<>VL!9?so6>mp}0z z^f#BeRawcd=ssW`KCljeoBVEq-0pq74=4kT|E@sDUBmXk5U^~(>g)$>M}x81Ie-c? zHU_-4zN~o%Ym{WU*|od;AMV~WDypSx7hMEJK|m0cj7kOp$w`t5*s#etD@lS)4iX!6 zt6L;UH$llt&Wa69ZWACNIZ0@coIzp}+f$A1@Sg8|zw@0j?!72?HsZn~(nC@FGj$lpc8X*c_JU^c^WI(MPEc&?k2PMMZ^W{^Qt zT>QjHRwg5pbhcYXF^zi=!+_01Z=3Lvntiz#2|VDk&kQH}3C`nu`L%od9hk=D>1XgK zsOh3S=gE%3a)n&qD#eFNPQ$fPM-OOtLtp{K_;j%P|(sfFE+Z1x}d7<#h&5%W^ zyB9fKgG9zDzQs29rlcVI#Wh_78YFY7GA$EEM!x=`>T>(!)tcxS^^{3%-jyoHrJil? zDMfpm4Q!c6ebdbD56B_8c@Wz^HZ9$TUGMpVi$rj&Zs)h)O$m2L`^&51x4Zl6hb%w$ zKNeL~@_4j{m2oS5nrw~1e%UC@>eb%#eqQP>db1XJU438UYkm7AzOy@aN?#(o#j_&C zWaFRT@Mn1G(!RXymhTd3t?@Y{+0>>rq3eMjsvDF(I8qx#sdu|REXImToij~tGcpLn zuhPSs@S2WH2=wxJLVu$|`h{2b?5oaV{0C0hC$U*@dqd6CwQ$c?v5m84Q)ZE++y=Gr z4ghZhQ<1a!WjLSa?p2?Zq7;jqmnYTMtFi_A`*Q_RiFGn-)qa+Pc}ClQ+;OJ0*&X^- z>ab?DQm4)MGwN>EPShsX%ai7R)2`^_U~T>u>;-=)j#Hc~`vFvfQ$eKkIN6Y%1YPL)$QGoKe|!nQyPrMCz_wKDuu@d!Sr5p$*a6F(DmiWv$RU z_}-xG{+825v8myyE|(_~BfyC225ic_Mb(?y( zDy-1h(4BC22#9%=u^k>O^rfyLfLS9haC_Ro?|bb*MR$8r{I0U^w`{nvL2X6#kmXk= z_*K5(6ONxF_7)7ohC`;}c&5=nqg#dZ!>FHqs-h#{!FB;}c@tfCj(oNiDYX%jvAXW~=??4jkuB!}nOvcK8!b2Q0wZf- zc@aM$wUymh{bjld=k#30Ug%|%4~*@7s>@Dmw`nVJD%riW>R^p2TVw0`w7mhNC*$`^ znBY&Dn7Kobn!|UWwi`qrzErh-sSk||Uek4&#qe;rSMLjk;I-m6d^UXegf?u}b=9nS zGlMt!wR(W2zYQ7kUSF@V=r40K{nGZ*RwUVng=fY6)d2akN0nc7htjmH-;gr0sKBm9 z6nmDmG+sY{YTM}B%0%Ay)F{UA_dMSCa;w)qI#gjcPANU8`G<9dDE~VD(qIvoVSYfD zWva$lP{W{rngaEW#us4*w#zr*B`aOeultd4zd<-&u!6UJWPD+E9I)( zZiU%1>D)5}4d>UdJB1CBz;Z(YH!u%}C)C`0c|%w=q1-vaQfN zKlp@?po<5EX>x3jSKRGwN6iM^Z*>am8^KR)Zqwe`b)VkWU5-J|(P2hM%w5SBifz28X8)pdofBrxmj3kpW@=nL)F&KERi@AvT>V-Ni8 zwr_!D(HkAJ zz;iUoyB9v9(xFyEZW=Jw&JypNx6><$8NgK%BXjif9R&Um71v??^$(ZY3K0^9s?9$EK zfwDHrjniT63xZc#${km8T?3|!E-g5Zw+VajE~*I#AgI!=Y|qS0`3hi~TURYp`(~!_ zal1zT&e;f;LS3&b5}{?|C2$_|ctl9}T!Id-D`9-l`S77p?x>C3t{2C&9nYP* znv)DO@~nnqM@Pk`6N=Y?^2jh>OOm?Kjw?tM_hr{`;nPj(*D}kNX=8k)-L5+n+@dLx z(i@s2?{Y_^1npexyAv5QkEClUAm60_8=B zpvl(TLb!DW4DI&XtOyvPwo_hfHr6;QZhd*IeSh49i5q60 z(9;^tb543g-hz(J3nw)e&sF`nTK$5$-H3sWnG9!O#mf+d;jx&v@p1O73I$?539*T_ z85y(ZSpAxLQ|_b`Tk{~iZTeGI5k8PZNb@gxW<- zdbE3|voiTy6yED3*L3TLpdzb$@vky@OI!KcgoLua`!oCmzR8?>rp@?i)$PtQeEW+E z7se$`FF~8Vw=1;3{K61QJ(Apg{da1lm(>V*G}@2n>##R1yuXqh6HKQ3X zl-^7`_4x7}(z0sMB2`RUt!g{8Q zW-R0Ut0~P%%Nnor_%E}*S~;~2^?Bl}TmuSef(O-90Tg3dc_yQ15L_c(N1VW5&&R!y zmlB8^{D9#p4UjS^%ffq4?_64$m^@IQc;#`uI;+1&?2(HIWio%rsew(XND6J*p1Sq;(H4;{aiG`$rp{{)dF|%xcC9?!WL@t zMfvrI+^?3BfSsHZ9BOho;T+PaBITtX;XU@TRJ# zdHBR#v$aPo0=DMX%^!-T{I+-Jdvow*5joSE5+V7rl@+Ldt3bQnJBc7ex~DOc&rmOf z_0P1}*npKdC0X^#OKtVe^Lz=Iu>51wo(nSs<1ii}gxOw-ZKh`$d~G~=YW*ealy^RN zrj9VkB(L-gkhBKRiw5Ohap=o#Yh^ZHztA5r<7F+#UwEg!idk5?`)r(h(zMP%1X9Y~ zzVH0Qar=DK={jxMPwwVXA8;+gQ9b#*HzI9xn(Cd(@8VxD%}T0b_+{MPt(5lE2a-y^ z)H8}0Q{jBJvRCDjq(JrVzGz^3`#L_q8>tu6zMxYU@pL+}n0b(&C$xL-Yk6O1ucKjb z5KlBkHTN)2-TQ~PqGhaKX5G9HD~+7F%)QGsC{`Asv-hC{k5y<;y}E3p*;@`gxWte) z-sE}{6ta&uR|6v$)3<6cF9ZwL8dKjr>?@9q@namv{BEI@-$d3dVSVm_Rq8}sWBTpr z^F0=>v=FIY_hV-kI~KEN;@sLfsi-0GY5%BcC1+esy&2d~`WM)w3nJm|>p?kni^72~ z*Y4!+?``2zs^sGN+DkKid$8B+*5}oyT(<{g-nyQ)gj$^(Htr8!(uK=ByZvb7Np0h; zD~0Y#onOkzo-n)mqvt;eVBO?+0$N2Jk1-3t__L}4g=>u%lJF=mTEFgOl;&mqnYZ3g zqhQS##z5;v{e*~hmqOkuW4D)uR5@qo{TM;uigR$1TEV5+ZQF6Qwz4wqa#_RWf!LX0CaU%M^;6w^XY04&2Wese(Hz z82BaIa?0?Pxu$lP>rSlm&K8(%V{(0^+FGix5t)X`*0P-p7BTT>J9<`Geb%-FbNmqY|ER zXg55f#vpQeO)I|CX;Ls?y6~jMTb=oR5R)%u!JF8V~s>Knka2^X457hPt2JLSHHeS0|jb|w%t5**yWo$y#`1PgGSgjalI+}2%|Q03Wfv#LIMwKJopy-}Mk zH~T+7@?m+DX5F~jdiWs$L$w`K{*3jM{c%svm;MvDsy?#<4CZ3Yn~&8C(s&s^D+Vb8 zFW;r?qPjC$c9NO{=)%w-OAtpwcw;VMNV>+;nCNP! z*Ud#r9O~cxfJ|r;x7ii%UHvdkubt(nLueK)Z`E$D6E^q()oQ)a)V57`GjNOVi8H+@ z6CXF`?gDnVJzj}?A0`>ydazOpIpv0jlL6^k{W%F#p_*Lv*zUCWu_4T5z-u8Lz#)Fn94R(T3kX3~#I@>*1#JXf_ZnD-8BUGrZSkIw3f)mLM z(<&EULhl5YBvk5SBtrdM;dsUHmJjLS-rjG z_TS*$Z5h7BFEhJ@8TAF6{f6t0n?nx>8NgdEyBerVt`;=kn*N-q7btUTu2VH%eW387 z1apq`L-%Eyz~3Ud)*Yk!!q2>0tE;!f)^Hc?I&xeexMbm51sqfXSV>?*uMV}Q(L%We zJ-pBcrcmm)0dpnD4r$@KrE{5s6}0m<-Ukbq^JD5N&bfXcYZ6|@y}fd8>*ST9`L$HQ zPQ!aP@mWPqsd2Z3 z03|Xq9ybK*HE{2Ow}{5KoPLZ>-P9D1ix3-hqbnSt3ugba&7wJ?X|Qhc$y+D!Cb!}B z8KW=1&679#lxHE?+qfStxQiTAs}9U2AhDNTuWiHm*PBJ}6f1DQLPj|kc%fSh)%fYHbpKJuT=AqxLu~j@fM9x?j&P4T8n}2Sdc@raMJ-^q`Omdkn zw-VL@-*`pu&TGAr+%pnmv}M|t*T6jz=;5MeIhJ6mQe2FEv6(RProIf04NhKaKnRaF z->I{5O+2?zpmFzHYxRAzcl8MZMqgX(;?3e-lfp<}HF?}|FlS)qGfiy1vSHO*Xs#TT zYGYQ{8}FRQ^JSWMmVB1O)VZNj>9*5WOpUz!!I`Y7m7AQ&tvl~`j>>DoKVUJxw1apzUtj)KNg{`$U-Bj=lz;tmHbNz+7$MjmbL z5i6GtS03{(c6}UYV@rMyEI&uZ?;qFvG~W90@3dwm4n4M?bTASjwFNsadESRl zdz_W56eoE;m9%g-i*r$~CnLJIseG)Jl&26Mc&Q>TKhG9sWBOEEq0RMM)~twG`CaL{ zOk=}IxN~B8ka<(AdBeP8nDGjC%2;hqN|Dd&yJ7Q=aq5ZR(PC~z(NX?Uvr<#^mgI?Y z&u(Pg_@k(+-MIB6?QXvxvBlzvGGNEBXf1@=Y(L0z1cDc@zV!(*}D z-COC#b%U0(GcH0Q#( zMBI1;v|FRPi~Rx*@@A6tou|`!tpy(K^)@!}?W>&84!%+osLD0;M^W4^+!!ZUO3b>oL(3*6xX3KqLDG4bhwM8rv5=J7eMli6e~Y#YMN~RpEvq z$?B8^F)+`JegNNgWcev;Nh>yTOVzKy}dGDT{?(ADJ6C($T($we-Wv@i!_v; zwlf|%MN#1>c1)0k?NV^ibnEhMMt8M8vYEEG4boTr7CCG&h4=f#pL|K)rh^9)T2Bx4 zAFO3-4f45Z;RguM)f_6!-AsR2daX-_chd{GV%1= z$vJUnX0*AJX=Faahb<^DHLFtah!G#m+igWH)@rLFMy4jzy#m&@n((ou}zIGUp01=qOokwSsB*-ri-hcZqvQ} z!ET~|YVd>g`0slE7R z;?+bi3XS~TUZ-9Esk=c9N*9@%bvRZoUlw1_?vRyYL)grgjvuSR3c-{h{VnjBiW=ckiOjr$o<>cGd^pzu8 zyVs=Q$8IW109HE_G9u|PS=?IyKMcyh4$O=J=dXSkzm@?qD`^a^$4RFUk zM*ZZ;CX(1c;)iZC0orYA0NxexjRYVj6ptKJUTywKTwioN3=JKJ(l@^9K}-$=z?mj@ zsQSUxKT?NGHGm24?`95xtkFfE9lhh(LJ+O@qPp^kJFoC%^x|v&MqcLz&8+=1C4!6~hudnkc&9D0#p800OxHZekhrRx1 zVe%Dgg2Pw2X;W5dPG7#WQOP!&(#7e;L+i)mkdR4 zCHE4sp6L{8y{m7`6|K?R+hvr|QzEk>^941LV$W_|VUh0ha0^M+F4f9f#_@GgpoZ|f zg;|GphOo&iT7OFcy7UW_gIvgi{zgv$e}bBE03})3OS_1+$asS#qcaaLeQngyj!zh< zt6hpeyo1C#B&sZpsX0!S1_#;N8n{A#AAzOc3Ycw~)+<`23QWuLaQAXefJc?^Pn0{xW%&6wk=NiXs`SXK z>ID&0^dM^wowYMd@rLoCG#tCQ6Oo$DBF)0|b7-34E%)UukD8U%zqyZ) zFY#H9GZu;obEe%9n%Z=mLECi_yL;_etx4hZyQlQ-PD2q4?*1an;n2uR!}BdCo@f|v zbn7daD0e*48fn4P<;-rmfE!C*IcUyRB-VN9*ztqo%!4vl$V@O%5e)q8K~w- zRC7U1kWzA>-@%}?mU|7s7I_16!=5MhmU?V^uZsDTIf$9>I|GUB$AZrJgnT}liZzFu zk?aqm527EBhD%Q0s#ek%Q{E#32TVRjYXHi;NAl5`K#P_x2eR{%3_s30m?Ncf;?VR~ znmFS1D}>|$V_^7-Jdq^%HS?4o;ePl<6AmBn`%lXepns%7v7JQV6sl9v8n)oPhtMNd z4X)LupWJ?5#Cr9BmJ-_!IOWHgN>q4nd2qsHNjgpAPaHq9Q|kc1j)^po9Rbc!TyCE7 z1!{HCiNkwcY4Qgo3(lB)h~h#0hQcissoI&F^+xG^I!yT@G+dxn5Q&b-onL7r%2yyb!MBDod&c@c18t9gEOo(cuL-J3^t z1`ICqBU-L(NMt-Zb0dS?1fl^?jKDqW`Mkf5n1id(7m}ob*F*;Cf_^hV$CQn9LvD@J^9f^u|5F8fI) zUayQ{#7FBeECz!LM0iYi#l1~5Um6G8OI3Sm7MF-pn4(H0zF@=#l5u!-z%hGx%cpQ@05=bq#Gf13>~)mbZKR6<2`u^Se?%G+Cj+b$cp6iR^b&pLb8LrTL839}-~>bjh_4b_ zKcMbZn{fI1|9^M*L?VX|;D>0}gc7619zqn%oM54ZWIuz7t!rR&VDAhHv6K&~ok>j_ z`AIC9l&`5!fq%|1ajF6``$=Hd!*ma+0kT<>vY`sps7u^#92XA*D;K$Tj2vPFG0P5V zdd2R5sWwTREYvYatN9`a7~Z4Rye*OiIu3Rl&xe%aAQ7=0;C;}kMg6%oP!~r~LbA9W zFjpv8v1G(0+^bso*9vYQQw&i@hH-(ld1nYP9Io(_QdtEG5PteIIbtfLlUqbI;wIvZ zBjssI8C&7oK!)>728^qaG-@3AF*M>zSmJ3=roT5qYY~u1BW-6VDfmaNkpkcti|64u z$yU?GmHhgf?lg{X-C()H!kWQl*Lt*xT!%z@?7l=YBe)q_e;&1+TeB`b$m)mI*55YP~uwVLJ#8~OiCV_-q8!94A&q7CV_yZF?j}3`+h8aN-nPm5X zeZ%OZzbhT2Vu!&|X5z4`K>Dbo!6yM-0K${#EI7kyQ^ky;Wv86%J0bf6*d`e$97d@y z5gZ9GH*$uVbaBVXQLmTUDtEm&w<%kK+#TigAneVWA5i)rcD1kc2gKD$vQ35EJxnGX zTKs_46(K3~V(u~RrLX57>=)WRCgA2s#{Rk^prfm@8Ptoqp?yP^etu=zsVcU{;%=^0sfob{Qc{Z>|~}opkmpkEga&kE}v6g z9woL0QaPgkc)mTs7L=!AO|S*_4>n1Z!YDPv=1VPLyO~h#niQoI`$Jri!u}Xgm zOSU58VXtzAk?9K1XDE}MVp`=y#AR7_e#n?RpA@!A8levq`%*G^6N`O; z%=OqKku&@d`@oGjFouLIOK3%V2tmpKC0B)N+pozK%;}>BNrkJwd4sa_qn?p4oE9)_ zJ;W1a2*md3g{wDOxBhyx@Bm-pBC)5P;5P_km1Ya7eiLF=!Nb)%MUyf`@tiwjeZ;O( zS5J8#{(z?c9InWtu>*zOd61jm7AWktP%ho?1%=&|7kv`PS$D>OIRuYO_InRpre7mO z^-m#r{vjk7J23ppKZpOs#JvDm>-Wi>vHd4NjvGW7mx<#_d>dp(Lq~j3KcJyspNRKi zhcZq~-@B&Kx;_i;iy_|Ezt|)?@N0a?EKB6E`hUJ04xDEosH^V+b@c^Mz~BJ!9Zsc# zF}(&Seg15kxFWMge*`ZoR(OCNy!)deCLJ#QOGFa*K-$q;QUwq4uJ9BEJM5350OKC8 zL5&f@{KEF%4j)W)QW$Uh;rc+?wFW4=&V!^EV_P?W2@4IEXdFrR@(1J^&XsnIQVvm{ z4r0t>w%d86>KHwF?BM}|AQ;YA{LjgFY+rt|5M0zmY>?zE&QtEMengyL$e$*Fl6+P$ zW595n{A&sxJ^bt<>gRi(18)bH=jL2i2k;zfZY80GhUr?Tn>h!^qi+d~U%xknlz5{==yQJV5n4l77g9IS(8tH6 zjg$!DY{gO7gjkdQy@mL4Z>!qWz_>Bw^i)sVkV}noMNOi3j9BP|wK`RPRHb>b9RPhy zIOoGDb#6)*ZQkKaGf#FY60Y|iT~ttZ2gWO0IXd8eP&htQemzNhRN8mdWrKgCMX>Dy zT7WvZg_9B=m~2`WCz4vqEc0cx*=;>%1ewa*+u!B4m}=4z=yZAz_s-JSfc`hOv-(~f zTYgLw_9*70%Od3Z6zd5KQaT)c3JsuM)K%KswC}2pLTPAE^-N8z8fTc*6WNMPK-TE3 z&F2x2BnVrY$m)H`EE#X^;lhG+-S)C+EM|H^#&GkRI*vn{oRpb~(VgNVIwg>MvU+a2 zCnuHDcS~n};rG;imz<>Bnbi169v8JiAApASrK_m%pMkxMJ zOgKK-KlmD*u|+}d%}>gHD!E2B`*~GjtFcdAqqTVSSnx|nZkoXi>f`r9`j`>G#x)Z1 ziF!aZFROp*4@q&a{V$Ct>L-~cnAAjV0%q^T%1!+qN`HM++V$ zbuXkJ3|D$1p%e%L`~Og5VW!lae5+|cAflt*8tEMbcDR6LdwX?Ng-nEG3dH%A>t{E> z_Ae1H(7gY2?CVcv?^KAgel{OmL|SU2YioN8tdE4q(rj(-`6}cEiBA96Eep;)W)Dn5IBZ9YB(c$rPx`6P?;lR{|KD!OE$7 zmbS`krVsoS5e70(sqa0!wQi*O9mWOs?!xYwJPn@urtsvV#?7gh^dcXzvN>@T2B_)< z6h@?)kGuP+=3@MYdA(cC5Z9?IC*iiIUlBL3>Z*Y$IL8|2JtJqfris%2Jt1k z4EY`8*{pek))iA|*JT`kw!=`_ELI$!qzTtiv@_RsyX%Az*bdxHlDq-&_>Yx<0Yp*HjH31#34IynR34$671xdqss;S+KHXLX2)XJ6Fltw5jGv zMc3p1*@*rJJFEA7Vx~cU3(O1+muK4LqnsWQaV6^Cr%_y9>d1Jb*B_P14aFV+Ru#ko1W1 zfy+oRX}KAzfTqVoQTN&yAjr|Iblz1yf`tbeLo0jK0Ep~c(d1$fK%G2k`8F~r;CK+{ zQoVz%($O8UA$$nuF(xVzWQ+%#J{mJ@O07{4Q`2k`*_fJ$Uk9;hRo>AhxPF6cCA=72zZ2rnW`Jw^$vNSih6kya-P^3>27@+0cUPAgf&^NNFT)q{6Xa#`ZjoADEq!qtXT?g^XV-#t@ z`#}IC{)y^FG75jDr(f3JsO{HEE#&-%De5+0Khg+Z> zv!iy3Xnp*y118DSQiz7inAQ&U`EF^L2uyK

5o}K~le6j}}>pvh$$iHU+ zq5B=!@q6orDEzq|AUAmd4cOWULJXWXv&3sjA9N@l)=`v0-U&B`XD5p|tO}$l#k)wyVcfQW7lD#s1gME(FElT^!&N8W$SYqq58|P7C-r%a{LHFGQ+(NL(R3{oomiAxm$(J(o7!?%8D3%7vXWSFfqTH`blFd<303dNDDH00IN?TXKhj!Q5PR?RC-yKI3XjKN`Z>N{jJ?J%f9(l_vSd`{KH=~Hmd^oxxVD&(IE zTI|Baxg!hnxeLi6A(b?i*QDeQE=r%KzqvRhm})qgQ+dSW1~HJTzg;DXr)&mQ?A*4# zmSjG;5KsRVJe@j=d*z0ih(i;6H{;asa}#QvJ}wd?3!7U;$H?&giV%3G{TY9yhbjME z&iFrnNy))Ru_Vwghw32&6U`27N^N>2A?*!dNXTM~4h`jpZmP?zF3*L6nG_lvb2Jsb zX^{u%m7{5ohr)T!e_JQcCWtIL7x+Px&~*y3BL(G`U0j@BByuQF_@<=SD=Jr}0SW~Q zzVvC8<1{OEk8=T;0h2=Ywo=+C;7`a{i9z8za`8m|gqwF~!EdK%yLZSr^=g(I0B81#)7bt4bcjwjS^e6lK$Dc8U@+UFCs>ydpg#dhYu@& zs74%tKShPeP#yD204)%j+L#|+1kGOd00n#$%d4Dqkf!SA*#XSYW}}1mf;odACQh44 zqOcdMsHd>X?{IZ7b`Q;jc)ZEB=Lb}(aw6lK53#?Hp4Y&@(dBy>caB%sY)>hpQ=zSXazS$Rg@Gi+9 zlC~mT9`UII)D%xuL(1dFx|@Q>4wx5g4-!=<4$fDbfw0|w`pjrKUjL})uqb;hjnVK6 zp}Q9YH<&g`FQ0lSE~j_**f8haDbmUv^YMo08)~(qR!ba4`+kH$rql0J!}=cIR5|~e z`!ub-$~mr`z)UraowZ2AiOOC-x^fB<({on*OPTk8uRQks{ybt6Y=|Eyg6KVR4pJiT zj-gn_?nw03cq>n@_J`B%sQO|p{}hpVsOf>^q9yB#3Aa8ZKaZdCkj0aam+lyS!#(p< z`F5YyONCuMyo2|J^V0B(;tcLI7%vYuQU|8k2D#?RvTXxpTtJSxB{;wI9S)bP~}>k=U5g=X_uMNud6C{N*4xfu3M2vo|T0??lDU~ zH#y63o%uyLOG2AOP&g%&9}r6YIN>%t%{QOYvq}7h=hO8Ek!iD(3b7i-G-WeotF0%s z54$4MTUt$?r9Ek0s`B^&d1zj?4arJuU)6dmqqipf==qGm=(Y2)7Y7;6ov8|l$~HE7 zB*6TZMS?yfoMc2bUmLIw+?h&?(O~+ZyJCEPwW0#2U|d`{7guk9`aNt{tR%CwC(ryt%6^aZB|bA2*qz!7Adt zOnoa{w5H4yeqj^q7A7o=30{Q94|hptWzYnDY_JH+6@2gBz4ENOu>$<+tuE$+gBwk`Lst{4m-b*EwN1rmYTZjpsvHM= z`)9BHcI)329sO5AAO@0aAHxqHUiSKuik(NtkrchJou1v{#MFjuk_Z3`X)^{)3KP-3 zErrez&3*Kdd5f5mK^_2*0{?BLO%gHcUp5G0Wj~rt7a!c~_-PM;HMaSA!};N?*_|ej z?K42bfyp^C67M@~K4Jz&!%wkoEd_@HqbG39Jtlop5&IRmkn#5>(~hhK6Pw@WGH^~w z?!BKqn!;qSM}OM9^A*%nxytYA>K+rP^5jojM54O25*!TnX++zf`x^MWaWW|*fNj+u zj{?sI&pa}d!0PX9?*(^4z|?ILd%^e)o|!%wgV}dmRiS?^dt|nWE6qdQ%4R~ zfS+GccB28ayCGn9p9SH(2Ixkc45y$U0i1%L1XYCEG1aLWYw5OoU?+?Isx0XH>xbnY zgrD;2TM+x0BV~00%UlDbsw8kXWa zK;SdSmxKjf?-r7u`sCm?2nv49I4PrDvy!X3nof{WA#o{FGMYm~?jw5SAu8?I^<3N4 z^lLw$-{wf=hO9FM(?gXbLFf$u?qbeq!s+fWfv|8jWa9EV%xL~_gD4Y801!MK4>TWi zBBJMJNPZ8oMl!0CTu-t_hfxU_D`pU;eWOAzq}|byil`?FOOn(4w}8;`WWH&g2Rj|u zLy3$NgV`IV)4wetCYADuLu7F$CSCzF@oS8lh%ErVd5H{?B#)I`{e#;BgsM*R0AW#Z zy0fv7ATdXjRZIK9`mbRj`G?roU0y5wDK=!1%=y=NaXrb#@iHpy5F`#i{ebMkxv8af zTT-#0>!7no!=-v~dyMd+lcX9x<|ofNsWi6P9?lh?jpJ-d51E`*1_2&^QgxTgqfUWv zWd@B_Q;zMDLTXEfok%q72V~6&f3Vw00snV8c3k5R=>ZTIV!4wZ zFHbwE1WKUV!!NJ2rkV`jB|D_wV6sC@7snKoC|f`i)7?@hmP#ZUFycJ8Pb;D!pF`pz zX2xfoe8s6^l+G{Zar+FR4oXx;}1PQ=b8O1sNfQ54P2j^QA6jNx2@kgQ+dDCemvQ zdeKXV4g5wh=ibCszLWLZBW%NlZ!)uY1fC8zDQ4aOWYx@FF>qT)Fk|RSaD9h8ga3OA zgXffvPL5|}qaGT?_a>|k_UQ*zq~xkLxMkT^$?3#=$h(v$P`z^>$tcD^Y-CTRchY7xm4;RrjL^NKh(NjmA2$#;xASlF3dtn zxTCdo&1$1}xQCqG=BCD%IVPB1m8G5GBHK<5sYQ~XsP{S%z@Dohp39*=j5t@GkSdpX zmF{UZu0TgS+tIhG)O%_r3%M`(svXoM5ZOabr;pLSVK6t2Hn}-kgDB_OsI~- zNa(~k#rUyR7^<>G#0b}Vp;ceqoBU)ywZS#YG%+q>X>Ex&7C@JyQ(gV!$^)kJU0}ji zoK@c&#p7Ud0z#u3+5BT!c3D-@T}`bp9|4@E;qUUH-u;Va6&21IceBjD#4tqB>F*O>BJ`(^-Ca!D7jNUqLlt_;%K-JzLB51ved7h(&NOewlJ}YBX_Q z%-b5CU4@o7xG+8_$*j6=g5&O4Lc$*^1y_C{1S>W2Onua-wog5{HB7<2#hsj&`MtU< z?rdv(gLQWIvMYMkK8dn3+ix|mMPk#YW3X}2k26$F_#yye(zxI@9tDH62%$770eD}&9hV+;3%%nwZ%mX@ZMxmYv-Ky%b2 zpS!zyTBF);PJquuJ49(v^N>f?do)7JD9O+&ieK5y zse1- ziJ`uc)L?%XZFLz}LbHOF%dOl8{PQcUn9fJMiVQ*y4UEpzgarU?J;-Q;~Fez3u z5uVS{<9~2x4RzfQ`XC-t9p~YwljXs$j8kj$&KBx+T}XDlvN>O6gqxMq2o$J?8`R`8 zbEkxEF{?{Fjogi6TT1>tm$52iM|){z!$X+m?!~w9w;uVwO;Ed?=D%)y(F|_q^yun6 zE{^LkuB&g+fyJ*ztdd<|1#||cb(zZ^tIC1vK8E!X2Zixki7y&g*h=FCj6&lqjpQ6# zSxY0%0RC0W=JghLz5<%Gcv4GY;pyR4ZPI%We^x60hcFof$&2xx^JUrvSx!1d8V41q zijN0#Wade>Qej5zO$3RBbg;NS%=x@)h+QMDlVp-3A^8LQvmgNo7E0zE!h#=XV<=)V z71HqT-qnr&?T*K!(&NJjD2}*tBgSqM7eHWrLwiA%vxB%GkgF$?P3xqODId6l<@pWX zFrOd+vXoYHq_RoY-M0)^VHT|h0ueXg1*X)p;#d7G2-9>7X+k3fD6L5EYcOP3zC5d0Pv(o9{IQ1 zA|&|_SEuG5i?!N67Hg89SpBPnk3X^cfolf<<8=s|keKWha6VuuUAI5{d<9>#j`F== z5JZfJHGwaFav}bN>JNSRCsaQoke}2lWpf#16!^GeY>1WAC*|0}>Kj4z51;eo&aaGu zjyz}=wC)cQ3eCjU=wRzy`9#vpQ0gYK%ri4wrd0#aN?4wIP_vXA1{10{Kx$*UTpe8rJl!Em`hJ$fT4d6b zOjVVSNXNY@bCVWp+{bI&y<*c|JuMvCD~9*CZ|^S=W)u``b9WIROKbZhj4IUSDk>5Of0WNu$=T>W7q~Q3HcR%r)m2ZzNsau`3=>exJqtw6Nz2c{=w`Bd`7kCi zyR%=2x^WD5pa0U;yi1=Tt<#xlFv#0ASfn_wR2ql09)@#cJg%DtbODS(&E@)L?%`1S zn&VYZcr3l=c>QjhwL(jO7%3FYdKrn_dwxp5kNo~e$@rB4E!!4+`Mym%ZfW@~N6Vq8 z+wRczR+&34R_X#>tDt43me3`y#_Si*9~v7oJ2}Zh&`ItTlIDE4nj9=Ep%~+kn9QQ1 ztuDAchs4!?z8Eh;=lCSy#+2UYmTN*nrwWBm-OoC$3KF7%LtgIU+LB8>T{#%-fQ#Y= zn))scSv@-Qxdkr8J#)hN zl3+(93Ob-f=QN z;>;FWWFYj8Q*FA%rpjFP3=8@_88%p0QEuXr zw_yPrd_U~SogB7Mi3-;O2OfOZGts2Y3`;EA_CbcOU%PcO<%Yl{R zogj~PbKGWSw3>9&AmiL zWtln6059DRzrZ`}=D>+Q@#c{tLoCcXuh7P-wq&8Lc|9xp`&cs06>m`OqIk0I)y$@T z-DZG^RonW3$uznoKE=WX-4jSVS>tAzaDR=3>6!Hz!6&@$%2O{~YG%09^1C!X_KzFk zAEjv)l01flmH>u*_OwB{)c-2(Jj0sW)_os91r}r?c;d_TmP!<%oKyG6GrycWk>^^V`3s=_m_2*h} z)`*WkF%`4TNoUe(>fW^|VQUie{Z)fl?=N28@apVoPEEJa-PP(tyDfiT6?{aL8?AQL zFkEA{v>CP3=F*|W?8}9P6&ph?|qfG^soFPg1b1snQ;E=0x1t zl?ov4P07YgyxdORtg5zXSa^r`w@Mz>)?QWU)9KKc5B_%1KeDu5MJ!mgY&yJm)GS6B z26YT?oi22ED5pni4|7Y>yqS@xkzdBG$83z7T_*UN5+=w%@}{t%T&$@5CZ0>Ft<+>B zu&4-knAn*qx~o~Q=p}gW{@Qw^v*S_cs$2abj?FjAZSK6(PV+Aaqmad<*Dczkq?ylp z9y>lnwp7jCd61|zOUlhV(ifjBkbicLWqNANdfSrd>RnNJzv7!i_M8Y*%In&|Q=vQ; zM2iTj2KS)BY?}x$qF&HtD++vGKrZoG9OL#I(s7S2*+)>uA|KsWOKoi^N&&~p4 zSrXs6{Y6kXtoJO*M^7HEBdf1KvQEdR!BMvTbNqu1UWM?N1_F*N0yQU|-Yz^Y0zE5o z$oR!8!S#$MU3X=Dg~AmwMNR6XE8#ln-h*101k*lZn^jBs#c}(yvO;_`;cqvYkuS+V!bT$1g~dctDWv+FZS{2S8u#XDG7=M-`= zbA?h@vP{3xGY|?YgD0TxJFQw57rtBKS(4DRx_P85tc!$dmV}yXz4J0Te=4$N)U;3B zRc3$sIkTLJxVoIW^Ra&HJ(l=S`wQV1OWJ@INGW=Dp41>J6YGj$jtSeE$ZQS+_!y}tFzp17y3R+z+8&HJ$qCm3FR ze9YVFaJ@=4;d+;hX2uLp6)M8UB+3_)zgHnc$&xR_`N@2*Ea?^UFO?TTX||9B<^a!O zWt-WJWz4dy+D81%2bN0C_H&ndj(vRLh&E!B+V+GVZ@q;9givv1GhJI#eBvG&2Q{%s z{;-PL(6%wDtJ4cJNBf}`5LRG|qs(T>9Ga?UjFAdItuak&de5R{yR6%1Km8!zoW0kt zO=q1l>>?xZF+UVy@mjF9Y+i^#Ip1FkrR%z@tfWI;V8Ns!MnIqF<>bRXJ0`s-n^i}X z&n#}33v<~S{xP{o7yFJ|9WWAWqPh0E*#(u9Hr~|sdFh=aK}Pk_ z`AQNq#t$vb>z?u(HF^h>I;xUkz)$4*8NZ1hj|7Q=HBtn2xNavATkR** z3e#D=c%O#D^vvP6%x($S3+U$V%`xVi1U9EWenoS)(c`!&2G`UsR-p7b%@!qm(<{Z;mW0pT5*{@hItLS}wLJMJj1m-M?yB zy6NWObByPnIYCnSSCv{|>j9;9Ud?b(xQKJc9G73C6dMQBCy?2|hEqS?MNxQ_OUq)o zVB5tga%Io1U}WbVvAqUr^M*0!-1|!c?zcX@)|Fi^XV$T~E$wI%iybU&v8h4Bge2j@ znk@>IY$94${(>By<7)R18!YRKH)^)XwTR4W+-sWlsIH!~z=dg)5y!;3ORcmWAG~>W zKXtRe#4uoU>lq2>(A z?-K5+x$0cjESQwfoklm#mqtMCUKPu8snokD7_coAGo)O3n*3<5vU>bzVz0*QBs!*y zVeum zKYM!YQyeB+(|w+7Ax`^wH|44E+RGA0Et-y-?@%7F!U>6s!|%uRMrGlOoes0{JY{LKD3SMKXHRFq^1yEEY)S> zR`uWghg1fP*t>sFWq%wWpFe(3ET{t06j8~W`T03`n(I+PrOlDoyTDoiys;zz3B4J=kwRnKP}36)86KciI``3hpnwTOuETmRa{N+nZaU z)~x&8)IrwF)sw{KV1qSKE|EqNdAA$6@ExQs%#fFP`}dq7=G&?Ug_U6K51c}W$G(7c zug+u>l4;z)z!Dr~GpVQnDn^_k5}&3ho7yI^_<(-~ zqp_^L!jm~UbM?r|--Ay*1lj0lhz4>}WNLp$RlR9qlSUM8%Vg)amJLxjI(3j4IstmC zGnw3|RXE{kl+Jg%u?tJol&)3WyVV8iVmzm|wl~!_A(#AlfQ~w-t+B?h*o9KK`oDln z)lz%q_cBhjY5W2w;!}Y9UNLPAy~gD4gKtdArStyI-EYzeBMs<5Muroj7(isYwg3wL zj;yBTd=FG@1gV_=W+Q1n{REZaA7hrDC`E$W0`1kS+-9RmPc)6tMj>>?Dh9NKrGf=h zs7);tIC6<;#&FY6KSALG=e1~lR+^jf@)?|&e1oF zu?Wf3zzey?qjwx$|^Flt6hJt@cVzONIQSC?}qw=OYz$vB=a)l3S#|A ztoV`=Ew6OPjp^%33m(oT!JI#u-!1PatLh!JslS=s-fM0I*{IbD&%wFb_`)E~9%4md z_p)>Cx`(_`^Mf~M?cO$vX06z$%uQabE^>*N)hLpvZs*fQE){n&w;w5VxRb~;ckroG z?zs3`wAzqxPOKmF%17Eu1?8LpV|BH#h=qxBxZK4;5nJ1NS(F?4gx$!y_wlJEN&C~f z6NaU%i2S49me%Dv2F}DAT9=!aa#)S(q%WcjzBFj~?#E4AabJIgoUO6Bd~gIk)UonI zyvq9aW&X&ULJs3^ZkoP^kgt&NPtnnjROhyQSe@?YY-J+6`!g>q4xW~O)Q2C*(AtZC zYV5t?lxho~IFCD}O=r6>)FW9TezykONN^^l8D=_9Wjt72Cg<=?=pu?se3L|WzmB&u z8}^{*iW%BV-?KZ*?F43t-*qW{rF?CD42=*HVt;RX4m08(aJ zqts`OC5f=i2^<=#WAy8z>G-{Zp)xN*g%_7|(4-G(?_GY(p2nH|I^y)T2PCWG2{&86 z=l4q@Ft=}n`yWl{GM0sBNMrWr6qA9fxu>Zj^Jt zn56e(lnSGCB~lY(#^YEVZ@fMfTM+-`X+lNzL19l?d{*@{SSd?dl)cDc^k$+`D<*g6 z$C_!u%MuSYGd4D3dC}T>rM~$jR{ygzJ}wqUQ3L6DoDn*hc{4(HwM~i}-xKZOe$ABe z9fp&o+w&rB53Iy`%I75B-HZAu+Oi$z@R3IrrTfBDn?l3*Oiie4D zeU*cD;&pEnbmMxKEHW=@c7_MIjz*aA^f8@Fy1q)S*79WHTw+T_T-n7u_&KA+(2hQb z<^-L`W%sMs9kI`kzp}@lVOK1Z@pa>BlUm`RYzTE>iffj%qD*}g(ZxsCMHE$92rcjR zJ&+0cBE`FFSU+Fm$oM&UK8& z+RFM1qABmeeItm+?Puc1kKmahCWD>X%uSltZGb+AzNy7{NBg@=^yricy3!mAz_A33ogl&IbA=;`=_nhfQYdgpwcO2j7 zDi}`O`}X|Zc@)EJjs|5(j^o^d~@#pF>l%D z<9>wkN@cjDUmw)U0DiWz1YX*Mlp)Fy(vxBZd2U`$;E9NW*X)WWaz$eY-(lwPjgN|O z1*EWVzF1`;G{-b83UIW1B2ME~@}-Z*GZ0=PHUyuYkM3BJo}Inx)V|zb6D@{d@-{c^ zN3@EfNbV>!T!3IROI}j*!H>`RUK}5-N+(!|J30zaS_tPK;$@I}dHxJtf}2i2tYH_;7lJ*Y;}I|TmrdHTN~A!g+kYun>Z*7%`q3l5OdwAkVF9tBv5Jf`Fy zZo|`fU3?b(r@jY@=V1cX*4pfkZ$)1D(tiAG9C7B6b{c<~gj+_7#;3JntqQcBke9KH zo(*aMuwL{6%dMWrsLkLAjq4XyV3V6mY5Du;ClQgb_Xj;Enth2Xav6zMOouw}e7Tt{ zBL*YlIyBEQ3zmpkb<$~{sJHoa=fu2RSZ9KEGy78y5Cz*t;E%ShQ&LHu)4KZT&R&a(}sGTS0=rmEhlI`AraMoOVKx)@1`{(jAiP3{jRcB z-=K$j5N0LcRqQg3xxpqrdQ})NkY>D{>d~rW=p9g+bnK`S(Zfbd(%_ZOLQ!qYdMQ4W zt&Xss!*&+|Zgxj$WQO3`)k6<9!e2r;-6Rcfpp|!E2*Q!V-4T5+MRZXmuXjiJ0KN{P zWgI0}MR5d(=7tDwOddmNi@!?2s+V3XRH#4IFfwtcX|OESs5p4)Mm>}-CRjwkT$9I~ z9N|fMO}BqunXH2leMUOfVU9BACX7 zfuxdcGk#4=1YAfK(dmt)ZRc?Q zMu-vfC6mi5_^fW|wYv%Q$%J*Fm8N7lUTLpj7zp3SS5N44BL`fxa_fgvPv^7Sj1Dyl zkP?htdJBpLR&=iP&h;uDd+BW+T|!XCR(b+3uP?N)Y@GOh<89x>jqP&pLkyB?9ZlNZ zXRLJu?Jw=T)i1hsd+;o9_55#6DYZ-5LIdkuqVD}d85QfmlO6BppwPAt@s>p`5gf3qBd&i{~$B zMF-%*M`j^fiu_MBZ)IcB&Eq^Bp<6+FIm&vf@g7n6uJWsm%dcGY$v;$gS%{Cx>W+&A*~aA@=!6$5B;?!?^`TW zu8N;U(PNp$iHWHspE-|6{Yww)=ir(PTtpEMO_e^=0fb*b9j>xo(p8hF)T$@>Xu>F| zPN-u>A$dx^zoB5#C6@7r?m^^T*4*01f<_Je)mSu#uF5M$d*{>dI3!`|xTvHgFK*mi zqYkP74Zb#MX%-@?CW+-81haHjvaawqv9C@Cd0n_(xDw~|+Lh-p0@f#Xu~sjf zaji=rqyw$1d=hE@qNkYqqg!Jx`kL}J{=**}d!Q)n%(lJ0w3`L5Ls`aPa&vBRN1lkl zfa9T&O8GSOlCcS1n?q-X*_=W!OORU8EbTbcE7UzzT<|Kq-sr&w?sI~~fjj?*d|IsB z=jimEk|smP2gTmjcf0*>>ZkSUux2+T!aESn;O7Z$l_+1=R03~ym>zbyGCAStsMk8# zkq*ZURi^eC>!!+PetuVuU`rN!>%Aejb6i}I{ujfGJ0gjnHf{v^aKHNgb6#r)LmF-| zF4t;U?A_LrY*%S_E3Dt2e-b9nDzIs~&<_fkEQ}OM{YL&Sgu3+-`?qMtkv^;JUO4&K zsOURf5_?59LF5ugiXQ)Q=RD?;_a$^P;hlGs^jdXLLogB5c$_GS}QES?p#Z2n4HRSR1QSvXwOSOM1{h7}M_n~bS%AD!Q?1LtEr(RziiCLe# zC@5(1(6L<>aq?7w+;BBi)mTq5+Cv{R=TxGv<8opI$I|C=DIJ!G8OK?;BkZ(0k3Edu ztgjA5Ti|BX^bMaT`30!dD4uB-!OK{uC5b0JEdTDB{r;*O)JG-80UzzEN(S6#}=}&FOu2r&(U8@EmnhzK!tv6T&!esMk!&oqwua4{-aP8 zPLH3UAKBQ?cWL&jr zl|KrzyKGR+E6^ftc-=mN?jcOUtmavgcB)&tvV+5&NLhNW8wu)&y*EtKQr^qN2iPCwTpKtNJc7Nmp-WRR~3`!T{x>VP{3&EeRdOphM(e zH3u~C{%#EbElGrPJk;O_ouWPnM`(aZk>Z<0RaFqUVeJf>`>F2UsLBLN#;ro%?W?zf z7=@>L;mUhHX$3WaEvB~-8pN1f6xFr3zT}`LR4FVr_AbK>;^LteCMBDH9#FLR`R61{M6E!*_ zXg-reoxx!@`vK4+03tT6k5mI2fi-@ke3vw}|8aARS|>`}@C1eats%7IF-*Y*ZhnyC zC?)m698gYL!8Lb|w;52_K;^37{Xc`TriH#+poWT&e-DnNrw5%vUk=IQyLstXGuw7! zuQFGAE)4_OknXl1A`n^ub8L>wrblk8$Nejk^c9G`BwEom&w^RCRhoPSA*b@Ft)=fk z_UG@(EeASeANBE=EX<*Xjw*xNMyWGwj-v{CdtKuKo3DM{{X$cpxzA(|x-N7k$V4&cHJ?@G{81%B41!V+MB>>Z)AsT-|=%E^2+nx&KDRn@?uC zx$Strywe1YWxc6WTrTq}T8Hxx$iXzwX{au?g!~n=GTtbeQD<{89`BhikhzWUx_bhn z(9QpWQ>(xEl^9>iD_)lZ%6Zz=H>VX9^ass)SRgCQdA+usx%DV-8>x%4yEPN!$nsy` zD~CF7H0dH+ga4yE6m#gmoX3-G2fl)!G8B9z41iRS6~5zW2k38f5eE0(dm%+9*}mN7{*=bG|8%d3#N1QefMsqm`E z`{TMJJ?>Au8{CktW2uTbLS7xF(q~jQv8Gwvy4({pNOmpi7;Y9D*^qveWu0X9`Vkqi z5}qX`%76FXbB=f~Tut6X`}-3Xu0+R|n5D+dy((hbXOM~5_%b0xKilDT4w$H+Vnom7eQWg;n zs-K?b_kB=PxXp7|EFrl;_CpIBkF==ALZ5m3&xw=-#8eaZ9MD)60v~@D*&T zzD&`tgB8y zDMsU*;S~e9hc>q2eCn4ZE3d?qa2jDRha^=;fu_E2Jx$wnqx3hX&KvugNUNGYcxgwL ze1CjcZb19!C1Y%mi00i=(}y)RD0hmVI?B8$(ny}HtBa;|9WCnQ?m9g!*mRl?mwQu3 z6M-o1JPX}PY`U($asEETJt=JmFuaisaZe_|*?2A%OH)1A65YMvGSlT^EH<2rxr|d6DB*c}7?lmEbW!ni_gNC1T zZ8!Z|&2n_w#R_J0P>KEU1&c&6As(o{#ZMM|URAYeiCKMPmK4?~qE}^tEq)MYq#JZE zI)NhQyX{|GaW94>woM?g75!*%*U1sY8i_YM+}7}wG#4}1dZ_Npcl3;sl$VIzPfrX5 zF+eKB#s`u3Q}YVho8Z^{OVT3b;;Tu~X|$b2 zuqeTPc3pQ{<4W@BF`Gr7nq)m>(qei~r2JQtOB`J$YW60~?Q`gR(fH+Ic^lKaMom_> zis|RPT`svtq>>O~14i|R#%@WgNolw_eF>bG2&_;x(#O4-@T1ayq`L)X-IGrVXTfrQ zyuT&4d%HZ~tw4N$o9NLvp3@4F(H&6M>h6c!7vkb(P`B1Kn#Ii0ZPF5mN+ZtmNLvfu z=b6=Je4{`BdU~)>^A&tKH!1zid_u*Xi;KAj$s%DuHEOk4PKd0!VyU@|kC4AnU;DI; zDXCP&>U@MYm;}urlCKsmlpy-4>Y6AF%9wvWe9$uHD5jC(3Nv1CvF(#dn8q2$3JfF+ z5aRKw!o?LICDbl5L-ead@cl-lv}o|;%Z~o4X}1y9D}r&v+G(< z)HkEA5AdDXft$^qmb`=<5tj+YDRt;hMS39eyXI^gf+Vb&w-Hj_aLral>&T2w%TZ2` z+9GFSvxgQ;n`rDdD%9CcFT~LvU@$_i4Bv9?d~W3qXR$-*o)>@$ddZGxdkRXOTjF{# zvYqmUMs|NBcel-n$ATTdR@WifZS}RMqFhBiNN(KM=V{|^ySfv5Q;hj2i<4{Np6$)i zB?E6063GIPO*&^}J^zB7n(Xvd$nm&1_6WOchyD2Mqs%LTCM`nv(R(zWYdOj**(g8F4e9ji$=Q6#an`@MwP z=4ilZ(3)kw!8g&>_$=LZi{zfCCcz>b&e&O8r3w+&-(X)%gbGx;JRj*>C~q}Xc7-2u zba!+2_fqp^-tqG^pd)LXORJI&ZIoA*@-xgg5>w?jN!ChMe_v3p&=+s;>iTv}pLu-5 z=%obSGSg8Xr(bzj79EY)ew&3*LY3>Y#}xqhd15K-0QL3mnI=k^L) z^j1W+0^F@9N+L)Ft7~S3M=G~%bl?mArN#38QX+~$*5j#W{D|TX_?WHZv1nIU=RR*^ zpZ-)46fyPL;A)us(bg;xe^))VQJ;tvtJ2)viVC_H6S_~@`HLVrB*UE3R|>2jnC>s= z$c_x<=y}3Tk%|U3Y4zrmvShgjDF(!R&d6!QkKIdo+n(RsFE;I3R#>%<5H9&#Upq`D zOPE9|IFgS@FBmW`^uT9%i%P;;%jO&c;RmeVU2p-D~T$b_$$^mLu-C0 zeTia0v7&k*6N&@+rbZux0suS${N8mq3 zHx~e8kGwKhUNIKgZ^A0CyN?P~aK#b`R-MPwVyofF%(uxGmIq;4f!D`%t35+_rSIsl z-0;SRAf- z!p+Wglyhn6Sq2EezBH9mCKie7O2h6^856xm^$nH6pX#CkHrDgfHaTt@W}dxCSE5NtdGtp9FfQ4(u>&y z<(CERqPLyAatDh$7AIK<6+5uZx<|1V5mP`b#@;!}_0%`M6y=!x8c{L1S+TL#0v%>a z53injYe8C^(+TR{EQTd+*B4$hH?FDCUzAkzx687UR15aVkg&OICebUJXrsuhm_N`s z1{<$xu3un{tNllL{4mo=1#y!~OO@b$xGeZg;wm&N#McI0DmQj>JWrC!Ijhlyw%5ns)V9Y zCGcU0pMyf*fHq$r)n7coQTf|v+g2Y3-F<0=woT7Njv!UGmjl76rnZ2CqT=%g{Jr*I zlAzBtZrcWgB1^vh_G4&kRQ)o+IY3e!_y<(&Nw-Nw`V}GjC7}HX{0P+(ki!IS|82Da z$k|!1y;!Q?+8-~?et!#y($}u;a6&k!$@J$TY(D?EDfGaJI@UMdQg`L@!}*_K6wrJu z;F|Jir@xXC2J|(DrDs+_OPU5qk<*QT2;BhPT1;y|Y^?-8Ksj?6WpeGfKIxgqV&|>0_ZXXdju4|Z`{$%!@FbZf56=0K|-CG#@3HG06JH;n(^UtjQejWV6AM7FO z|8~Xb1!u-yq-hY2EkaMKh{}H}m@j+fKXP~wiyvEE8oJeo`wvv_99#f` zHXsBbKSVmmlyN!u5vYUEJlu6n--f;iR4@Flx!@cB@R_<7;4Yx?U=W(uZ=T=*u>~5B zlLkE*^lMDu+Rl5y@9ui#DMR@aB@wqDcD`yzHvACxeEry+AG(JZH7_Q_N1@@Ra$9<; z{Wj0j^p~`7dY?U_{JZ*i9F za>CW0%++Y)TWRj-&Gl>rZ48@^4~xYgY@w?sHNV2wTl!;wUC`IAf2nFY?TMb3g{{j@ z?*m4O2Z-{nli_EyXWQQ7#j065Dv*U=sho-~jmI*6*%2-TljvV!` zYtz1u?br4m&O;$8@PQ-D&abuI8Q13cu`gN<*D>@l;2+ws8cGL-cG3m4+(aXR zF)!?o1k9^>vr!1+Ba`(iF@C9O{ta38DR^B<&9LaoqEA4(rCPCbS>|??9g6!%tUZEL z;{^6}%PFsDX5m32$G;%Ai*l|S39W@df~UKc{VDT9YqLH>YyCu@iOUiSz3^OSiWkhX z7+%;AuVY)R5I@nKtII9hudBhDaBA$rY?%zk#r^F!+8BD4)q`La!5x7{W}8!GNu9sy zgVWpw@dGV`4I+qgTMd8IO3>%(2lrGS1jLepD-_7yIVqBxG2c$Vf981pH9TTA84GJv zT>t5f+i}>L&i6O4eSxCnecB%M`H4-UR3`)u%{X;d8<;?`-0}_Np`y{>f`X!b)O4wBT#4*_UDH^H$x4M`b}?I;?VDD| zUnMQNy7~%5GrEd>;JR6)K^rlrd+0(6D?R&t_IL6M&9cD=d8t*QJmZzri76~pBDX){ zz`U&ACKV1N#vON7aCS@@Qd(YditThwdz54OK7@9U%=LVf)jQ0>2p42~nAy5FfeQ{e zG5$4Uh@JZS#(h%1-Z*P+e-*_naj0Vm>MJHDfrS=e5C1sGm>nB~x-qujW3k$KgLy6y za@autS1t$A z>D7JCf`cqkz)TZE+NC>EsXiiIX;(qJjX#8O%Cyp}koz2R1|dGXgBf=1obViLP}O+nT5*X`w$oZ1m@{X;5@esA4nNv@>oMdi8060Jba$r@7zXol z^ehs~w#=7`mHL(k58Y$9E)G9s#ECfFii!ip5~tR~DW>a?oW=3XDx|DiX@bqn#od;g zX^Meak(I6|Hn9%o2layrc25>v%O4s2EI4pVgB7%`EIHonb%^J_(q_i^7vu*YT*?d8 zc7r$y6I)zGQV^5f6nnoWr&68vMsh(iIk&&vTSD)U4@2**QUT<#Dka7;Cc}jdoLVc*Q8hLvLO4;M|bkQaUMuAy?1T=@v9dm zFOLc^#$WF@D=4ws-t>yxiX2&~qZ>pumJ1@2&^kIq4BFVn7@9cKTgHdBM2`|329$j*C%FJ%9Dk!cl0zA}B;NJB$ zQ(J=+kkY{Cuc=!MuKbEQYrZ&b#Bz6_UhI&~+xA%L(5NRrxjrdBU?k;B+J)(H_UoM^ zjBcVRS-vh?13dK<15#c`Rtae8zq#<{4BQ)0?|CXq-a)&|*{7^fwlttw_LG(a{e{mG z@tTPQL_w-&Z-=gdkcj-ei0T;ksEh72tZ-08(?)eDE@Csv;>MM)R%hZ_ZjdfzM5cfA zIK42$-xWN5r9!k~)qlxHY>SlO>#*<0>(w~!>314Hd0-t12g`PH7P;{ViTFVmD~#bE zE0l)yT67bM*`&C?8n#E?k*XF|S1wGJ+P z2lbRD9w(W4{x+HpcPZ`uZJ!*{O?jy1x$xT_lAyl*x5;+M-TEV^L58w>Y)Y`Z>UY{} zzkk-7UjRdZ7ary_3Dgb3KwICR*I_@yfRc)~M3+_Ghr*M=FMveZcUmn%SZoGp-=x|O zCp=E7{qo}d2(lXx)h;FV%bo{6sMed$XwJc};7N@=XId`Tl&3e3XJ26Z8I}ze$V@t= z_U99bRZ8$brqXc_hTk>^wQxuaFXLMVc0_26$pT2a9aZZ;x!RPWPPIqqJWgu&WH7BU zftC5d5 zVU+b<1w!xuouwK^)r<~HZ-@n|qCJ-(39moR`T(MrJ2*M;E_kSuWvICAEA?qN8n_s@ z08k|E}5Krd{8n;cneS)SteJH1OAt1=)v98to>hjpre!u$t!t;b{$hD=k?2<-_Q_n zfeA|=&l4761GuSlT6aJUS@cvIoz%xgTzfN9^UTuVkZf!A6V+?%ax?{H(#qUG<<6VS zfd5}eFy&bZ%t2}kzt$`62}PC9d;$xA4N?rYJz2MMmn3zd0w_J0&;uLE=GBzu zpJWKXOHarfTpfflD@k$^Xby7k7YptE0-zR|;I&bRIu7{WS^xa!(f{Ey26-oC+M%V; z(Djz@OnY0Kd?9SR#s)i^b)Z8WboI?(A;B)-Dyk*hQM&B*ya84Rsy^Shbg&co7bNBj zEw|+`WR!?{sCK*YC{P4x`pxBWxv4()uS015r&UO8W3o0Dyg+++%U_T-XPVMd70COc zr9%z@Z$OoO4Hm#n(7~Vchxa$w{(^94)BTTAJF{!KfnUa3LU`-hz^wxV;*oa$FuPhUeu+A>Fio$eL9vqmkJs5F&bCr ze^WHF|3MjAp8&X$LyWON79VnohW9s&EJPueG6SXwRO=tg05v=+iAM5>FbxYMQ+753 z;_YbgKNPa7^NY?9h{Jv`V35(oZ-Or|-<^MB(a`a)fm!_~mN3%)h8cR&qd_gI;<_Bo zC7RHy5IPpfizB_8U|N4@G^e=0lk(4#IE2b9kyZ?%0}UgO>wf_7AYZ}>AdTJ>yh#tl z_fD!|A=2!7+F(gkbN*MhKi7NyZzP!t&od%0Cxu{5}k%n z;Vi=Jd*C`S3-8$=%~}8VSYTXxEsDV3*-)|yq#0BYE&G-)kD;20TShZ(OJOy z#Arf6V!sv;h76_Eo`hV$)4{<{)*lQFT~;ymf>N19kRt|E_FH(!M)H2!f%}(3nBRy> zy5EE$XFAh!DkrQBxfNUw3Hc(!r;|&KNvs^{S?}Lu^Y)}jUp)!Wv{wC2- zmrT9mBMcR_4|c%q#sgFUsWhdx_G`R7A@p=q%Ft^7qcOaa0JMatUnN6ELr8WIl_Hn@ zho@wCg(@?oo}NG>d1MuMmq-5KXc!>W*#U7cF6~`#;_t3knZY^_-;oXikw9>7L>vJ; 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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 zcmds=3!EH9weWW!JWP}T2@(Pc2?|#rncdCi9dfy0^OQW6Y)AqoK<`ZN?j$qQlb+t) zY$OSf0D=%85l|FZKU5GTKJhBP;1U(Spj=);R8V<*qVm(r54k9cepNm7>YnPJTAWk8 zmF1UY!&Xhs>H7DnQ>V_UT6WgT_SRHtY^tFlm2I89uFZA2T&pl)VJ>f%*JT~AXnX5Q zR%T;)-LhQ9b;?esmtMYLRjcFL>3OA6dZpdDt|i@^Zd&J}?@;Ru`iWI4q5miJXl?Jd z+)_H5&!^D8)X>nkurHH)_V83HbtM1TN~6>;+(7v9JD|U(wxi#MU4p(rf2Q)CNW$5T zjg76d8v{Z&R)lW!g>K}Z9um6I7rGG%-53yB=phI_fd0vb4_5y9PoQrPJ*e{cmF@GI z8<4;$^xt7myuD`cp8fxGXL`pyBlo=XzTdt2&W6{nzv^!X_vSBO`l(&xzc%N$x9)%D zmk&LE>yy(*O+4s^5$+j(z2v6tdw!GIA~Vq z$~{*8EUTQ$qE)>FTFf`E05R9VIpL8@Yl&|iYaX_H>2u=oR5BBO9RPx^#IB$;5R z*5ryUQ_^j2u8{L`y>^*TbD6t7S#77n&HV`v+a^4x;IG8&R9G@Y?bLb>R#+oW*Hq-Q zOsSWdvg>W8+WX6%UBC`jt5fvs&7O>@qpDi@C}E7^Q7S;0rz(LAC1_QZA|g0b>IY5- z5IAS`baoymoV$+r$6epM^Q8QS z>t+mJ`qaU1jb6O5_}!DvyW$Ho7mY8zx#_W8pR>+r_}1I@&rbO1ulK$3MyBb}ffMIl ze(oK||LKE|oqFt#_K$k$^YdC}k2_@l_n$m{=04xqe(a;8{`r&1&!3YzC-_W7Sen#e z$z*19G`DniX0lVV+3d7t{0PFX_9h@$TAh5}&Uk2PP)2@sG1KF^c2R0^#t}W`Js3(O zE7C_yWc|P>$%Ql25?QfNJP2BI-Q4by2GW)k3O%0Hk+->#IyQeBahFbzsLeX~Ap~r$ z;um(CHFCyeGZeE6ojLv zI72zRNWvLcii1{H@7&d3&Du+LCTHbyTkI?+?R)R}G_hynC3*{86j9rv(V_{`8OoY$ z>J+pBf~`{nY!Xv;bTwd#P!%;oWoDr2-0Eiw1WykbJcW`|L{9xOCu`@^^NK~svnmch zW9e6qUqX!4M5b?4FCVRg@d72*)87h?R+NB-nyhTiCoh8M?_kwu7pGqjc=IjKlET+; z)Kdh$ljV;f)YAu$u;>4QLG*tGHVH3H=_N2Er80&0|NVyF9<;sds_}iZuYcj{nU9Qn z`k)8wZ%-I^(Ru%V^WI(d_>X<&p)*Gx^N0J-{L)d)dqzCf^20w~G_h;)En|M)eD|L3 zy>H;K#*?4h`hfH3$al`$`Sj1Hy}WeRwF_Dgd-bI`J5Curch945-h8=p%Oh_sYJ26D zb=eok+_B(=KW{wk^rIKu_pp8Xn-3p;$oen((TwIF%v^on^ySl1-AY3slBl?!*wWn5 z*)nt5bSv93eX89utulbY{X|V9F6c$p5N$2m*?HyuqOdyn{p`=glNjo$ILqoLF(joj zi9>4>JA%ZJaw3U!4C;G&Y@%%6h%X!_$lpcY$>*-Z0|su_Z^q(*w)6kP2leR%@loh#)nkQEFFxmK5D!mJCHKx_VrH z{mM-jUqOs!S_%yrMueiuVgWB20vMIRZIvLCp%kr=J>MWib<5g;+8;-AU)d42o^E*V zQ^X9>{c+O72M61nWcSBKfd??dHTK7cmLE^|$B7mqEOm6)F9fqVDA$=ySd-6snvgPr zy$G(YXkWU(?F$k0NX!WNVlyXB5dP0C>sB8(_19OYmJ8C01ERQ=@0c;enwFV1y?Iv4 zRJ&>BtjfO?T+2hn!f}`dWhxX3PLYob_g!f$TxjMGPwiC_bSzwu$WY@?W8s1(B0P0m z2|;ZS`;z0|+HG&aX3s9-oOmHqigQG_hvhxk8_yh4^11~*3h3=&xo|#RwQT_1j+K9i zIIvB)#6!U`RImzAX4+RG#!Ta%vq2+c72yU#n`tI$?IEIO8g~f}h?J)^mZp*Ea#%2W$-Sb*dWsaMkwiJz%%3N-jge`bgAs7NM%6#}5@P4U7k~V(m_k zn-Nw5Pu$&43>b};#`R-%EIw-yI$9bhqB32UkT6B$pEymz5)48Qnkbel#?7m`q!zM&P{uxmg2AbfS3QtMuNE71g%R%h(8 z;I;7_?Xl6#eb{O*x4}wpqa`yGvh~p#iEx>){ASZ>Io&h3&SfiCVgScyjM|=(En9tk zfS`gZNC_AMJgtUTtURxb7NkUUQ3MRSASFUNL#-JtNQux22sRz71U#$eiqg=VY?VNo z`;ZN-Rf4F9d3H>;N+24EaMf{@P=IpYT&oWQ70M&^x7xT7JC3>sRjnCX@tbo!(9w=6 z!3;HB>mr|Y5G=J#LWN_+pb~%PlM!M2gy)x^2_^oJCO$&gm7!l4CI43-V+{IW3{l_# zEQ(Q{6;1phT8OZy=9wtKvH{8p^%t!IDji?hQ}l8LAxLriKL-dj@24+Du&ECZh9y30 z%!Rf;L8%NiKIh4wu?SNg;}KBK`J%kjfeXYdd8@5f-Y#Y>`4K(TIgjX~2p6<-9wD8f zkWuG6LMtHH%0QMYsD&)eiN{$_B(GOgA4S|g3T(@w#50t)jv7xL2so&${NU&??~2E= zXjXopp(0w)S@{8(4CRStDf(R;MY{?3o)mw7Wh9k+ z31iX@K2){C3eX8-QrUcEYA5~C1TE=jh=Z!Slvl_CgerFp>3}!=iRoijg0I17xoc!6 ze{k_Kht zpCkpnt1A~yact=>s{BKO1FLqJn!>Ger4`uCG2J~_>LOOp;2k>lQRA7uQHzTwX zVXI?o18TV$)It`tf6s!OiSI_2n^Aofaf4oNMu}%AZ*;jCr4|ryfGY*Tx~;LiOt+ZM z!)v%`X-QCT(m@e54J(BRWz4f{!pfyuCm_h6Vj4kaB(4vQX#^T7LIoYu2*_lp(V{Vp zfG$7~LG5HC*_WWB1}x9cwtJ3ibqT8??t;;s?CREvI6?1ZR|{q+Q*Y@YM9GW1k|fB{?uo?9$fMKs4{E2gR4Lw7+I{8!nJ;PZR? zgjJxlk0MYSR)JFS47ECSS&*=&iN5f83-t-P=G%g zZ5FyDH;>NGXGVw9L_-0AhCWItGL3Ht_P_i|5PcUsA_zJZ5Rl1Gj%X+#po<7m9Z#-= z+Dr3gKc~lb`-9hB@oZAMmlkZPh!ON&T2LrMIih=eL1lnIf;#ES7hYn7=z)az=Ze!9 z^hsAt)B$V&joPB4CvRaDL_q53|5!l&Pi6JR=P7<@!~4j5Txr1|VG|P`CG-`XpmM;z zjv(#;{?}_@GPF*gX$w5jASg=ZFe>NFK*e~L+g^IH=ao1rJa9;3JhTmiCBCKsI>tlA zF?0f=F&?T$5SY5%NDZ~JqGDGcp>Xo<&ygK|y0Qwl^#Q`~cRWDQE32?r${-CbyF*u2 zVRb~H>UjAuqy<`L<%C5Q2T8O*xQ!w}IxG+qc>n`#)PyBkAgl%uFi;z+nD^yY!Cq;j zZPgxpi5;K3_)3ays8+Z3A*!9u2ECzLEts+$8d~R{(GAsVg+#FGn8XH^!|W?ydwq_( z5ob!eh0qNX94?x}Ebrk1Ry*npox>~_&QQo`PPJSq5wtq4&LF3}w!m>2a;F=&QM6V% zraUIH%9J;>Y?L_VVKqcx>S&pu0*9D-SyC-8h@yeRU`s`KpaX|Np$xS^G;kPH1_-1s zptukgex<#s$1aNpv2%xe6p3Ys?Gzz$fMZ0WNQNS`TJm%a1gPE4^q{j8`~6Yy1BZP2 z5&@#^h7dqhqyqpTstOQ5kny8-+wIL|>^3J?^kfi;@guRV4-#H(Cm%l&#Zrc8=*Evk zbwr@*7(ar#3kuH*?M`N+EzW4%F*;hXxultoQSJ2^^j%P4!Oiok>0MBQCL%m_T!}%Y zbzsTY*d49;9J&phnemWh!EA$0zp;U64@8qR@Qb~ z9h4!I2}UJ(nFp=3jp(B2DM44-Mo6cO*w9-0(@NV2twh-B7z%(~!6A!^Hm+2+;Ct3f8mIf|0{4zZ>NUvBo=f4i%=sSU})K$W&Bx+aHt+Sm0*>} z@}7e2;>$%>Bahn=k5c6xH;W~O+A>RXC>}8McJx5vb<$ zbfp`F6@UWUn5E)NOb*LMB5f4y4*15bD01zC1=&U@=_U@4>Km_c8W~iuZ$-xj^r|m& zvhpGv8tfCAvG+VaW(*zd6C^V96{o>IK@$<4Iy$9L<;g4UP8yn-pDUN}nL(jtG@mwT zd2&=A_V&k5AJFB=QQ{fO7%fkZQcDD`seaQ5)Gfk-8Vd?JFTG^xB7EyyWLg=$ML4b> zvuDg-@x7O@-vWJ$aGZ#S!bfirj#EtpusN+%-O-`o&p_QG99Cs%uB+SYv+?i#Ao&dO zHTg+oB@ok15kKf#gkvN$lsgT6c^B$c6H z(aTdp+5};$TiFp*)LN8&GrGGKpARpb>cG#Hp;2pT2Su=;qt;T{48@B^t))5vK?ZfB zp`gPu+q1HkXYpy0=eW@u4JFM}ct#zDe#IYO4t?*rL^4D1qBj~!G~#eIr>CaX4pLX} z#WYwjSt>U_zDQ!hc!!~9!MJROA||n5Tqh3MROIg0ZJLC-YeRTVR`r)`*}YGvkGPm@ zqR_iGB0DL<27T8?ql%j{{ZtZkc}?smsaYD@8v2MRZa`41W5Hkk3%o`1BW{7Z7ytR%{KbiV|<3;KwCb zTd_)8Ma1-3F+m0MCd6thR#68Cs4Y^!+uWuuH|JZXxA%VOhXM@PHhlG8tb@;`BB|8uFjQvtbD6%YW=rbPlEJSG=G;T z#-pgCdG>IF;xLFr*3rv@Isj-Tu^zJM2ES&E9spF`+NZAe`V;y9plZPkwR`jcplXFg zu3PCU@f+a*}!EgJUGjCEQ3uI4H@{3Wl$S(2|NC8irONwYw z;FOpbM_v=@5mS71+8naowLgV9cXWp)Q|w73RQbeQ4AX!h95GQq7Ky@ z=~{Sj!IbG5TGiO$g$Gwi1gl9cPgms>cBpb$`g$yN@DUO+sA#!tu%!=yKVh zP|7e3tujbjE*n%v1gVY*-(Y(YWXZ%TBbj~TCq%Hlh)`PwQ+(Df_+CUvEJJZ(dl4aZ zfRyb(_>|qPnzqEM1vC`DY|SSh1g~xh zyn0O`PZjmY$#=wf@>{$qQhO!uM3b}eBk#$^;f@S*>-y&!;))s`j zRCuiwOLvOu#QeW_LkOPk6eXXb?6GvGD7{4JnhmI9km|wK8T^bMR>vUgz-C<%0jZqW zgIBVF%?j{JESYRRXtj^hV08ns&kzT-j^}E006SG|7%%(kF5=A}5uC>rXQ~aF+$AjV z0A|Rj%rkMB;1N1g|NGd}o7P?Iy z(nf@-j@#r=UNx8A?Js#wmur=}`_uFCT@Fg7?H1|c*|gNFuGQDa?O5CD;=cS7bd5`V1xFc)VrenRGb6=_Up zLgsQB%47V5%+&)T5-c4}ZamgbYns(+yB@lnK4W>Ph?Ff}`SeWF(bPV{$Fp`67CaqI zOA|Vx;AynO3(Lh3H2vrMaC~P!C zs9GTrtUCIKP^ZT*1^jR9cAS9LC9`-m>MHG^h!=F!RVtgIgwZIlR3{+FppL(0IZiY4<@kNf6 z^SIF+6WkziaA>+&xUC{U(CKDjv6Ml&|L-^a_Mq)uSB>wRefUr$A9cI51l#sm_OWq=9i9Y-ZSETF~xqBXc^XAK)TON69QQIrGtjoSQ=8gp~ z{CVSPrysrOzK8A8-+cJ+L)L#WbvSzSntw2J^?lQqPfO8sv#>hv0;rbej?R{u)23V5 zmg!UNmT8q2JT<&`YPu=i($vyCt04uI2us9A$8w`nVFZunBVL+SJC9VMe9N$x-g#!ZzSL^ zmTMi@sfMl#iIC1v#%NtggjOPK&FPkUT^j;*u|QA@Sy?)dNSsJwhEaVOZt;6o&=(6t ziDxKp^kRW1wSvIaEh`)9+S;HR%vF7le*gRSTxf)X=PEt5!$^t4S?GP^FqJ3$&N%D-cY` z=fxpMif;~X#4uuhRmIH;Wdi1b6Uu@@4`3N;q?8RUdfLO!H3*H;RV6CWfI93e?XI4@ zH@eGBHg&HE%5`mkp=FF=EQlZLixk;`7 zCyQ?JDU+7lM)&fe!=co8cOSakHd;Vs?1q;APs?qi6%)a0so$9>x-4CrUgjKR9RE_&0%3xp14KJnnFz#JvQjF zrjS&IQbo&}LfVKh)zMFb%8U@C_x%8WF#IWxA4o?tBf@PJae~f_2#aMXPc$3XG}{8AjT8X_-vXlY7;22z77*0~2pX^rY1cv* z++;j|-)E_l5sN2yh=E1rt6KXQp~ymwz$tiCzDh7@TSuIoTrkS(+htLpA1BSX$bhVZ5 zu~+q!^1@|T+yc-WMR_YWSn)ZU&^L;5fed})>5ZaXksvrqnV2eB1W-;aPGVZ@d8Ko` zTt4UZ`@`U`x_PXSIf@E2%iX$hDnLTF6Md zy_*42a5$mD^;qo69pu6VxFN@ZhU>#^{Q;|;ZwMW(4~yjwoOv%g4cCX&5rL{>hG8Da zcfjJK0y^Q$eCA2a$^C*%6l2KjSJhb226(`JMU`BN#^57SdWnOmTHdT+6`;y&u>Nc3 zJDHw>UG%u^=4%OBW)o=W4<5E1@hb`FGMj)*h8hD}W)sjwgs6@?!9_qzu(r+ex@9Qw zeY>&3I}>WI2vn~IYY>vmP^{KRKWiYkp!Qk(7sH2(O)osjnC`Q%Mv5Ro@3Sy@48@1; zvoJk?pn(dK`EnQXaT6O2l7*To0t6i-3rS@tMl?tk(gp}7NJkfGKz&^?aez;5qN5vV ztO%42M>imsp-732cR(K?sGuURNOpe&{1rG4tkK9T`mqlV^0C<$ZtDLEY|lYQUa6q9 z10*!kiF$~GQO8x#`9KaV^DHKx^)y+y9$Imr6bGRL*fGTd)*gT*6`6q^{)P2^_$1JI zmQ@z^KzZn3gz$64Vd#Py20;{MyE(K@&sr|1@GbyRIWcQpy#XndGme|j30qW8{ptzw zejZMLsE9@u3JofU&qeKb^laMxT?~RszCzvK(xUB^J*$|tT%pYK3w!>XjKc`w=P4Wx z@+UMhXUS(Qu(wnYMfoiqTKP!J1r<0TQ*bGhNzT35oy*K(5$hRkjkLFG5geEE57*be?0p2 ziL-aVa`BFDxQ{F!am^89U*6gE#lQUT3%6akeeTLHec5hx<+D*tk zUFG4B{)*6kJaK46eFv~5sgd+Ew9IR95JLil>Lm%Hid+U}VW0V0k#PDUwTI%`ijaP6 z2tZ0SHILAZs)GT9r}|AQ)!y1*{m|faxMCjqJLhxjReK4ar>Z;>zqcZwgTFneDvg_AP|X4=6kgMJfk>^;ETf{~DE8Kl6C(R2Tvv z5;Yr3bT8?a%A%>wLG?1i>iwQ?{jjF74EI*VG9hCbme$Y(QnaxQD+q+>7Om@gt`xJC z15eH+Xr2`LqOxv^OkS3Zhsne=6h^!D002>Vg}y~kt5YaBMHFBvr?EpB95X*sxnO=4dt{ns9^y@cB?kSDU9Ai#})4#KfZStA@$^1kHqh(Naz)b z63TTn&)4i$8U`RDw33gneY|oIlRLk<8U6)_5Nk z3EV!|%$R>U93+gAnW3PL8c!YwJguu6bY}qBESK+`^YwPr>PG$vic}`Dx{*t4sD+GL z-N=;$LbhL{t;8GT;eh3{zxxrfmJ=i1S>8*L%uAE-Ai0!=(wMFJ_<;!Aq>(^ZxMU@L z!}El^<04dd%ZkMHCon@3Rm@wZ$a4`<8eY9Nqqs=XAMNsk=YDr0CghQ9MEL zrmm9+a;M3WlXX{Q@R|gyo=jXr&0j%%K!7m4Dj`hKX?ye0DG|Mu4Mnz$`Q16o2&?iG z+g5S#1D~M??DYu)8<5#hYTeq@2Gj&XS7XvJzHA6x&F)p!J`qX7|Mkfa5O(EB!!aMC z2yX&O!!be|O0PU=I7Uw(e6Q4Jc|*#{ZZi6RH(3PUwDj$c{0K#Kcj~vhks=!k?xw*F z3=p>Z0`8&lV?M&2J5MF3$^-7feu{i15O5F5X(*KPfO}9iAQCmkJ?H1FuA);$RYNGP zLs~KPlY`GBP|D+;QJ7YpE#o9OJMTN}Um%lLXnFGP5FOz+- zht6}98_3z0TuZQ%XR=pyRRl1BO!g{K4K;@HO!g}64ge~>QUsO%{e#nLP#Nkvl&K7f zdVo(%Jnl%yUUESV#j#6e5P)dZ7*EGEF0?It)v~bZc*5UKCM-t8)5UIz zBqkA07sWJ`!-#mgs2LE2`id`nBhO~$UH48W@X3oWaBD^8^cP<+Rm}4Y<;53RAt1o? znzPwx#uTnW&)sUR?Dep--MAA?vM3VggwbGHDIPQl2FtQxFJI zedi7YlwxE)w(VX*ru^Iiv6mu|37k71N@*yG@^c47#efLZH!Cl*Ju8dq7X1}G!#1dtW1>x0#4tK$gISf67H!8U;;ZLVMz@oQNAM* zRt|_reLEt)LZ#e-&80lLl+5LpusgGo93v^;5eaov80JmwDm>XZK)yG+JU0QXSjFM$bJRLQ)!bi{;AlsZs=6L#N@NVO5W7vTX`R98KL zDly0yKv5wbBJw>#xvA;XyX|Y_JG4Pq#qET+qG?@;8#x2X)P)p zV4fnMxc^NW+OO`b$mUi0v$(2dH5APaibDZJrryYo7#rnWeq!W<#ArrGcEp~FR3;SJ z5hXR0$>_+As2mWHy;^PMN^6TF#B}!l{YwPJ@saPP-$jwZZQ3`Tv0>y&nYX&J;-5PZ zbove?W8>3ib&1JdT`zo}kS9NoEZ9#GzXT2>3(9Gz0hAv|7F6wkAaRw|wb;&=@Ir>U zi7F)WenXo?F6RM9B3BKFM7^%z>YUwIo$0^f)_ZEYhGI`e8WVC2MM(|iG1@g0l>;KO zq}4TK2JxZ)c!)qaCdxTc&QO_C(E`90#7598)56X8f*nHl7_`|P~ zjXZg}f1sNpa0#UQ2gEeg%E_ym0-6C)=xQ@@6>XT28}q;y2z_IvFP`6Ak-9C~%^X+8 zJVUoW`gsFEcD26q7*^V4bd`14ZnZMq(MQrPyY*QDU$gvS#C(b(lQ-&*QP88n>UBLQbLN+U0Ui z+nUco0* z5C~C?_Ap#l%FlF&u!ofniX0|j4+Vt|7<*XJ2?#cQTg&7uZG0*~d%v)uH)HPrF@*B1 zY2UP8p@{p8f&SBK$vQb z;t;jYw#%Mt=R*~J2fgeNODT`yNIEMbnm`msBCMf+%A+_E{ebYiPT#IgNU2q>RYco3 zu9t3iG8=7A@GTpU*+f{K7B{z0(5pU45!{>f@7^dq1M`+Zo8Q$i0U@n@ZA3X_n>ZT^ z{VHoC(od1rwBdO_@{kbepva#7kdUa5dCbW}&h3&!5?l_2~ z*2>Sa6MHFAn7~a1aOdBNFL=7#NtX6k z#PZ__tYD<_8VYD}5JLilszx7?j2v60^j>owv6B%#Vx@y3iwXFMfDww?V|P>q75Tl- z@~uMNN0B}KTZLRiL&=kG6>_zJz|*%NL8_eVK=wxvn0P9wx6Jxb?En5G)({jG^=yiilpX zDI};Qv!S@UgB%(lbal@20d&vG6|>@{1h0SS>+$ovq_-ljNzC&SX$=K6VxE^M2!!aB zNm*CC_iPojOLIkG<8S==-yoc(72aUsBNSoXnY3*cL^hPzO@kX4AZ)Ks3fl$6-kj?c z3+P(P)s~yHIs)ES$@H<1{G08ql5#(Ev23aP%p+s+amqP`FHuNzpk$xZE>k)p) z;!xZu3&*es4O;`VRq#_3an^MVi=xzqf-D@vqNpek!rNejrIE@lDdzm4^DE0XfA^cj z7N4T7`nl7h}z-4FnLCz}GEAQW?ApgkvdOw-DP`kxO0IEyT)dsO1!{ zTZmN;h|DXJ@@CJr``c_6PubjVPP+6v#BNSjcwPA?C}MhD(&m#(Y$&XyK@AHKvWdkF z&Mn(+yPa_Z^=5y}|9B8_1K}eSp-nt)Ac$-zwJLD~K}#TPlk!XhH!dB2`y+$!Oi@=w zN)z`?sg?((XNt4~q7wL!=}4D`E%BfE`R53c!XeX9-4tomb;xv-n1U8pB(W1ItYD zJ-_}NAzL2aiuwdam=g$ZMM-QZzw+=_l#)QmYD_xB(N|P^+?zugTT$ki=fcX94x>Lq z5!(ck4x?o@6kK`IVYHe+=kKM!m8_}hnn&l%BpbE zLroPAG*sKV`^&kEmFK8VeE+~8P^JA%L6ypT0HaD(1VU9`t_wD*=v;ZvE-cDB9Ub^K zH@4RL@z-x7l*)5ms{1Munn12gwXBBHDbIDORu70wjWscgal8xhC=YS*K!CjVRn}V( z&je~>WYQW6sJtddrXUcaz$ZEh?h93lSkMl~U^h&$4u_fJ{Zf)|3qwnRs{?Nq*f1H0KImbkvToUM}h+G26B>^!FHE{Cel7MDF z6awGOE7%66O}mGzh;S*v@ZEP14uzX}qdO{+sOx6lXh98SQMj2mS~nmXHD)g_bx`0Y zx21C9kbEW5H{%0jDwbD0M}CANq6uU#M~ZAHtn%#TNG*Y|1wIB(cnt%ZvDL}w&#ot+ z3di6@^ipI~*D-hzQW}b*a136AVn76HjNUADy1JsHH#{#?9=(zERzxy^=#5NTL(!B+ zZ)6GrAqsu1VBm$TAR4TIXXbLkjqfGqQn*&IR(C~6bzLi1OI$-K6|NPmr6CZe`W8g= z-9wa?9kJ5$j9-ll@&!>yHy|4I1&OFW^X-lvVXKg{C=U_^ zx+#*FK#(XPrlB0lgG2$%fGFImEf!9-S?M)726q&D1Bk;NUpcn+KJ(Lu5+db^6r_V9h58dI zM1{=T?u5&U zq-p_ycdfpHn-wK|R6-dgSO^Ez3%WA%mw)Uqg5y-F`&{FL6dC=5{zXj)cJvFP9{2Ql6ZF zC?cod$K)a!YVzbhCRYmxJm7nO1s4+CrMRG&as0ywn58>EdnuWRg?oSHofOg2b?>iS zNJHTh?){bP1w`LQ*nOD~w=XGrw(AJh2_yF$M}Ql}U1Qcxk+HI#x&$Vcp;WDwJYgV^ zTv(7$QAygbazQ6t1r6AoLne^i@3CClaC1ehw&=ksES;frt&e`*K#-MSAzPCxwoE}a zOI^9LC*D`tx3DjhdluiNI)j5k% zn=)R9J@NLMy?gfm&zN^`=yZ)-b9o(D0eCelljsM!5-`=|anO{Ei z{H;$;A2soy8%DTi{PmKXw(tE--;vk<=&-&Or@lJnh+V%wy|b zyZfPgKlP#Y9h2^{CyjgiXLG&v&g55K-*&;5NAA6O?kOi+b9AdcW=@7y=%t|R_&*Z1x`DZk;m8N-)8 zb?{rG7jGPPOJ#}#iB*IZX?6Qc9xgR6b)$M?@z9y3fBb-$u?e+0`PgA! zEZ!f3jaAeV(opm=we%9{YpI`urk_4~>xFN-)}FCdi9UR*2VM=}uOUkx)ocJu`N7Hx z!ZfMa)mj^PHL^Fq0k@W%%q!c)8veC9iF_SByg9lNpAnWuA ziv^C`#G1W)=&*H+_wHazfon>s%-+y)@v=1)6A7%Nn+I}m5mAQXChT&sy(muJagnW= 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] =?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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] =?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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] =?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/301] =?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/301] =?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/301] 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/301] 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/301] =?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/301] =?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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] 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/301] =?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/301] 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/301] 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 ce8cc6ad82a2635bde593a25829d046ff42895f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 21:32:06 +0000 Subject: [PATCH 301/301] Bump Newtonsoft.Json in /TFS/Tfs.WebHook/Tfs.WebHook Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 11.0.1 to 13.0.2. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/11.0.1...13.0.2) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- TFS/Tfs.WebHook/Tfs.WebHook/packages.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TFS/Tfs.WebHook/Tfs.WebHook/packages.config b/TFS/Tfs.WebHook/Tfs.WebHook/packages.config index 582da968..7c5a4a30 100644 --- a/TFS/Tfs.WebHook/Tfs.WebHook/packages.config +++ b/TFS/Tfs.WebHook/Tfs.WebHook/packages.config @@ -23,7 +23,7 @@ - +