diff --git a/.gitignore b/.gitignore index 025815d8b..7a45a17b2 100644 --- a/.gitignore +++ b/.gitignore @@ -213,10 +213,16 @@ ModelManifest.xml # Strong-Name Key *.snk -/src/Cuemon.Core/Cuemon.Core.csproj.DotSettings # SonarLint .sonarlint/ # Resharper *.DotSettings + +# DocFx +/docfx/wwwroot +/docfx/api/**/*.yml +/docfx/**/*.manifest +/docfx/.vscode/docfx-assistant +/.vscode/docfx-assistant \ No newline at end of file diff --git a/BuildDocfxImage.ps1 b/BuildDocfxImage.ps1 new file mode 100644 index 000000000..69e75910f --- /dev/null +++ b/BuildDocfxImage.ps1 @@ -0,0 +1,6 @@ +$version = (nbgv get-version -f json | ConvertFrom-Json).NuGetPackageVersion +docfx metadata docfx/docfx.json +docker build -t cuemon-docfx:$version -f Dockerfile.docfx . +get-childItem -recurse -path docfx/api -include *.yml, .manifest | remove-item +docker tag cuemon-docfx:$version tcr.cuemon.dk/cuemon-docfx:$version +docker push tcr.cuemon.dk/cuemon-docfx:$version \ No newline at end of file diff --git a/Cuemon.sln b/Cuemon.sln index 1108aa31f..ed4a866e5 100644 --- a/Cuemon.sln +++ b/Cuemon.sln @@ -13,9 +13,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.AspNetCore", "src\Cu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Newtonsoft.Json", "src\Cuemon.Extensions.Newtonsoft.Json\Cuemon.Extensions.Newtonsoft.Json.csproj", "{080BDF91-E7C7-4CB4-A39D-E1A5374C5602}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml", "src\Cuemon.AspNetCore.Mvc.Formatters.Xml\Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj", "{A70ADF91-E7C7-4CB4-A39D-E1A5374C5602}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml", "src\Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml\Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj", "{A70ADF91-E7C7-4CB4-A39D-E1A5374C5602}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json", "src\Cuemon.AspNetCore.Mvc.Formatters.Json\Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json.csproj", "{A60ADF91-E7C7-4CB4-A39D-E1A5374C5602}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json", "src\Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json\Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj", "{A60ADF91-E7C7-4CB4-A39D-E1A5374C5602}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.IO", "src\Cuemon.Extensions.IO\Cuemon.Extensions.IO.csproj", "{060BDF91-E7C7-4CB4-A39D-E1A5374C5602}" EndProject @@ -43,7 +43,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.AspNetCor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.AspNetCore.Authentication", "src\Cuemon.AspNetCore.Authentication\Cuemon.AspNetCore.Authentication.csproj", "{A10ADF91-E7C7-4CB4-A39D-E1A5374C5602}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.AspNetCore.Razor", "src\Cuemon.AspNetCore.Razor\Cuemon.AspNetCore.Razor.csproj", "{A30ADF91-E7C7-4CB4-A39D-E1A5374C5602}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.AspNetCore.Razor.TagHelpers", "src\Cuemon.AspNetCore.Razor.TagHelpers\Cuemon.AspNetCore.Razor.TagHelpers.csproj", "{A30ADF91-E7C7-4CB4-A39D-E1A5374C5602}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Core.Tests", "test\Cuemon.Core.Tests\Cuemon.Core.Tests.csproj", "{CDE37A87-B35E-4F9B-9C5A-32E9B22A1B69}" EndProject @@ -55,7 +55,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Xunit", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Net.Tests", "test\Cuemon.Extensions.Net.Tests\Cuemon.Extensions.Net.Tests.csproj", "{E1C8F634-F655-487D-80B4-82F5F149FBA9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Data.Integrity.Tests", "test\Cuemon.Extensions.Integrity.Tests\Cuemon.Extensions.Data.Integrity.Tests.csproj", "{4B1D7CA2-67E0-4D54-BBCA-6607A7770E44}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Data.Integrity.Tests", "test\Cuemon.Extensions.Data.Integrity.Tests\Cuemon.Extensions.Data.Integrity.Tests.csproj", "{4B1D7CA2-67E0-4D54-BBCA-6607A7770E44}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Core.Tests", "test\Cuemon.Extensions.Core.Tests\Cuemon.Extensions.Core.Tests.csproj", "{6ACD0AEE-4B47-4B29-9EA8-4FF9C20FF2F6}" EndProject @@ -105,6 +105,38 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Diagnostics", "src\C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Diagnostics.Tests", "test\Cuemon.Diagnostics.Tests\Cuemon.Diagnostics.Tests.csproj", "{06559CB0-899C-4B48-AFB8-633CBF97A766}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Security.Cryptography", "src\Cuemon.Security.Cryptography\Cuemon.Security.Cryptography.csproj", "{1B0BDF91-E7C7-4CB4-A39D-E1A5374C5602}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Security.Cryptography.Tests", "test\Cuemon.Security.Cryptography.Tests\Cuemon.Security.Cryptography.Tests.csproj", "{5D67081C-4458-41AA-A1F5-FAC974D29FDF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Xunit.Hosting", "src\Cuemon.Extensions.Xunit.Hosting\Cuemon.Extensions.Xunit.Hosting.csproj", "{1E0BDF91-E7C7-4CB4-A39D-E1A5374C5602}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Hosting", "src\Cuemon.Extensions.Hosting\Cuemon.Extensions.Hosting.csproj", "{1D0BDF91-E7C7-4CB4-A39D-E1A5374C5602}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Xunit.Tests", "test\Cuemon.Extensions.Xunit.Tests\Cuemon.Extensions.Xunit.Tests.csproj", "{2108E7E7-F002-481C-B17F-918E76D98378}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Xunit.Hosting.Tests", "test\Cuemon.Extensions.Xunit.Hosting.Tests\Cuemon.Extensions.Xunit.Hosting.Tests.csproj", "{86B43822-0733-416E-8DA2-666C5657974F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Runtime.Caching.Tests", "test\Cuemon.Runtime.Caching.Tests\Cuemon.Runtime.Caching.Tests.csproj", "{581174AB-62AA-4A04-85DE-4F9E307C9712}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Runtime.Caching", "src\Cuemon.Extensions.Runtime.Caching\Cuemon.Extensions.Runtime.Caching.csproj", "{1F0BDF91-E7C7-4CB4-A39D-E1A5374C5602}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Runtime.Caching.Tests", "test\Cuemon.Extensions.Runtime.Caching.Tests\Cuemon.Extensions.Runtime.Caching.Tests.csproj", "{0F614FD1-BC7C-4F7F-9847-D3675614576C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Newtonsoft.Json.Tests", "test\Cuemon.Extensions.Newtonsoft.Json.Tests\Cuemon.Extensions.Newtonsoft.Json.Tests.csproj", "{8A3E26BD-A3C4-4684-909B-1ABDFDB4108D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Data.SqlClient.Tests", "test\Cuemon.Data.SqlClient.Tests\Cuemon.Data.SqlClient.Tests.csproj", "{A9610C9E-1944-4771-A5E1-CE47ADA243D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Xunit.Hosting.AspNetCore", "src\Cuemon.Extensions.Xunit.Hosting.AspNetCore\Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj", "{200BDF91-E7C7-4CB4-A39D-E1A5374C5602}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests", "test\Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests\Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj", "{72422689-CDC3-4AD6-89D7-25F85545B0FE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc", "src\Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc\Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.csproj", "{210BDF91-E7C7-4CB4-A39D-E1A5374C5602}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cuemon.AspNetCore.Authentication.Tests", "test\Cuemon.AspNetCore.Authentication.Tests\Cuemon.AspNetCore.Authentication.Tests.csproj", "{D29D6F7E-8A76-48AD-A042-EFC83CD474A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cuemon.Extensions.AspNetCore.Authentication", "src\Cuemon.Extensions.AspNetCore.Authentication\Cuemon.Extensions.AspNetCore.Authentication.csproj", "{C5DB7806-EC4F-4D74-A9FE-A5B9A0625A41}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -307,6 +339,70 @@ Global {06559CB0-899C-4B48-AFB8-633CBF97A766}.Debug|Any CPU.Build.0 = Debug|Any CPU {06559CB0-899C-4B48-AFB8-633CBF97A766}.Release|Any CPU.ActiveCfg = Release|Any CPU {06559CB0-899C-4B48-AFB8-633CBF97A766}.Release|Any CPU.Build.0 = Release|Any CPU + {1B0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.Build.0 = Release|Any CPU + {5D67081C-4458-41AA-A1F5-FAC974D29FDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D67081C-4458-41AA-A1F5-FAC974D29FDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D67081C-4458-41AA-A1F5-FAC974D29FDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D67081C-4458-41AA-A1F5-FAC974D29FDF}.Release|Any CPU.Build.0 = Release|Any CPU + {1E0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.Build.0 = Release|Any CPU + {1D0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.Build.0 = Release|Any CPU + {2108E7E7-F002-481C-B17F-918E76D98378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2108E7E7-F002-481C-B17F-918E76D98378}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2108E7E7-F002-481C-B17F-918E76D98378}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2108E7E7-F002-481C-B17F-918E76D98378}.Release|Any CPU.Build.0 = Release|Any CPU + {86B43822-0733-416E-8DA2-666C5657974F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86B43822-0733-416E-8DA2-666C5657974F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86B43822-0733-416E-8DA2-666C5657974F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86B43822-0733-416E-8DA2-666C5657974F}.Release|Any CPU.Build.0 = Release|Any CPU + {581174AB-62AA-4A04-85DE-4F9E307C9712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {581174AB-62AA-4A04-85DE-4F9E307C9712}.Debug|Any CPU.Build.0 = Debug|Any CPU + {581174AB-62AA-4A04-85DE-4F9E307C9712}.Release|Any CPU.ActiveCfg = Release|Any CPU + {581174AB-62AA-4A04-85DE-4F9E307C9712}.Release|Any CPU.Build.0 = Release|Any CPU + {1F0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F0BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.Build.0 = Release|Any CPU + {0F614FD1-BC7C-4F7F-9847-D3675614576C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F614FD1-BC7C-4F7F-9847-D3675614576C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F614FD1-BC7C-4F7F-9847-D3675614576C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F614FD1-BC7C-4F7F-9847-D3675614576C}.Release|Any CPU.Build.0 = Release|Any CPU + {8A3E26BD-A3C4-4684-909B-1ABDFDB4108D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A3E26BD-A3C4-4684-909B-1ABDFDB4108D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A3E26BD-A3C4-4684-909B-1ABDFDB4108D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A3E26BD-A3C4-4684-909B-1ABDFDB4108D}.Release|Any CPU.Build.0 = Release|Any CPU + {A9610C9E-1944-4771-A5E1-CE47ADA243D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9610C9E-1944-4771-A5E1-CE47ADA243D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9610C9E-1944-4771-A5E1-CE47ADA243D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9610C9E-1944-4771-A5E1-CE47ADA243D5}.Release|Any CPU.Build.0 = Release|Any CPU + {200BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {200BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.Build.0 = Debug|Any CPU + {200BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.ActiveCfg = Release|Any CPU + {200BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.Build.0 = Release|Any CPU + {72422689-CDC3-4AD6-89D7-25F85545B0FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72422689-CDC3-4AD6-89D7-25F85545B0FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72422689-CDC3-4AD6-89D7-25F85545B0FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72422689-CDC3-4AD6-89D7-25F85545B0FE}.Release|Any CPU.Build.0 = Release|Any CPU + {210BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {210BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Debug|Any CPU.Build.0 = Debug|Any CPU + {210BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.ActiveCfg = Release|Any CPU + {210BDF91-E7C7-4CB4-A39D-E1A5374C5602}.Release|Any CPU.Build.0 = Release|Any CPU + {D29D6F7E-8A76-48AD-A042-EFC83CD474A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D29D6F7E-8A76-48AD-A042-EFC83CD474A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D29D6F7E-8A76-48AD-A042-EFC83CD474A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D29D6F7E-8A76-48AD-A042-EFC83CD474A5}.Release|Any CPU.Build.0 = Release|Any CPU + {C5DB7806-EC4F-4D74-A9FE-A5B9A0625A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5DB7806-EC4F-4D74-A9FE-A5B9A0625A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5DB7806-EC4F-4D74-A9FE-A5B9A0625A41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5DB7806-EC4F-4D74-A9FE-A5B9A0625A41}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -361,6 +457,22 @@ Global {190BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} {1A0BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} {06559CB0-899C-4B48-AFB8-633CBF97A766} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {1B0BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} + {5D67081C-4458-41AA-A1F5-FAC974D29FDF} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {1E0BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} + {1D0BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} + {2108E7E7-F002-481C-B17F-918E76D98378} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {86B43822-0733-416E-8DA2-666C5657974F} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {581174AB-62AA-4A04-85DE-4F9E307C9712} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {1F0BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} + {0F614FD1-BC7C-4F7F-9847-D3675614576C} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {8A3E26BD-A3C4-4684-909B-1ABDFDB4108D} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {A9610C9E-1944-4771-A5E1-CE47ADA243D5} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {200BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} + {72422689-CDC3-4AD6-89D7-25F85545B0FE} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {210BDF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} + {D29D6F7E-8A76-48AD-A042-EFC83CD474A5} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338} + {C5DB7806-EC4F-4D74-A9FE-A5B9A0625A41} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2A848386-B682-4F6D-8254-B5F6247C3054} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..ad1c4734f --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,74 @@ + + + $(MSBuildProjectName.EndsWith('Tests')) + + + + true + true + + + + ..\..\.sonarlint\cuemoncsharp.ruleset + + + + Copyright © Geekle 2009-2021. All rights reserved. + gimlichael + Geekle + Cuemon for .NET + https://nblcdn.net/cuemon.net/128x128.png + https://www.cuemon.net/ + MIT + https://github.com/gimlichael/Cuemon + git + en-US + true + true + true + snupkg + true + true + $(MSBuildThisFileDirectory)cuemon.snk + + + + + + + + + + + + + + + + + net5.0 + false + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..ff27971c6 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,23 @@ + + + + <_LocalTopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''"/> + + + + + $(MSBuildProjectDirectory)/Properties/PackageReleaseNotes.txt + + + + + + + + @(PackageReleaseNotesLines, '%0A') + + + \ No newline at end of file diff --git a/Dockerfile.docfx b/Dockerfile.docfx new file mode 100644 index 000000000..f99f06682 --- /dev/null +++ b/Dockerfile.docfx @@ -0,0 +1,47 @@ +# escape=` + +FROM nginx:1.19.7 AS base +RUN rm -rf /usr/share/nginx/html/* + +FROM mono:6.10.0.104 AS build +ARG DOCFX_VERSION=v2.56.6 + +ENV PATH ${PATH}:/opt/docfx +ENV DOCFX_SOURCE_BRANCH_NAME="development" + +# MONO IMAGE +RUN curl -sSL --output packages-microsoft-prod.deb https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb +RUN dpkg -i packages-microsoft-prod.deb +RUN apt-get update ` +&& apt-get install -y powershell zip apt-transport-https dirmngr gnupg ca-certificates git ` +&& mkdir -p /opt/docfx/ ` +&& curl -sSL --output docfx.zip "https://github.com/dotnet/docfx/releases/download/${DOCFX_VERSION}/docfx.zip" ` +&& unzip docfx.zip -d /opt/docfx/ ` +&& rm docfx.zip ` +&& echo '#!/bin/bash' >> /opt/docfx/docfx ` +&& echo 'exec mono /opt/docfx/docfx.exe $@' >> /opt/docfx/docfx ` +&& chmod +x /opt/docfx/docfx ` +&& apt-get -y purge unzip + +SHELL ["/opt/microsoft/powershell/7/pwsh", "-Command"] + +WORKDIR /build + +ADD ["docfx", "docfx"] +#ADD ["src", "src"] INCLUDE THIS WHEN DOCFX IS MATURED - SEE NOTE BELOW + +RUN cd docfx; ` +# docfx metadata; ` INCLUDE THIS WHEN DOCFX IS MATURED - SEE NOTE BELOW +docfx build + +# After carefull considerations, I have concluded that DoxFx is not a matured product to be run entirely as part of a Docker image. +# Reason is that for DocFx to resolve cref correctly (and assembly names) it requires the msbuild project file (csproj) and not only the source files (cs). +# As stated by docfx team themself: Visual Studio 2019 is needed for docfx metadata msbuild projects. +# Given that VS2019 is to hard a dependency, I have moved the docfx metadata taskto my local development environment. +# It would have been great to have all documentation done in a container - but for now this is not an option without significant drawbacks. + +FROM base AS final +WORKDIR /usr/share/nginx/html +COPY --from=build /build/docfx/wwwroot /usr/share/nginx/html + +ENTRYPOINT ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/README.md b/README.md index f4c1b6b5e..9d5cd658d 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,74 @@ -![Cuemon](https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png) +![Cuemon for .NET](https://nblcdn.net/cuemon/128x128.png) -Cuemon --------------------- -Cuemon is a free and flexible assembly package for the Microsoft .NET ecosystem. It was built to extend and boost your codebelt - providing vast ways of possibilities for all breeds of coders, programmers, developers and the likes thereof. Ideal for .NET, .NET Standard, .NET Core, Universal Windows Platform and .NET Framework 4.6.1 and newer. +# Cuemon for .NET -![License](https://img.shields.io/github/license/gimlichael/cuemoncore) +An open-source project (MIT license) that targets and complements the Microsoft .NET platform. It provides vast ways of possibilities for all breeds of coders, programmers, developers and the likes thereof. Ideal for .NET, .NET Standard, .NET Core, Universal Windows Platform and .NET Framework 4.6.1 and newer. -This development branch contains the latest version which has been completely refactored and updated to suport .NET Core 3.1. -An Azure DevOps pipeline is currently in progress. +It is, by heart, free, flexible and built to extend and boost your agile codebelt. -Once automated and tested thoroughly, it will be pushed to a new branch, release, and hereafter again tested and lastly to master and Nuget packages. +## State of the Union -[![Build Status](https://gimlichael.visualstudio.com/CuemonCore/_apis/build/status/CuemonCore%20-%20Development%20-%20CI?branchName=development)](https://gimlichael.visualstudio.com/CuemonCore/_build/latest?definitionId=1&branchName=development) +Cuemon for .NET (formerly Cuemon .NET Standard) has been completely refactored and updated to support .NET 5 while for the most part being compatible with .NET Standard 2.0, .NET Core 2.0 and .NET Core 3.0. -[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=bugs)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=code_smells)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=coverage)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=ncloc)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=security_rating)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=sqale_index)](https://sonarcloud.io/dashboard?id=CuemonCore) -[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=CuemonCore&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=CuemonCore) +Another big change for this upcoming release is the versioning; the developers has spoken and cast their love on semantic versioning. +The release for now is planned to be 6.0.0. -[![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=CuemonCore)](https://sonarcloud.io/dashboard?id=CuemonCore) +Check out the WIP documentation (generated by DocFx): https://docs.cuemon.net/ -Stay tuned! +All CI and CD integrations are done on [Microsoft Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) and is currently in the process of being tweaked. + +All code quality analysis are done by [SonarCloud](https://sonarcloud.io/) and [CodeCov.io](https://codecov.io/). + +Currently work is done on ironing out the kinks in relations to NuGet package description, release notes conventions, concepts and last minute refactorings to provide the best experience possible with RC-1! + +![License](https://img.shields.io/github/license/gimlichael/cuemon) [![Build Status](https://dev.azure.com/gimlichael/Cuemon/_apis/build/status/gimlichael.Cuemon?branchName=development)](https://dev.azure.com/gimlichael/Cuemon/_build/latest?definitionId=9&branchName=development) [![codecov](https://codecov.io/gh/gimlichael/Cuemon/branch/development/graph/badge.svg)](https://codecov.io/gh/gimlichael/Cuemon) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=coverage)](https://sonarcloud.io/dashboard?id=Cuemon) + + +## Development Branch + +The `development` branch contains the latest (and greatest) version of the code. + +To consume a CI build, create a `NuGet.Config` in your root solution directory and add following content: + +```xml + + + + + + + + + + +``` +Do note, that builds from development are preview builds and not to be considered stable. + +Once tested thoroughly and feature milestone has been reached, the code will be pushed and merged to a new branch; `release`. + +## Release Branch + +The `release` branch contains the next version of Cuemon for .NET. Here it will be tested again while the next semantic version is being determined. + +All CI builds are pushed to NuGet.org as either `alpha`, `beta` or `rc` releases. For more information, check out [Package versioning - Pre-release Versions](https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#pre-release-versions) at Microsoft. + +Lastly, when things are looking all fine and dandy, the code will be pushed and merged to the `master` branch. + +## Master Branch + +The `master` branch always contains the current `production` ready version of Cuemon for .NET. + +Builds performed from this repository are pushed to NuGet.org as the actual version they represent. Eg. the forthcoming version of Cuemon for .NET will be 6.0.0. + +### Code Quality Monitoring + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=alert_status)](https://sonarcloud.io/dashboard?id=Cuemon) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=Cuemon) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=Cuemon) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=security_rating)](https://sonarcloud.io/dashboard?id=Cuemon) + +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=ncloc)](https://sonarcloud.io/dashboard?id=Cuemon) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=code_smells)](https://sonarcloud.io/dashboard?id=Cuemon) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=sqale_index)](https://sonarcloud.io/dashboard?id=Cuemon) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=bugs)](https://sonarcloud.io/dashboard?id=Cuemon) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=Cuemon) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=Cuemon&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=Cuemon) + + + +### Links to NuGet packages (will be updated once Cuemon for .NET has shipped in 6.0.0) Useful links for this project (will soon be changed for the forthcoming release): diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..b75c1894d --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,231 @@ +trigger: +- development + +variables: + - group: Integration Test + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: true + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: BuildSource + value: 'src' + - name: BuildPlatform + value: 'Any CPU' + - name: BuildConfiguration + value: 'Release' + +jobs: +- job: CI + timeoutInMinutes: 120 + + strategy: + matrix: + Linux_Build_and_Test: + imageName: 'ubuntu-20.04' + Windows_Build_Test_and_Package: + imageName: 'windows-2019' + + pool: + vmImage: $(imageName) + + steps: + + - task: UseDotNet@2 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Use .Net Core SDK 2.2.207 (SonarCloud)' + inputs: + version: 2.2.207 + + - task: UseDotNet@2 + displayName: 'Use .Net Core SDK 5.0.103' + inputs: + version: 5.0.103 + + - task: DotNetCoreCLI@2 + displayName: 'Install NBGV tool' + inputs: + command: custom + custom: tool + arguments: 'install --global nbgv' + + - task: DotNetCoreCLI@2 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Install ReportGenerator tool' + inputs: + command: custom + custom: tool + arguments: install --global dotnet-reportgenerator-globaltool + + - script: 'nbgv cloud' + displayName: 'Set Version using NBGV' + + - task: DownloadSecureFile@1 + displayName: 'Download cuemon.snk' + inputs: + secureFile: 'cuemon.snk' + + - task: CopyFiles@2 + displayName: 'Copy cuemon.snk to $(System.DefaultWorkingDirectory)' + inputs: + SourceFolder: '$(Agent.TempDirectory)' + Contents: cuemon.snk + TargetFolder: '$(System.DefaultWorkingDirectory)' + + - task: DotNetCoreCLI@2 + displayName: Restore + inputs: + command: restore + projects: | + **/*.csproj + + - task: DockerCompose@0 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Spin up SQL Server for unit/integration test' + inputs: + containerregistrytype: 'Container Registry' + dockerComposeFile: '**/docker-compose.yml' + dockerComposeFileArgs: | + SA_PASSWORD=$(awsql-password) + dockerComposeCommand: "up -d" + action: 'Run a Docker Compose command' + + - task: SonarCloudPrepare@1 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Prepare Analysis on SonarCloud' + inputs: + SonarCloud: 'Cuemon-SonarCloud' + organization: 'geekle' + scannerMode: 'MSBuild' + projectKey: 'Cuemon' + projectName: 'Cuemon' + projectVersion: '$(GitAssemblyInformationalVersion)' + extraProperties: | + sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/**/*opencover.xml + sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/*.trx + + - task: DotNetCoreCLI@2 + displayName: 'Build net5.0 compatible Assemblies' + inputs: + command: 'build' + projects: | + src/**/*.csproj + arguments: '--configuration $(BuildConfiguration) --no-restore --framework net5.0' + workingDirectory: '$(BuildSource)' + + - task: DotNetCoreCLI@2 + displayName: 'Build netcoreapp3.1 compatible Assemblies' + inputs: + command: 'build' + projects: | + src/**/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj + src/**/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.csproj + arguments: '--configuration $(BuildConfiguration) --no-restore --framework netcoreapp3.1' + workingDirectory: '$(BuildSource)' + + - task: DotNetCoreCLI@2 + displayName: 'Build netcoreapp3.0 compatible Assemblies' + inputs: + command: 'build' + projects: | + src/**/Cuemon.AspNetCore*.csproj + src/**/Cuemon.Extensions.AspNetCore*.csproj + src/**/Cuemon.Extensions.Hosting.csproj + src/**/Cuemon.Extensions.Xunit.Hosting.csproj + arguments: '--configuration $(BuildConfiguration) --no-restore --framework netcoreapp3.0' + workingDirectory: '$(BuildSource)' + + - task: DotNetCoreCLI@2 + displayName: 'Build netstandard2.1 compatible Assemblies' + inputs: + command: 'build' + projects: | + src/**/Cuemon.Extensions.IO.csproj + src/**/Cuemon.IO.csproj + arguments: '--configuration $(BuildConfiguration) --no-restore --framework netstandard2.1' + workingDirectory: '$(BuildSource)' + + - task: DotNetCoreCLI@2 + displayName: 'Build netstandard2.0 compatible Assemblies' + inputs: + command: 'build' + projects: | + src/**/*.csproj + !src/**/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj + !src/**/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.csproj + arguments: '--configuration $(BuildConfiguration) --no-restore --framework netstandard2.0' + workingDirectory: '$(BuildSource)' + + - task: DotNetCoreCLI@2 + displayName: 'Test Solution - Linux' + condition: eq(variables['Agent.OS'], 'Linux') + inputs: + command: 'test' + projects: test/**/*.csproj + arguments: '--configuration $(BuildConfiguration) --collect:"XPlat Code Coverage" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover' + publishTestResults: true + env: + ConnectionStrings__AdventureWorks: $(ConnectionStrings--AdventureWorks) + + - task: DotNetCoreCLI@2 + displayName: 'Test Solution - Windows' + condition: eq(variables['Agent.OS'], 'Windows_NT') + inputs: + command: 'test' + projects: test/**/*.csproj + arguments: '--configuration $(BuildConfiguration) --collect:"XPlat Code Coverage" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --filter FullyQualifiedName!~SqlClient' + publishTestResults: true + + - script: reportgenerator "-reports:**/*.opencover.xml" "-targetdir:$(Build.SourcesDirectory)/Coverage" "-reporttypes:Cobertura;HTMLInline;HTMLChart" + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Create Code Coverage Reports' + + - task: PublishCodeCoverageResults@1 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Publish Code Coverage' + inputs: + codeCoverageTool: Cobertura + summaryFileLocation: '$(Build.SourcesDirectory)/Coverage/Cobertura.xml' + reportDirectory: '$(Build.SourcesDirectory)/Coverage' + + - bash: bash <(curl -s https://codecov.io/bash) + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Upload to codecov.io' + + - task: SonarCloudAnalyze@1 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Run Sonar Cloud Code Analysis' + + - task: SonarCloudPublish@1 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Publish Quality Gate Result to Sonar Cloud' + inputs: + pollingTimeoutSec: '300' + + - task: DockerCompose@0 + condition: eq(variables['Agent.OS'], 'Linux') + displayName: 'Take down SQL Server' + inputs: + containerregistrytype: 'Container Registry' + dockerComposeFile: '**/docker-compose.yml' + dockerComposeCommand: "down" + action: 'Run a Docker Compose command' + + - task: DotNetCoreCLI@2 + condition: eq(variables['Agent.OS'], 'Windows_NT') + displayName: dotnet pack + inputs: + command: pack + packagesToPack: src/**/*.csproj + nobuild: true + + - task: WhiteSource Bolt@20 + condition: eq(variables['Agent.OS'], 'Windows_NT') + displayName: 'WhiteSource Bolt' + + - task: PublishBuildArtifacts@1 + condition: eq(variables['Agent.OS'], 'Windows_NT') + displayName: 'Store NuGet Packages' + inputs: + PathtoPublish: $(Build.ArtifactStagingDirectory) + ArtifactName: Packages + publishLocation: Container \ No newline at end of file diff --git a/docfx/api/aspnet/ext/index.md b/docfx/api/aspnet/ext/index.md new file mode 100644 index 000000000..5939a2736 --- /dev/null +++ b/docfx/api/aspnet/ext/index.md @@ -0,0 +1,45 @@ +--- +uid: extensions-aspnet-md +title: Extensions for ASP.NET Core API Reference +--- +## Cuemon Extensions for ASP.NET Core API Reference + +The **Cuemon** assembly family provides both enhancements and extension methods to these namespaces of [Microsoft .NET Standard](https://docs.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0): + ++ System ++ System.Collections ++ System.Collections.Concurrent ++ System.Collections.Generic ++ System.Collections.ObjectModel ++ System.Collections.Specialized ++ System.ComponentModel ++ System.Configuration ++ System.Data ++ System.Data.Common ++ System.Diagnostics ++ System.Globalization ++ System.Linq ++ System.IO ++ System.Net.Httpp ++ System.Net.Http.Headers ++ System.Net.Mail ++ System.Reflection ++ System.Runtime.CompilerServices ++ System.Security.Cryptography ++ System.Text ++ System.Threading ++ System.Threading.Tasks ++ System.Xml ++ System.Xml.Serialization ++ System.Xml.XPath + +### Cuemon.Data +[NS-2.0-API](/api/cuemon/data/index.html) + +### Cuemon.Diagnostics +[NS-2.0-API](/api/cuemon/diagnostics/index.html) + +### Cuemon.Xml +[NS-2.0-API](/api/core/netstandard2.0/Cuemon.Xml.html) + +[![Build status](https://gimlichael.visualstudio.com/CuemonCore/_apis/build/status/CuemonCore%20-%20Development%20-%20CI)](https://gimlichael.visualstudio.com/CuemonCore/_build/latest?definitionId=1) \ No newline at end of file diff --git a/docfx/api/aspnet/index.md b/docfx/api/aspnet/index.md new file mode 100644 index 000000000..ef0528f00 --- /dev/null +++ b/docfx/api/aspnet/index.md @@ -0,0 +1,45 @@ +--- +uid: aspnet-md +title: ASP.NET Core API Reference +--- +## Cuemon ASP.NET Core API Reference + +The **Cuemon** assembly family provides both enhancements and extension methods to these namespaces of [Microsoft .NET Standard](https://docs.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0): + ++ System ++ System.Collections ++ System.Collections.Concurrent ++ System.Collections.Generic ++ System.Collections.ObjectModel ++ System.Collections.Specialized ++ System.ComponentModel ++ System.Configuration ++ System.Data ++ System.Data.Common ++ System.Diagnostics ++ System.Globalization ++ System.Linq ++ System.IO ++ System.Net.Httpp ++ System.Net.Http.Headers ++ System.Net.Mail ++ System.Reflection ++ System.Runtime.CompilerServices ++ System.Security.Cryptography ++ System.Text ++ System.Threading ++ System.Threading.Tasks ++ System.Xml ++ System.Xml.Serialization ++ System.Xml.XPath + +### Cuemon.Data +[NS-2.0-API](/api/cuemon/data/index.html) + +### Cuemon.Diagnostics +[NS-2.0-API](/api/cuemon/diagnostics/index.html) + +### Cuemon.Xml +[NS-2.0-API](/api/core/netstandard2.0/Cuemon.Xml.html) + +[![Build status](https://gimlichael.visualstudio.com/CuemonCore/_apis/build/status/CuemonCore%20-%20Development%20-%20CI)](https://gimlichael.visualstudio.com/CuemonCore/_build/latest?definitionId=1) \ No newline at end of file diff --git a/docfx/api/dotnet/ext/index.md b/docfx/api/dotnet/ext/index.md new file mode 100644 index 000000000..382653803 --- /dev/null +++ b/docfx/api/dotnet/ext/index.md @@ -0,0 +1,45 @@ +--- +uid: extensions-dotnet-md +title: Extensions for Core API Reference +--- +## Cuemon Extensions for Core API Reference + +The **Cuemon** assembly family provides both enhancements and extension methods to these namespaces of [Microsoft .NET Standard](https://docs.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0): + ++ System ++ System.Collections ++ System.Collections.Concurrent ++ System.Collections.Generic ++ System.Collections.ObjectModel ++ System.Collections.Specialized ++ System.ComponentModel ++ System.Configuration ++ System.Data ++ System.Data.Common ++ System.Diagnostics ++ System.Globalization ++ System.Linq ++ System.IO ++ System.Net.Httpp ++ System.Net.Http.Headers ++ System.Net.Mail ++ System.Reflection ++ System.Runtime.CompilerServices ++ System.Security.Cryptography ++ System.Text ++ System.Threading ++ System.Threading.Tasks ++ System.Xml ++ System.Xml.Serialization ++ System.Xml.XPath + +### Cuemon.Data +[NS-2.0-API](/api/cuemon/data/index.html) + +### Cuemon.Diagnostics +[NS-2.0-API](/api/cuemon/diagnostics/index.html) + +### Cuemon.Xml +[NS-2.0-API](/api/core/netstandard2.0/Cuemon.Xml.html) + +[![Build status](https://gimlichael.visualstudio.com/CuemonCore/_apis/build/status/CuemonCore%20-%20Development%20-%20CI)](https://gimlichael.visualstudio.com/CuemonCore/_build/latest?definitionId=1) \ No newline at end of file diff --git a/docfx/api/dotnet/index.md b/docfx/api/dotnet/index.md new file mode 100644 index 000000000..a6766c963 --- /dev/null +++ b/docfx/api/dotnet/index.md @@ -0,0 +1,45 @@ +--- +uid: dotnet-md +title: Core API Reference +--- +## Cuemon Core API Reference + +The **Cuemon** assembly family provides both enhancements and extension methods to these namespaces of [Microsoft .NET Standard](https://docs.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0): + ++ System ++ System.Collections ++ System.Collections.Concurrent ++ System.Collections.Generic ++ System.Collections.ObjectModel ++ System.Collections.Specialized ++ System.ComponentModel ++ System.Configuration ++ System.Data ++ System.Data.Common ++ System.Diagnostics ++ System.Globalization ++ System.Linq ++ System.IO ++ System.Net.Httpp ++ System.Net.Http.Headers ++ System.Net.Mail ++ System.Reflection ++ System.Runtime.CompilerServices ++ System.Security.Cryptography ++ System.Text ++ System.Threading ++ System.Threading.Tasks ++ System.Xml ++ System.Xml.Serialization ++ System.Xml.XPath + +### Cuemon.Data +[NS-2.0-API](/api/cuemon/data/index.html) + +### Cuemon.Diagnostics +[NS-2.0-API](/api/cuemon/diagnostics/index.html) + +### Cuemon.Xml +[NS-2.0-API](/api/core/netstandard2.0/Cuemon.Xml.html) + +[![Build status](https://gimlichael.visualstudio.com/CuemonCore/_apis/build/status/CuemonCore%20-%20Development%20-%20CI)](https://gimlichael.visualstudio.com/CuemonCore/_build/latest?definitionId=1) \ No newline at end of file diff --git a/docfx/api/index.md b/docfx/api/index.md new file mode 100644 index 000000000..7ff9326b1 --- /dev/null +++ b/docfx/api/index.md @@ -0,0 +1,45 @@ +--- +uid: concept-md +title: Cuemon Concept Reference +--- +## Cuemon Concept Reference + +The **Cuemon** assembly family provides both enhancements and extension methods to these namespaces of [Microsoft .NET Standard](https://docs.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0): + ++ System ++ System.Collections ++ System.Collections.Concurrent ++ System.Collections.Generic ++ System.Collections.ObjectModel ++ System.Collections.Specialized ++ System.ComponentModel ++ System.Configuration ++ System.Data ++ System.Data.Common ++ System.Diagnostics ++ System.Globalization ++ System.Linq ++ System.IO ++ System.Net.Httpp ++ System.Net.Http.Headers ++ System.Net.Mail ++ System.Reflection ++ System.Runtime.CompilerServices ++ System.Security.Cryptography ++ System.Text ++ System.Threading ++ System.Threading.Tasks ++ System.Xml ++ System.Xml.Serialization ++ System.Xml.XPath + +### Cuemon.Data +[NS-2.0-API](/api/cuemon/data/index.html) + +### Cuemon.Diagnostics +[NS-2.0-API](/api/cuemon/diagnostics/index.html) + +### Cuemon.Xml +[NS-2.0-API](/api/core/netstandard2.0/Cuemon.Xml.html) + +[![Build status](https://gimlichael.visualstudio.com/CuemonCore/_apis/build/status/CuemonCore%20-%20Development%20-%20CI)](https://gimlichael.visualstudio.com/CuemonCore/_build/latest?definitionId=1) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Basic.md b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Basic.md new file mode 100644 index 000000000..b94d60232 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Basic.md @@ -0,0 +1,16 @@ +--- +uid: Cuemon.AspNetCore.Authentication.Basic +summary: *content +--- +The Cuemon.AspNetCore.Authentication.Basic namespace contains types that enable support for Basic Authentication Scheme. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Authentication/Basic)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Authentication/Basic)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Authentication/Basic) + +NuGet packages 📦\ +[Cuemon.AspNetCore.Authentication (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Authentication)\ +[Cuemon.AspNetCore.Authentication (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Authentication) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Digest.md b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Digest.md new file mode 100644 index 000000000..46a052c31 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Digest.md @@ -0,0 +1,16 @@ +--- +uid: Cuemon.AspNetCore.Authentication.Digest +summary: *content +--- +The Cuemon.AspNetCore.Authentication.Digest namespace contains types that enable support for Digest Access Authentication Scheme. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Authentication/Digest)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Authentication/Digest)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Authentication/Digest) + +NuGet packages 📦\ +[Cuemon.AspNetCore.Authentication (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Authentication)\ +[Cuemon.AspNetCore.Authentication (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Authentication) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Hmac.md b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Hmac.md new file mode 100644 index 000000000..40c57b8c3 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.Hmac.md @@ -0,0 +1,16 @@ +--- +uid: Cuemon.AspNetCore.Authentication.Hmac +summary: *content +--- +The Cuemon.AspNetCore.Authentication.Hmac namespace contains types that enable support for HMAC Access Authentication Scheme. Inspired by AWS Signature Version 4 [Authenticating Requests: Using the Authorization Header](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html) and [Signing AWS requests with Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html). + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Authentication/Hmac)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Authentication/Hmac)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Authentication/Hmac) + +NuGet packages 📦\ +[Cuemon.AspNetCore.Authentication (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Authentication)\ +[Cuemon.AspNetCore.Authentication (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Authentication) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.md b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.md new file mode 100644 index 000000000..8c85671b9 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Authentication.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Authentication +summary: *content +--- +The Cuemon.AspNetCore.Authentication namespace contains types that enable support for authentication using the concept of an Authenticator, AuthorizationHeader and (to tie the knots) an AuthorizationHeaderBuilder. Basic-, Digest Access- and HMAC Authentication is provided out-of-the-box. The namespace is an addition to the Microsoft.AspNetCore.Authentication namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Authentication namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Authentication)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Authentication)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Authentication) + +NuGet packages 📦\ +[Cuemon.AspNetCore.Authentication (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Authentication)\ +[Cuemon.AspNetCore.Authentication (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Authentication) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Builder.md b/docfx/api/namespaces/Cuemon.AspNetCore.Builder.md new file mode 100644 index 000000000..091b508ee --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Builder.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Builder +summary: *content +--- +The Cuemon.AspNetCore.Builder namespace contains types that supports adding either middleware or configurable middleware types to the application request pipeline. The namespace is an addition to the Microsoft.AspNetCore.Builder namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Related: [Cuemon.Extensions.AspNetCore.Builder namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Builder.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Configuration.md b/docfx/api/namespaces/Cuemon.AspNetCore.Configuration.md new file mode 100644 index 000000000..b6b0f457a --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Configuration.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Configuration +summary: *content +--- +The Cuemon.AspNetCore.Configuration namespace contains types that provides a way to support a [cache busting strategy](https://www.keycdn.com/support/what-is-cache-busting). + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Related: [Cuemon.Extensions.AspNetCore.Mvc.Configuration namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Mvc.Configuration.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Diagnostics.md b/docfx/api/namespaces/Cuemon.AspNetCore.Diagnostics.md new file mode 100644 index 000000000..f475cbae3 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Diagnostics.md @@ -0,0 +1,22 @@ +--- +uid: Cuemon.AspNetCore.Diagnostics +summary: *content +--- +The Cuemon.AspNetCore.Diagnostics namespace contains types that provides a way to support the Server-Timing header for communicating metrics about the request-response cycle to an user agent. The namespace is an addition to the Microsoft.AspNetCore.Diagnostics namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Diagnostics namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.diagnostics?view=aspnetcore-2.0) 🔗 + +Related: [Cuemon.AspNetCore.Mvc.Filters.Diagnostics namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Mvc.Filters.Diagnostics.html) 📘 + +Related: [Cuemon.Extensions.AspNetCore.Diagnostics namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Diagnostics.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Hosting.md b/docfx/api/namespaces/Cuemon.AspNetCore.Hosting.md new file mode 100644 index 000000000..758f0417d --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Hosting.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Hosting +summary: *content +--- +The Cuemon.AspNetCore.Hosting namespace contains types that provides middleware for determining the hosting environment. The namespace is an addition to the Microsoft.AspNetCore.Hosting namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Hosting namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Http.Headers.md b/docfx/api/namespaces/Cuemon.AspNetCore.Http.Headers.md new file mode 100644 index 000000000..0ba680c00 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Http.Headers.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Http.Headers +summary: *content +--- +The Cuemon.AspNetCore.Http.Headers namespace contains types that provides a set of middleware components tied to HTTP headers. The namespace is an addition to the Microsoft.AspNetCore.Http.Headers namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Http.Headers namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.headers?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Http.Throttling.md b/docfx/api/namespaces/Cuemon.AspNetCore.Http.Throttling.md new file mode 100644 index 000000000..80253f3c3 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Http.Throttling.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Http.Throttling +summary: *content +--- +The Cuemon.AspNetCore.Http.Throttling namespace contains types that provides a middleware based throttling mechanism by specifying allowed quota and window duration of HTTP requests tied to a custom context (eg. IP-address, Authorization header, etc.). + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Related: [Cuemon.Extensions.AspNetCore.Http.Throttling namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Http.Throttling.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Http.md b/docfx/api/namespaces/Cuemon.AspNetCore.Http.md new file mode 100644 index 000000000..cafd6a7b8 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Http.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.AspNetCore.Http +summary: *content +--- +The Cuemon.AspNetCore.Http namespace contains types focusing on ways to provide developer friendly exception messages optimized for open- and otherwise public application programming interfaces (API). The namespace is an addition to the Microsoft.AspNetCore.Http namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Http namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http?view=aspnetcore-2.0) 🔗 + +Related: [Cuemon.Extensions.AspNetCore.Http namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Http.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Cacheable.md b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Cacheable.md new file mode 100644 index 000000000..1a2d8047e --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Cacheable.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.AspNetCore.Mvc.Filters.Cacheable +summary: *content +--- +The Cuemon.AspNetCore.Mvc.Filters.Cacheable namespace contains types that specializes in cache expiration and validation models. The namespace is an addition to the Microsoft.AspNetCore.Mvc.Filters namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.Filters namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters?view=aspnetcore-2.0) 🔗 + +Related: [Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Mvc)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Diagnostics.md b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Diagnostics.md new file mode 100644 index 000000000..f2540d5a1 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Diagnostics.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.AspNetCore.Mvc.Filters.Diagnostics +summary: *content +--- +The Cuemon.AspNetCore.Mvc.Diagnostics namespace contains types that provide ways for developers to describe exceptions including evidence to why an operation faulted optimized for open- and otherwise public application programming interfaces (API). The namespace is an addition to the Microsoft.AspNetCore.Mvc.Filters namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.Filters namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters?view=aspnetcore-2.0) 🔗 + +Related: [Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Mvc)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Headers.md b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Headers.md new file mode 100644 index 000000000..2d78e4fef --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Headers.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Mvc.Filters.Headers +summary: *content +--- +The Cuemon.AspNetCore.Mvc.Headers namespace contains types that provide filters explicitly written to different types of HTTP headers. The namespace is an addition to the Microsoft.AspNetCore.Mvc.Filters namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.Filters namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Mvc)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.ModelBinding.md b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.ModelBinding.md new file mode 100644 index 000000000..afa9abefd --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.ModelBinding.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Mvc.Filters.ModelBinding +summary: *content +--- +The Cuemon.AspNetCore.Mvc.ModelBinding namespace contains types that alters the built-in way of doing model binding. The namespace is an addition to the Microsoft.AspNetCore.Mvc.ModelBinding namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.ModelBinding namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelbinding?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Mvc)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Throttling.md b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Throttling.md new file mode 100644 index 000000000..4555e14cf --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.Throttling.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Mvc.Filters.Throttling +summary: *content +--- +The Cuemon.AspNetCore.Mvc.Filters.Throttling namespace contains types that provides filter based throttling mechanism by specifying allowed quota and window duration of HTTP requests tied to a custom context (eg. IP-address, Authorization header, etc.). The namespace is an addition to the Microsoft.AspNetCore.Mvc.Filters namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.Filters namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Mvc)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.md b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.md new file mode 100644 index 000000000..d7da0dd25 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.Filters.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Mvc.Filters +summary: *content +--- +The Cuemon.AspNetCore.Mvc.Filters namespace contains types that supports a generic way of working with built-in interfaces providing ready-to-use class abstractions. The namespace is an addition to the Microsoft.AspNetCore.Mvc.Filters namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.Filters namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Mvc)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.md b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.md new file mode 100644 index 000000000..90957ba0c --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Mvc.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.AspNetCore.Mvc +summary: *content +--- +The Cuemon.AspNetCore,Mvc namespace contains types that specializes in cache expiration and validation models and an abundant range of ready-to-use filters in the ASP.NET Core MVC pipeline. The namespace is an addition to the Microsoft.AspNetCore.Mvc namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc?view=aspnetcore-2.0) 🔗 + +Related: [Cuemon.Extensions.AspNetCore.Mvc namespace](https://docs.cuemon.net/api/aspnet/ext/Cuemon.Extensions.AspNetCore.Mvc.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Mvc)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.Razor.TagHelpers.md b/docfx/api/namespaces/Cuemon.AspNetCore.Razor.TagHelpers.md new file mode 100644 index 000000000..9da759522 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.Razor.TagHelpers.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore.Razor.TagHelpers +summary: *content +--- +The Cuemon.AspNetCore.Razor.TagHelpers namespace contains types tailored tag helper implementations. The namespace is an addition to the Microsoft.AspNetCore.Razor.TagHelpers namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Razor.TagHelpers namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.razor.taghelpers?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore.Razor.TagHelpers)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore.Razor.TagHelpers)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore.Razor.TagHelpers) + +NuGet packages 📦\ +[Cuemon.AspNetCore.Razor.TagHelpers (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore.Razor.TagHelpers)\ +[Cuemon.AspNetCore.Razor.TagHelpers (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore.Razor.TagHelpers) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.AspNetCore.md b/docfx/api/namespaces/Cuemon.AspNetCore.md new file mode 100644 index 000000000..ce34245bd --- /dev/null +++ b/docfx/api/namespaces/Cuemon.AspNetCore.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.AspNetCore +summary: *content +--- +The Cuemon.AspNetCore namespace contains types focusing on providing means for easier plumber coding in the ASP.NET Core pipeline while serving some concrete implementation of the shell as well. The namespace is an addition to the Microsoft.AspNetCore namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore?view=aspnetcore-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.AspNetCore) + +NuGet packages 📦\ +[Cuemon.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.AspNetCore)\ +[Cuemon.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Collections.Generic.md b/docfx/api/namespaces/Cuemon.Collections.Generic.md new file mode 100644 index 000000000..53a1b8fd2 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Collections.Generic.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.Collections.Generic +summary: *content +--- +The Cuemon.Collections.Generic namespace contains types that define generic collections that support paging, partitioning, dynamic comparers and some specialized collections such as a read-only enum dictionary and a generic, conditional collection. The namespace is an addition to the System.Collections.Generic namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Collections.Generic namespace](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic?view=netstandard-2.0) 🔗 + +Related: [Cuemon.Extensions.Collections.Generic namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Collections.Generic.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Collections/Generic)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Collections/Generic)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Collections/Generic) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core/) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Collections.md b/docfx/api/namespaces/Cuemon.Collections.md new file mode 100644 index 000000000..80446ab84 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Collections.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Collections +summary: *content +--- +The Cuemon.Collections namespace contains types that define various collections of objects. The namespace is an addition to the System.Collections namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Collections namespace](https://docs.microsoft.com/en-us/dotnet/api/system.collections?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Collections)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Collections)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Collections) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core/) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Configuration.md b/docfx/api/namespaces/Cuemon.Configuration.md new file mode 100644 index 000000000..4e90c033c --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Configuration.md @@ -0,0 +1,16 @@ +--- +uid: Cuemon.Configuration +summary: *content +--- +The Cuemon.Configuration namespace contains types focusing on writing configurable classes to help suport adhering to Separation of Concerns (SoC). + +Availability: NET Standard 2.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Configuration)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Configuration)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Configuration) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core/) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Data.Integrity.md b/docfx/api/namespaces/Cuemon.Data.Integrity.md new file mode 100644 index 000000000..467c6acb3 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Data.Integrity.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Data.Integrity +summary: *content +--- +The Cuemon.Data.Integrity namespace contains types that provide ways for developers to determine and maintain integrity of data that is normally associated with an entity/resource. + +Availability: NET Standard 2.0, .NET 5.0 + +Related: [Cuemon.Extensions.Data.Integrity namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Data.Integrity.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Data.Integrity)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Data.Integrity)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Data.Integrity) + +NuGet packages 📦\ +[Cuemon.Data.Integrity (CI)](https://nuget.cuemon.net/packages/Cuemon.Data.Integrity)\ +[Cuemon.Data.Integrity (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Data.Integrity) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Data.SqlClient.md b/docfx/api/namespaces/Cuemon.Data.SqlClient.md new file mode 100644 index 000000000..cb9219e5e --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Data.SqlClient.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Data.SqlClient +summary: *content +--- +The Cuemon.Data.SqlClient namespace contains types that provide ways for developers to work with Microsoft SQL Server integrations. The namespace is an addition to the System.Data.SqlClient namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Data.SqlClient namespace](https://docs.microsoft.com/en-us/dotnet/api/system.Data.SqlClient?view=netframework-4.8) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Data.SqlClient)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Data.SqlClient)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Data.SqlClient) + +NuGet packages 📦\ +[Cuemon.Data.SqlClient (CI)](https://nuget.cuemon.net/packages/Cuemon.Data.SqlClient)\ +[Cuemon.Data.SqlClient (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Data.SqlClient) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Data.Xml.md b/docfx/api/namespaces/Cuemon.Data.Xml.md new file mode 100644 index 000000000..ed7166a59 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Data.Xml.md @@ -0,0 +1,16 @@ +--- +uid: Cuemon.Data.Xml +summary: *content +--- +The Cuemon.Data.Xml namespace contains an implementation of the DataReader class that provides a way of reading a forward-only stream of rows from an XML based data source. + +Availability: NET Standard 2.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Data/Xml)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Data/Xml)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Data/Xml) + +NuGet packages 📦\ +[Cuemon.Data.SqlClient (CI)](https://nuget.cuemon.net/packages/Cuemon.Data)\ +[Cuemon.Data.SqlClient (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Data) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Data.md b/docfx/api/namespaces/Cuemon.Data.md new file mode 100644 index 000000000..14fb5a086 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Data.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Data +summary: *content +--- +The Cuemon.Data namespace contains types that provide ways to connect, build and manipulate different data sources. The namespace is an addition to the System.Data namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Data namespace](https://docs.microsoft.com/en-us/dotnet/api/system.Data?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Data)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Data)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Data) + +NuGet packages 📦\ +[Cuemon.Data (CI)](https://nuget.cuemon.net/packages/Cuemon.Data)\ +[Cuemon.Data (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Data) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Diagnostics.md b/docfx/api/namespaces/Cuemon.Diagnostics.md new file mode 100644 index 000000000..bcc81fe8a --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Diagnostics.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.Diagnostics +summary: *content +--- +The Cuemon.Diagnostics namespace contains types that provide ways for developers to describe exceptions including evidence to why an operation faulted. Also includes a flexible, generic and lambda friendly way to perform both synchronous and asynchronous time measuring operations. The namespace is an addition to the System.Diagnostics namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Diagnostics namespace](https://docs.microsoft.com/en-us/dotnet/api/system.Diagnostics?view=netstandard-2.0) 🔗 + +Related: [Cuemon.Extensions.Diagnostics namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Diagnostics.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Diagnostics)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Diagnostics)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Diagnostics) + +NuGet packages 📦\ +[Cuemon.Diagnostics (CI)](https://nuget.cuemon.net/packages/Cuemon.Diagnostics)\ +[Cuemon.Diagnostics (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Diagnostics) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Builder.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Builder.md new file mode 100644 index 000000000..a07aca809 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Builder.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Builder +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Builder namespace contains extension methods that complements the Cuemon.AspNetCore.Builder namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore.Builder namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Builder.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore/Builder)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore/Builder)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore/Builder) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore)\ +[Cuemon.Extensions.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IApplicationBuilder|⬇️|`UseHostingEnvironment`, `UseCorrelationIdentifier`, `UseRequestIdentifier`, `UseUserAgentSentinel`, `UseThrottlingSentinel`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Data.Integrity.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Data.Integrity.md new file mode 100644 index 000000000..d00b22675 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Data.Integrity.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Data.Integrity +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Data.Integrity namespace contains extension methods that complements the Cuemon.Data.Integrity namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.Data.Integrity namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Data.Integrity.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore/Data/Integrity)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore/Data/Integrity)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore/Data/Integrity) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore)\ +[Cuemon.Extensions.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|CacheValidator|⬇️|`ToEntityTag`| +|ChecksumBuilder|⬇️|`ToEntityTagHeaderValue`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Http.Throttling.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Http.Throttling.md new file mode 100644 index 000000000..79dce2238 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Http.Throttling.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Http.Throttling +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Http.Throttling namespace contains both types and extension methods that complements the Cuemon.AspNetCore.Http.Throttling namespace. Provides an in-memory implementation of a throttling cache for ASP.NET Core. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore.Http.Throttling namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Http.Throttling.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore/Http/Throttling)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore/Http/Throttling)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore/Http/Throttling) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore)\ +[Cuemon.Extensions.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddThrottlingCache{T}`, `AddMemoryThrottlingCache`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Http.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Http.md new file mode 100644 index 000000000..3aaa10035 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Http.md @@ -0,0 +1,27 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Http +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Http namespace contains extension methods that complements the Cuemon.AspNetCore.Http namespace while being an addition to the Microsoft.AspNetCore.Http namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore.Http namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Http.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore/Http)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore/Http)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore/Http) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore)\ +[Cuemon.Extensions.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IHeaderDictionary|⬇️|`AddOrUpdateHeaders`, `AddOrUpdateHeader`| +|HttpRequest|⬇️|`IsGetOrHeadMethod`, `IsClientSideResourceCached`| +|HttpResponse|⬇️|`AddOrUpdateEntityTagHeader`, `AddOrUpdateLastModifiedHeader`, `WriteBodyAsync`, `OnStartingInvokeTransformer`| +|Int32|⬇️|`IsInformationStatusCode`, `IsSuccessStatusCode`, `IsRedirectionStatusCode`, `IsNotModifiedStatusCode`, `IsClientErrorStatusCode`, `IsServerErrorStatusCode`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Configuration.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Configuration.md new file mode 100644 index 000000000..bb5ae5edf --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Configuration.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Configuration +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Configuration namespace contains both types and extension methods that complements the Cuemon.AspNetCore.Configuration namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides a set of different cache busting strategies for ASP.NET Core MVC that can be easily customized. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore.Configuration namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Configuration.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc)\ +[Cuemon.Extensions.AspNetCore.Mvc (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`AddCacheBusting{T}`, `AddAssemblyCacheBusting`, `AddDynamicCacheBusting`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable.md new file mode 100644 index 000000000..920b9a385 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable namespace contains extension methods that complements the Cuemon.AspNetCore.Mvc.Filters.Cacheable namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore.Mvc.Filters.Cacheable namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Mvc.Filters.Cacheable.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc)\ +[Cuemon.Extensions.AspNetCore.Mvc (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|ICacheableAsyncResultFilter|⬇️|`AddFilter{T, TOptions}`, `InsertFilter{T, TOptions}`, `AddEntityTagHeader`, `AddLastModifiedHeader`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics.md new file mode 100644 index 000000000..053b1b75b --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics namespace contains extension methods that complements the Cuemon.AspNetCore.Mvc.Filters.Diagnostics namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore.Mvc.Filters.Diagnostics namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Mvc.Filters.Diagnostics.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc)\ +[Cuemon.Extensions.AspNetCore.Mvc (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|FaultResolver|⬇️|`Add{T}`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Converters.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Converters.md new file mode 100644 index 000000000..aae9bf089 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Converters.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Converters +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Converters namespace contains extension methods that complements the Cuemon.Extensions.Newtonsoft.Json.Converters namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.Extensions.Newtonsoft.Json.Converters namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Newtonsoft.Json.Converters.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|JsonConverter|⬇️|`AddHttpExceptionDescriptorConverter`, `AddStringValuesConverter`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.md new file mode 100644 index 000000000..d6eb73be4 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json namespace contains both types and extension methods that complements the Cuemon.Extensions.Newtonsoft.Json namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides JSON formatters for ASP.NET Core that is powered by Newtonsoft.Json. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.Extensions.Newtonsoft.Json namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Newtonsoft.Json.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json)\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IMvcBuilder|⬇️|`AddJsonSerializationFormatters`, `AddJsonFormatterOptions`| +|IMvcCoreBuilder|⬇️|`AddJsonSerializationFormatters`, `AddJsonFormatterOptions`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters.md new file mode 100644 index 000000000..e1b7993fe --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters namespace contains extension methods that complements the Cuemon.Extensions.Xml.Serialization.Converters namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.Extensions.Xml.Serialization.Converters namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Xml.Serialization.Converters.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|XmlConverter|⬇️|`AddHttpExceptionDescriptorConverter`, `AddStringValuesConverter`, `AddHeaderDictionaryConverter`, `AddQueryCollectionConverter`, `AddFormCollectionConverter`, `AddCookieCollectionConverter`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.md new file mode 100644 index 000000000..5690aef9b --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml namespace contains both types and extension methods that complements the Cuemon.Extensions.Xml namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides XML formatters for ASP.NET Core that offers same flexibility as the one provided by the JSON equivalent from Newtonsoft. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.Extensions.Xml namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Xml.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml)\ +[Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IMvcBuilder|⬇️|`AddXmlSerializationFormatters`, `AddXmlFormatterOptions`| +|IMvcCoreBuilder|⬇️|`AddXmlSerializationFormatters`, `AddXmlFormatterOptions`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Rendering.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Rendering.md new file mode 100644 index 000000000..dfe3b5b93 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.Rendering.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc.Rendering +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc.Rendering namespace contains extension methods that complements the Microsoft.AspNetCore.Mvc.Rendering namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.Rendering namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.rendering?view=aspnetcore-3.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc)\ +[Cuemon.Extensions.AspNetCore.Mvc (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IHtmlHelper|⬇️|`UseWhen`, `UseWhen{T}`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.md new file mode 100644 index 000000000..88db4bc3f --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.Mvc.md @@ -0,0 +1,26 @@ +--- +uid: Cuemon.Extensions.AspNetCore.Mvc +summary: *content +--- +The Cuemon.Extensions.AspNetCore.Mvc namespace contains both types and extension methods that complements the Cuemon.AspNetCore.Mvc namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides a set of different cache busting strategies for ASP.NET Core MVC that can be easily customized. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore.Mvc namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.Mvc.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore.Mvc (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore.Mvc)\ +[Cuemon.Extensions.AspNetCore.Mvc (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore.Mvc) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IApplicationBuilder|⬇️|`UseWhen`| +|Object|⬇️|`MakeCacheable`, `MakeCacheable{T}`| +|ViewDataDictionary|⬇️|`AddBreadcrumbs{T}`, `GetBreadcrumbs`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.md b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.md new file mode 100644 index 000000000..f2f4054e0 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.AspNetCore.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Extensions.AspNetCore +summary: *content +--- +The Cuemon.Extensions.AspNetCore namespace contains both types and extension methods that complements the Cuemon.AspNetCore namespace while being an addition to the Microsoft.AspNetCore namespace. Provides an in-memory implementation of a throttling cache for ASP.NET Core. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Cuemon.AspNetCore namespace](https://docs.cuemon.net/api/aspnet/Cuemon.AspNetCore.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.AspNetCore) + +NuGet packages 📦\ +[Cuemon.Extensions.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.AspNetCore)\ +[Cuemon.Extensions.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Collections.Generic.md b/docfx/api/namespaces/Cuemon.Extensions.Collections.Generic.md new file mode 100644 index 000000000..6df1d3cc2 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Collections.Generic.md @@ -0,0 +1,27 @@ +--- +uid: Cuemon.Extensions.Collections.Generic +summary: *content +--- +The Cuemon.Extensions.Collections.Specialized namespace contains extension methods that complements the Cuemon.Collections.Generic namespace while being an addition to the System.Collections.Specialized namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Collections.Specialized namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Collections.Generic.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Collections.Generic)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Collections.Generic)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Collections.Generic) + +NuGet packages 📦\ +[Cuemon.Extensions.Collections.Generic (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Collections.Generic)\ +[Cuemon.Extensions.Collections.Generic (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Collections.Generic) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|ICollection{T}|⬇️|`ToPartitioner{T}`, `AddRange{T}`| +|IDictionary{TKey, TValue}|⬇️|`GetValueOrDefault{TKey, TValue}`, `TryGetValueOrFallback{TKey, TValue}`, `ToEnumerable{TKey, TValue}`, `TryAdd{TKey, TValue}`, `TryAddOrUpdate{TKey, TValue}`| +|IEnumerable{T}|⬇️|`Chunk{T}`, `Shuffle{T}`, `OrderBy{T}`, `OrderByDescending{T}`, `RandomOrDefault{T}`, `Yield{T}`, `ToDictionary{TKey, TValue}`, `ToPartitioner{T}`, `ToPagedCollection{T}`| +|IList{T}|⬇️|`Remove{T}`, `HasIndex{T}`, `Next{T}`, `Previous{T}`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Collections.Specialized.md b/docfx/api/namespaces/Cuemon.Extensions.Collections.Specialized.md new file mode 100644 index 000000000..ada5764da --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Collections.Specialized.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.Collections.Specialized +summary: *content +--- +The Cuemon.Extensions.Collections.Specialized namespace contains extension methods that complements the Cuemon.Collections.Specialized namespace while being an addition to the System.Collections.Specialized namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Collections.Specialized namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Collections.Specialized.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Collections.Specialized)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Collections.Specialized)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Collections.Specialized) + +NuGet packages 📦\ +[Cuemon.Extensions.Collections.Specialized (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Collections.Specialized)\ +[Cuemon.Extensions.Collections.Specialized (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Collections.Specialized) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IDictionary{string, string[]}|⬇️|`ToNameValueCollection`| +|NameValueCollection|⬇️|`ContainsKey`, `ToDictionary`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Data.Integrity.md b/docfx/api/namespaces/Cuemon.Extensions.Data.Integrity.md new file mode 100644 index 000000000..b922d8574 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Data.Integrity.md @@ -0,0 +1,27 @@ +--- +uid: Cuemon.Extensions.Data.Integrity +summary: *content +--- +The Cuemon.Extensions.Data.Integrity namespace contains extension methods that complements the Cuemon.Data.Integrity namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Data.Integrity namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Data.Integrity.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Data.Integrity)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Data.Integrity)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Data.Integrity) + +NuGet packages 📦\ +[Cuemon.Extensions.Data.Integrity (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Data.Integrity)\ +[Cuemon.Extensions.Data.Integrity (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Data.Integrity) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|Assembly|⬇️|`GetCacheValidator`| +|ChecksumBuilder|⬇️|`CombineWith{T}`| +|DateTime|⬇️|`GetCacheValidator`| +|FileInfo|⬇️|`GetCacheValidator`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Data.md b/docfx/api/namespaces/Cuemon.Extensions.Data.md new file mode 100644 index 000000000..dd4aacd56 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Data.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.Data +summary: *content +--- +The Cuemon.Extensions.Data namespace contains extension methods that complements the Cuemon.Data namespace while being an addition to the System.Data namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Data namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Data.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Data)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Data)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Data) + +NuGet packages 📦\ +[Cuemon.Extensions.Data (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Data)\ +[Cuemon.Extensions.Data (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Data) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IDataReader|⬇️|`ToColumns`, `ToRows`| +|QueryFormat|⬇️|`Embed`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.DependencyInjection.md b/docfx/api/namespaces/Cuemon.Extensions.DependencyInjection.md new file mode 100644 index 000000000..51d88c2e7 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.DependencyInjection.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.DependencyInjection +summary: *content +--- +The Cuemon.Extensions.DependencyInjection namespace contains extension methods that complements the Microsoft.Extensions.DependencyInjection namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Microsoft.Extensions.DependencyInjection namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection?view=dotnet-plat-ext-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.DependencyInjection)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.DependencyInjection)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.DependencyInjection) + +NuGet packages 📦\ +[Cuemon.Extensions.DependencyInjection (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.DependencyInjection)\ +[Cuemon.Extensions.DependencyInjection (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.DependencyInjection) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IServiceCollection|⬇️|`Add`, `Add{TOptions}`, `Add{TService, TImplementation}`, `Add{TService, TImplementation, TOptions}`, `TryAdd`, `TryAdd{TOptions}`, `TryAdd{TService, TImplementation}`, `TryAdd{TService, TImplementation, TOptions}`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Diagnostics.md b/docfx/api/namespaces/Cuemon.Extensions.Diagnostics.md new file mode 100644 index 000000000..e178394b8 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Diagnostics.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.Diagnostics +summary: *content +--- +The Cuemon.Extensions.Diagnostics namespace contains extension methods that complements the Cuemon.Diagnostics namespace while being an addition to the System.Diagnostics namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Diagnostics namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Diagnostics.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Diagnostics)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Diagnostics)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Diagnostics) + +NuGet packages 📦\ +[Cuemon.Extensions.Diagnostics (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Diagnostics)\ +[Cuemon.Extensions.Diagnostics (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Diagnostics) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|ExceptionDescriptor|⬇️|`ToInsightsString`| +|FileVersionInfo|⬇️|`ToProductVersion`, `ToFileVersion`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Hosting.md b/docfx/api/namespaces/Cuemon.Extensions.Hosting.md new file mode 100644 index 000000000..9d4e4003b --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Hosting.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.Hosting +summary: *content +--- +The Cuemon.Extensions.Hosting namespace contains extension methods and features related to the Microsoft.Extensions.Hosting namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [Microsoft.Extensions.Hosting namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting?view=dotnet-plat-ext-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Hosting)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Hosting)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Hosting) + +NuGet packages 📦\ +[Cuemon.Extensions.Hosting (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Hosting)\ +[Cuemon.Extensions.Hosting (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Hosting) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IHostEnvironment|⬇️|`IsLocalDevelopment`, `IsNonProduction`| +|IHostingEnvironment|⬇️|`IsLocalDevelopment`, `IsNonProduction`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.IO.md b/docfx/api/namespaces/Cuemon.Extensions.IO.md new file mode 100644 index 000000000..28efe6242 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.IO.md @@ -0,0 +1,27 @@ +--- +uid: Cuemon.Extensions.IO +summary: *content +--- +The Cuemon.Extensions.IO namespace contains extension methods that complements the Cuemon.IO namespace while being an addition to the System.IO namespace. + +Availability: NET Standard 2.0, NET Standard 2.1, .NET 5.0 + +Complements: [Cuemon.IO namespace](https://docs.cuemon.net/api/dotnet/Cuemon.IO.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.IO)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.IO)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.IO) + +NuGet packages 📦\ +[Cuemon.Extensions.IO (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.IO)\ +[Cuemon.Extensions.IO (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.IO) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|byte[]|⬇️|`ToStream`, `ToStreamAsync`| +|Stream|⬇️|`Concat`, `ToCharArray`, `ToByteArray`, `ToByteArrayAsync`, `WriteAsync`, `TryDetectUnicodeEncoding`, `ToEncodedString`, `ToEncodedStringAsync`, `CompressBrotli`, `CompressBrotliAsync`, `CompressDeflate`, `CompressDeflateAsync`, `CompressGZip`, `CompressGZipAsync`, `DecompressBrotli`, `DecompressBrotliAsync`, `DecompressDeflate`, `DecompressDeflateAsync`, `DecompressGZip`, `DecompressGZipAsync`| +|String|⬇️|`ToStream`, `ToStreamAsync`, `ToTextReader`| +|TextReader|⬇️|`CopyToAsync`, `ReadAllLines`, `ReadAllLinesAsync`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Net.Http.md b/docfx/api/namespaces/Cuemon.Extensions.Net.Http.md new file mode 100644 index 000000000..71c1e61c1 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Net.Http.md @@ -0,0 +1,23 @@ +--- +uid: Cuemon.Extensions.Net.Http +summary: *content +--- +The Cuemon.Extensions.Net.Http namespace contains both types and extension methods that complements the Cuemon.Net namespace. Includes support for both traditional and factory based ways of working with HttpMangager instances while also including a simple and lightweight implementation of the IHttpClientFactory interface named SlimHttpClientFactory (that provides "managed" HttpClient instances). + +Availability: NET Standard 2.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Net/Http)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Net/Http)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Net/Http) + +NuGet packages 📦\ +[Cuemon.Extensions.Net (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Net)\ +[Cuemon.Extensions.Net (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Net) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|HttpMethod|⬇️|`ToHttpMethod`| +|Uri|⬇️|`HttpDeleteAsync`, `HttpGetAsync`, `HttpHeadAsync`, `HttpOptionsAsync`, `HttpPostAsync`, `HttpPutAsync`, `HttpPatchAsync`, `HttpTraceAsync`, `HttpAsync`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Net.Security.md b/docfx/api/namespaces/Cuemon.Extensions.Net.Security.md new file mode 100644 index 000000000..cbe162418 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Net.Security.md @@ -0,0 +1,23 @@ +--- +uid: Cuemon.Extensions.Net.Security +summary: *content +--- +The Cuemon.Extensions.Net.Security namespace contains extension methods that provides a generic way to make a Uniform Resource Identifier signed and tampering protected. This could be used to make your own lightweight concept of a Azure shared access signatures (SAS). Originally part of Cuemon .NET Framework: https://github.com/gimlichael/CuemonNetFramework/blob/master/Cuemon.Web/Security/WebSecurityUtility.cs. Greatly simplified anno 2020. + +Availability: NET Standard 2.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Net/Security)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Net/Security)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Net/Security) + +NuGet packages 📦\ +[Cuemon.Extensions.Net (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Net)\ +[Cuemon.Extensions.Net (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Net) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|String|⬇️|`ToSignedUri`, `ValidateSignedUri`| +|Uri|⬇️|`ToSignedUri`, `ValidateSignedUri`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Net.md b/docfx/api/namespaces/Cuemon.Extensions.Net.md new file mode 100644 index 000000000..e97df1d87 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Net.md @@ -0,0 +1,28 @@ +--- +uid: Cuemon.Extensions.Net +summary: *content +--- +The Cuemon.Extensions.Net namespace contains both types and extension methods that complements the Cuemon.Net namespace while being an addition to the System.Net namespace. Includes support for both traditional and factory based ways of working with HttpMangager instances while also including a simple and lightweight implementation of the IHttpClientFactory interface named SlimHttpClientFactory (that provides "managed" HttpClient instances). + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Net namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Net.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Net)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Net)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Net) + +NuGet packages 📦\ +[Cuemon.Extensions.Net (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Net)\ +[Cuemon.Extensions.Net (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Net) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|byte[]|⬇️|`UrlEncode`| +|IDictionary{string, string[]}|⬇️|`ToQueryString`| +|HttpStatusCode|⬇️|`IsInformationStatusCode`, `IsSuccessStatusCode`, `IsRedirectionStatusCode`, `IsClientErrorStatusCode`, `IsServerErrorStatusCode`| +|NameValueCollection|⬇️|`ToQueryString`| +|String|⬇️|`UrlDecode`, `UrlEncode`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Converters.md b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Converters.md new file mode 100644 index 000000000..a7bbb93c7 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Converters.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.Newtonsoft.Json.Converters +summary: *content +--- +The Cuemon.Extensions.Newtonsoft.Json.Converters namespace contains both types and extension methods that complements the Newtonsoft.Json.Converters namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Newtonsoft.Json.Converters namespace](https://www.newtonsoft.com/json/help/html/N_Newtonsoft_Json_Converters.htm) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Newtonsoft.Json/Converters)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Newtonsoft.Json/Converters)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Newtonsoft.Json/Converters) + +NuGet packages 📦\ +[Cuemon.Extensions.Newtonsoft.Json (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Newtonsoft.Json)\ +[Cuemon.Extensions.Newtonsoft.Json (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Newtonsoft.Json) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|JsonConverter|⬇️|`AddStringEnumConverter`, `AddStringFlagsEnumConverter`, `AddExceptionDescriptorConverter`, `AddTimeSpanConverter`, `AddExceptionConverter`, `AddDataPairConverter`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Diagnostics.md b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Diagnostics.md new file mode 100644 index 000000000..4e6a5b48f --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Diagnostics.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.Newtonsoft.Json.Diagnostics +summary: *content +--- +The Cuemon.Extensions.Newtonsoft.Json.Diagnostics namespace contains extension methods that complements the Cuemon.Diagnostics namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Diagnostics namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Diagnostics.html) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Newtonsoft.Json/Diagnostics)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Newtonsoft.Json/Diagnostics)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Newtonsoft.Json/Diagnostics) + +NuGet packages 📦\ +[Cuemon.Extensions.Newtonsoft.Json (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Newtonsoft.Json)\ +[Cuemon.Extensions.Newtonsoft.Json (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Newtonsoft.Json) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|ExceptionDescriptor|⬇️|`ToInsightsJsonString`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Formatters.md b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Formatters.md new file mode 100644 index 000000000..bff68a885 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.Formatters.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Extensions.Newtonsoft.Json.Formatters +summary: *content +--- +The Cuemon.Extensions.Newtonsoft.Json.Formatters namespace contains types that are used to serialize and deserialize objects into and from JSON format using a generic signature. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Newtonsoft.Json.Serialization namespace](https://www.newtonsoft.com/json/help/html/N_Newtonsoft_Json_Serialization.htm) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Newtonsoft.Json/Formatters)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Newtonsoft.Json/Formatters)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Newtonsoft.Json/Formatters) + +NuGet packages 📦\ +[Cuemon.Extensions.Newtonsoft.Json (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Newtonsoft.Json)\ +[Cuemon.Extensions.Newtonsoft.Json (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Newtonsoft.Json) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.md b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.md new file mode 100644 index 000000000..7ef6e033e --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Newtonsoft.Json.md @@ -0,0 +1,28 @@ +--- +uid: Cuemon.Extensions.Newtonsoft.Json +summary: *content +--- +The Cuemon.Extensions.Newtonsoft.Json namespace contains both types and extension methods that complements the Newtonsoft.Json namespace by adding new ways of working with JSON; both in terms of serialization and parsing. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Newtonsoft.Json namespace](https://www.newtonsoft.com/json/help/html/N_Newtonsoft_Json.htm) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Newtonsoft.Json)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Newtonsoft.Json)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Newtonsoft.Json) + +NuGet packages 📦\ +[Cuemon.Extensions.Newtonsoft.Json (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Newtonsoft.Json)\ +[Cuemon.Extensions.Newtonsoft.Json (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Newtonsoft.Json) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|JDataResult|⬇️|`Flatten`, `ExtractArrayValues`, `ExtractObjectValues`| +|JsonReader|⬇️|`ToHierarchy`| +|JsonSerializerSettings|⬇️|`ApplyToDefaultSettings`, `UseCamelCase`| +|JsonWriter|⬇️|`WriteObject`, `WritePropertyName`| +|Validator|⬇️|`InvalidJsonDocument`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Reflection.md b/docfx/api/namespaces/Cuemon.Extensions.Reflection.md new file mode 100644 index 000000000..afb29fef8 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Reflection.md @@ -0,0 +1,27 @@ +--- +uid: Cuemon.Extensions.Reflection +summary: *content +--- +The Cuemon.Extensions.Reflection namespace contains extension methods that complements the Cuemon.Reflection namespace while being an addition to the System.Reflection namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Reflection namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Reflection.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Reflection)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Reflection)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Reflection) + +NuGet packages 📦\ +[Cuemon.Extensions.Reflection (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Reflection)\ +[Cuemon.Extensions.Reflection (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Reflection) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|Assembly|⬇️|`GetAssemblyVersion`, `GetFileVersion`, `GetProductVersion`, `IsDebugBuild`| +|MemberInfo|⬇️|`HasAttributes`| +|PropertyInfo|⬇️|`IsAutoProperty`| +|Type|⬇️|`GetEmbeddedResources`, `ToMethodBase`, `GetRuntimePropertiesExceptOf{T}`, `ToFullNameIncludingAssemblyName`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Runtime.Caching.md b/docfx/api/namespaces/Cuemon.Extensions.Runtime.Caching.md new file mode 100644 index 000000000..e584fd101 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Runtime.Caching.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.Runtime.Caching +summary: *content +--- +The Cuemon.Extensions.Runtime.Caching namespace contains extension methods that complements the Cuemon.Runtime.Caching namespace by adding support for Memoization techniques and GetOrAdd convenience ; both with vast overloads and extended by the ICacheEnumerable{TKey} interface for loose coupling. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Runtime.Caching namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Runtime.Caching.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Runtime.Caching)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Runtime.Caching)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Runtime.Caching) + +NuGet packages 📦\ +[Cuemon.Extensions.Runtime.Caching (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Runtime.Caching)\ +[Cuemon.Extensions.Runtime.Caching (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Runtime.Caching) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|ICacheEnumerable{TKey}|⬇️|`GetOrAdd{TKey, TResult}`, `Memoize{TKey, T, TResult}`, `Memoize{TKey, T, TResult}`, `Memoize{TKey, T1, T2, TResult}`, `Memoize{TKey, T1, T2, T3, TResult}`, `Memoize{TKey, T1, T2, T3, T4, TResult}`, `Memoize{TKey, T1, T2, T3, T4, T5, TResult}`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Text.md b/docfx/api/namespaces/Cuemon.Extensions.Text.md new file mode 100644 index 000000000..cd196cb58 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Text.md @@ -0,0 +1,25 @@ +--- +uid: Cuemon.Extensions.Text +summary: *content +--- +The Cuemon.Extensions.Text namespace contains extension methods that complements the Cuemon.Text namespace while being an addition to the System namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Text namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Text.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Text)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Text)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Text) + +NuGet packages 📦\ +[Cuemon.Extensions.Text (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Text)\ +[Cuemon.Extensions.Text (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Text) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IEncodingOptions|⬇️|`DetectUnicodeEncoding`| +|String|⬇️|`ToEncodedString`, `ToAsciiEncodedString`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Threading.Tasks.md b/docfx/api/namespaces/Cuemon.Extensions.Threading.Tasks.md new file mode 100644 index 000000000..bd44b9081 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Threading.Tasks.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.Threading.Tasks +summary: *content +--- +The Cuemon.Extensions.Threading.Tasks namespace contains extension methods that complements the System.Threading.Tasks namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Threading.Tasks namespace](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Threading/Tasks)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Threading/Tasks)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Threading/Tasks) + +NuGet packages 📦\ +[Cuemon.Extensions.Threading (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Threading)\ +[Cuemon.Extensions.Threading (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Threading) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|Task|⬇️|`ContinueWithCapturedContext`, `ContinueWithCapturedContext{TResult}`, `ContinueWithSuppressedContext`, `ContinueWithSuppressedContext{TResult}`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Threading.md b/docfx/api/namespaces/Cuemon.Extensions.Threading.md new file mode 100644 index 000000000..e6121d392 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Threading.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Extensions.Threading +summary: *content +--- +The Cuemon.Extensions.Threading namespace contains extension methods that complements the System.Threading namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Threading namespace](https://docs.microsoft.com/en-us/dotnet/api/system.threading?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Threading)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Threading)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Threading) + +NuGet packages 📦\ +[Cuemon.Extensions.Threading (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Threading)\ +[Cuemon.Extensions.Threading (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Threading) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xml.Linq.md b/docfx/api/namespaces/Cuemon.Extensions.Xml.Linq.md new file mode 100644 index 000000000..a8e853fa1 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xml.Linq.md @@ -0,0 +1,22 @@ +--- +uid: Cuemon.Extensions.Xml.Linq +summary: *content +--- +The Cuemon.Extensions.Xml.Linq namespace contains extension methods that complements the System namespace while being an addition to the System.Xml.Linq namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xml/Linq)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xml/Linq)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xml/Linq) + +NuGet packages 📦\ +[Cuemon.Extensions.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xml)\ +[Cuemon.Extensions.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|String|⬇️|`IsXmlString`, `TryParseXElement`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.Converters.md b/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.Converters.md new file mode 100644 index 000000000..f83b23b24 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.Converters.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.Xml.Serialization.Converters +summary: *content +--- +The Cuemon.Extensions.Xml.Serialization.Converters namespace contains extension methods that complements the Cuemon.Xml.Serialization.Converters namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Xml.Serialization.Converters namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Xml.Serialization.Converters.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xml/Serialization/Converters)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xml/Serialization/Converters)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xml/Serialization/Converters) + +NuGet packages 📦\ +[Cuemon.Extensions.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xml)\ +[Cuemon.Extensions.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|IList{XmlConverter}|⬇️|`FirstOrDefaultReaderConverter`, `FirstOrDefaultWriterConverter`, `AddXmlConverter`, `InsertXmlConverter`, `AddEnumerableConverter`, `AddExceptionDescriptorConverter`, `AddUriConverter`, `AddDateTimeConverter`, `AddTimeSpanConverter`, `AddStringConverter`, `AddExceptionConverter`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.Diagnostics.md b/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.Diagnostics.md new file mode 100644 index 000000000..054dd7ebb --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.Diagnostics.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.Xml.Serialization.Diagnostics +summary: *content +--- +The Cuemon.Extensions.Xml.Serialization.Diagnostics namespace contains extension methods that complements the Cuemon.Diagnostics namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Diagnostics namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Diagnostics.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xml/Serialization/Diagnostics)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xml/Serialization/Diagnostics)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xml/Serialization/Diagnostics) + +NuGet packages 📦\ +[Cuemon.Extensions.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xml)\ +[Cuemon.Extensions.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|ExceptionDescriptor|⬇️|`ToInsightsXmlString`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.md b/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.md new file mode 100644 index 000000000..ec71dd2b7 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xml.Serialization.md @@ -0,0 +1,24 @@ +--- +uid: Cuemon.Extensions.Xml.Serialization +summary: *content +--- +The Cuemon.Extensions.Xml.Serialization namespace contains extension methods that complements the Cuemon.Xml.Serialization namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Xml.Serialization namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Xml.Serialization.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xml/Serialization)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xml/Serialization)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xml/Serialization) + +NuGet packages 📦\ +[Cuemon.Extensions.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xml)\ +[Cuemon.Extensions.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|XmlSerializerOptions|⬇️|`ApplyToDefaultSettings`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xml.md b/docfx/api/namespaces/Cuemon.Extensions.Xml.md new file mode 100644 index 000000000..2456c2712 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xml.md @@ -0,0 +1,31 @@ +--- +uid: Cuemon.Extensions.Xml +summary: *content +--- +The Cuemon.Extensions.Xml namespace contains extension methods that complements the Cuemon.Xml namespace while being an addition to the System.Xml namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon.Xml namespace](https://docs.cuemon.net/api/dotnet/Cuemon.Xml.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xml)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xml)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xml) + +NuGet packages 📦\ +[Cuemon.Extensions.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xml)\ +[Cuemon.Extensions.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xml) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|byte[]|⬇️|`ToXmlReader`| +|DateTime|⬇️|`ToString`| +|IHierarchy{T}|⬇️|`HasXmlIgnoreAttribute`, `IsNodeEnumerable`, `GetXmlRootOrElement`, `OrderByXmlAttributes`| +|Stream|⬇️|`ToXmlReader`, `CopyXmlStream`, `TryDetectXmlEncoding`, `RemoveXmlNamespaceDeclarations`| +|String|⬇️|`EscapeXml`, `UnescapeXml`, `SanitizeXmlElementName`, `SanitizeXmlElementText`| +|Uri|⬇️|`ToXmlReader`| +|XmlReader|⬇️|`Chunk`, `ToHierarchy`, `ToStream`, `MoveToFirstElement`| +|XmlWriter|⬇️|`WriteObject`, `WriteObject{T}`, `WriteStartElement`, `WriteEncapsulatingElementWhenNotNull{T}`, `WriteXmlRootElement{T}`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.md b/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.md new file mode 100644 index 000000000..289a76fa4 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc +summary: *content +--- +The Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core MVC and used in conjunction with Microsoft Dependency Injection. The namespace relates to the Microsoft.AspNetCore.Mvc.Testing namespace. + +Availability: NET Core 3.1, .NET 5.0 + +Complements: [Microsoft.AspNetCore.Mvc.Testing namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing?view=aspnetcore-3.0) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc) + +NuGet packages 📦\ +[Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc)\ +[Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.AspNetCore.md b/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.AspNetCore.md new file mode 100644 index 000000000..7c65d9835 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.AspNetCore.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Extensions.Xunit.Hosting.AspNetCore +summary: *content +--- +The Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core and used in conjunction with Microsoft Dependency Injection. The namespace relates to the Microsoft.AspNetCore.TestHost namespace. + +Availability: NET Core 3.1, .NET 5.0 + +Complements: [Microsoft.AspNetCore.TestHost namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.testhost?view=aspnetcore-3.0) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore) + +NuGet packages 📦\ +[Cuemon.Extensions.Xunit.Hosting.AspNetCore (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xunit.Hosting.AspNetCore)\ +[Cuemon.Extensions.Xunit.Hosting.AspNetCore (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xunit.Hosting.AspNetCore) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.md b/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.md new file mode 100644 index 000000000..75a957658 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xunit.Hosting.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Extensions.Xunit.Hosting +summary: *content +--- +The Cuemon.Extensions.Xunit.Hosting namespace contains types that provides a uniform way of doing unit testing that is used in conjunction with Microsoft Dependency Injection. The namespace relates to the Xunit.Abstractions namespace. + +Availability: NET Standard 2.0, NET Core 3.0, .NET 5.0 + +Complements: [xUnit: Shared Context between Tests](https://xunit.net/docs/shared-context) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xunit.Hosting)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xunit.Hosting)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xunit.Hosting) + +NuGet packages 📦\ +[Cuemon.Extensions.Xunit.Hosting (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xunit.Hosting)\ +[Cuemon.Extensions.Xunit.Hosting (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xunit.Hosting) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.Xunit.md b/docfx/api/namespaces/Cuemon.Extensions.Xunit.md new file mode 100644 index 000000000..6641e0d4c --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.Xunit.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Extensions.Xunit +summary: *content +--- +The Cuemon.Extensions.Xunit namespace contains types that provides a uniform way of doing unit testing. The namespace relates to the Xunit.Abstractions namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [xUnit: Capturing Output](https://xunit.net/docs/capturing-output) 🔗 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xunit)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xunit)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xunit) + +NuGet packages 📦\ +[Cuemon.Extensions.Xunit (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Xunit)\ +[Cuemon.Extensions.Xunit (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Xunit) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Extensions.md b/docfx/api/namespaces/Cuemon.Extensions.md new file mode 100644 index 000000000..07de24eb8 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Extensions.md @@ -0,0 +1,37 @@ +--- +uid: Cuemon.Extensions +summary: *content +--- +The Cuemon.Extensions namespace contains extension methods that complements the Cuemon namespace while being an addition to the System namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [Cuemon namespace](https://docs.cuemon.net/api/dotnet/Cuemon.html) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Core)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Core)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Core) + +NuGet packages 📦\ +[Cuemon.Extensions.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Extensions.Core)\ +[Cuemon.Extensions.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Extensions.Core) + +### Extension Methods + +|Type|Ext|Methods| +|--:|:-:|---| +|Action|⬇️|`Configure{TOptions}`, `CreateInstance{T}`| +|Byte|⬇️|`ToEncodedString`, `ToHexadecimalString`, `ToBinaryString`, `ToUrlEncodedBase64String`, `ToBase64String`, `TryDetectUnicodeEncoding`| +|Char|⬇️|`ToEnumerable`, `FromChars`| +|Condition|⬇️|`HasDifference`| +|DateTime|⬇️|`ToUnixEpochTime`, `ToUtcKind`, `ToLocalKind`, `ToDefaultKind`, `IsWithinRange`, `IsTimeOfDayNight`, `IsTimeOfDayMorning`, `IsTimeOfDayForenoon`, `IsTimeOfDayAfternoon`, `IsTimeOfDayEvening`, `Floor`, `Ceiling`, `Round`| +|Double|⬇️|`FromUnixEpochTime`, `ToTimeSpan`, `Factorial`, `RoundOff`| +|Exception|⬇️|`Flatten`| +|Int*|⬇️|`Min`, `Max`, `IsPrime`, `IsCountableSequence`, `IsEven`, `IsOdd`| +|Mapping|⬇️|`Add`| +|Object|⬇️|`UseWrapper{T}`, `As{T}`, `GetHashCode32{T}`, `GetHashCode64{T}`, `ToDelimitedString{T}`, `Adjust{T}`, `IsNullable{T}`| +|String|⬇️|`Difference`, `ToCharArray`, `ToByteArray`, `FromUrlEncodedBase64`, `ToGuid`, `FromBinaryDigits`, `FromBase64`, `Join`, `ToCasing`, `ToUri`, `IsNullOrEmpty`, `IsNullOrWhiteSpace`, `IsEmailAddress`, `IsGuid`, `IsHex`, `IsNumeric`, `IsBase64`, `IsCountableSequence`, `SplitDelimited`, `Count`, `RemoveAll`, `ReplaceAll`, `JsEscape`, `JsUnescape`, `ContainsAny`, `ContainsAll`, `EqualsAny`, `StartsWith`, `TrimAll`, `IsSequenceOf{T}`, `FromHexadecimal`, `ToHexadecimal`, `ToEnum{TEnum}`, `ToTimeSpan`, `SubstringBefore`, `Chunk`, `SuffixWith`, `SuffixWithForwardingSlash`, `PrefixWith`| +|TimeSpan|⬇️|`GetTotalNanoseconds`, `GetTotalMicroseconds`, `Floor`, `Ceiling`, `Round`| +|Type|⬇️|`ToFriendlyName`, `ToTypeCode`, `HasEqualityComparerImplementation`, `HasComparableImplementation`, `HasComparerImplementation`, `HasEnumerableImplementation`, `HasDictionaryImplementation`, `HasKeyValuePairImplementation`, `IsNullable`, `HasAnonymousCharacteristics`, `IsComplex`, `IsSimple`, `GetDefaultValue`, `HasTypes`, `HasInterfaces`, `HasAttributes`| +|Validator|⬇️|`HasDifference`, `NoDifference`| \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Globalization.md b/docfx/api/namespaces/Cuemon.Globalization.md new file mode 100644 index 000000000..549d677a5 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Globalization.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Globalization +summary: *content +--- +The Cuemon.Globalization namespace contains types that focuses on culture-related information, including language, country/region and localized resources useful for writing globalized (internationalized) applications. The namespace is an addition to the System.Globalization namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Globalization namespace](https://docs.microsoft.com/en-us/dotnet/api/system.globalization?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Globalization)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Globalization)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Globalization) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.IO.md b/docfx/api/namespaces/Cuemon.IO.md new file mode 100644 index 000000000..2fabbdc65 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.IO.md @@ -0,0 +1,9 @@ +--- +uid: Cuemon.IO +summary: *content +--- +The Cuemon.IO namespace contains types primarily focusing on configuration options for IO related operations. The namespace is an addition to the System.IO namespace. + +Availability: NET Standard 2.0, NET Standard 2.1, .NET 5.0 + +Complements: [System.IO namespace](https://docs.microsoft.com/en-us/dotnet/api/system.io?view=netstandard-2.1) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Messaging.md b/docfx/api/namespaces/Cuemon.Messaging.md new file mode 100644 index 000000000..d14400c3f --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Messaging.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Messaging +summary: *content +--- +The Cuemon.Messaging namespace contains types that assist in more advanced scenarios such as CQRS, microservices and event-driven architecture. The namespace is an addition to the System.Messaging namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Messaging namespace](https://docs.microsoft.com/en-us/dotnet/api/system.messaging?view=netframework-4.6.1) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Messaging)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Messaging)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Messaging) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Net.Http.md b/docfx/api/namespaces/Cuemon.Net.Http.md new file mode 100644 index 000000000..f9a596aef --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Net.Http.md @@ -0,0 +1,9 @@ +--- +uid: Cuemon.Net.Http +summary: *content +--- +The Cuemon.Net.Http namespace contains types that is compliant with RFC 7231, section 4: Request methods and RFC 5789, section 2: Patch method while allowing custom definitions as well. The namespace is an addition to the System.Net.Http namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Net.Http namespace](https://docs.microsoft.com/en-us/dotnet/api/system.net.http?view=netstandard-2.0) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Net.Mail.md b/docfx/api/namespaces/Cuemon.Net.Mail.md new file mode 100644 index 000000000..d0d8c583b --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Net.Mail.md @@ -0,0 +1,9 @@ +--- +uid: Cuemon.Net.Mail +summary: *content +--- +The Cuemon.Net.Mail namespace contains types that makes delivery of mail a piece of cake. The namespace is an addition to the System.Net.Mail namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Net.Mail namespace](https://docs.microsoft.com/en-us/dotnet/api/system.net.mail?view=netstandard-2.0) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Net.md b/docfx/api/namespaces/Cuemon.Net.md new file mode 100644 index 000000000..357b7f670 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Net.md @@ -0,0 +1,9 @@ +--- +uid: Cuemon.Net +summary: *content +--- +The Cuemon.Net namespace contains types that provides a simple programming interface for HTTP and SMTP protocols. The namespace is an addition to the System.Net namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Net namespace](https://docs.microsoft.com/en-us/dotnet/api/system.net?view=netstandard-2.0) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Reflection.md b/docfx/api/namespaces/Cuemon.Reflection.md new file mode 100644 index 000000000..8cedf1af7 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Reflection.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Reflection +summary: *content +--- +The Cuemon.Reflection namespace contains types that focuses on working natural with the hidden gems of reflection in order to retrieve information about assemblies, members, parameters, and different versioning schemes that support both traditional and semantic. The namespace is an addition to the System.Reflection namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Reflection namespace](https://docs.microsoft.com/en-us/dotnet/api/system.reflection?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Reflection)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Reflection)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Reflection) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Resilience.md b/docfx/api/namespaces/Cuemon.Resilience.md new file mode 100644 index 000000000..400d08ab1 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Resilience.md @@ -0,0 +1,16 @@ +--- +uid: Cuemon.Resilience +summary: *content +--- +The Cuemon.Resilience namespace contains types related to applying transient fault handling to existing code using intuitively named methods taking both Action{..} and Func{..} delegates to provide a lightweight resilience framework. + +Availability: NET Standard 2.0, .NET 5.0 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Resilience)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Resilience)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Resilience) + +NuGet packages 📦\ +[Cuemon.Resilience (CI)](https://nuget.cuemon.net/packages/Cuemon.Resilience)\ +[Cuemon.Resilience (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Resilience) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Runtime.Caching.md b/docfx/api/namespaces/Cuemon.Runtime.Caching.md new file mode 100644 index 000000000..b56f3d367 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Runtime.Caching.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.Runtime.Caching +summary: *content +--- +The Cuemon.Runtime.Caching namespace contains types related to interfaces for generic caching in applications while providing a concrete in-memory cache implementation named SlimMemoryCache. The namespace is an addition to the System.Runtime.Caching namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Runtime.Caching namespace](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.caching?view=netframework-4.6.1) 🔗 + +Related: [Cuemon.Extensions.Runtime.Caching namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Runtime.Caching.html) 📘 + +Github branches: 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Runtime.Caching)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Runtime.Caching)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Runtime.Caching) + +NuGet packages 📦\ +[Cuemon.Runtime.Caching (CI)](https://nuget.cuemon.net/packages/Cuemon.Runtime.Caching)\ +[Cuemon.Runtime.Caching (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Runtime.Caching) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Runtime.Serialization.Formatters.md b/docfx/api/namespaces/Cuemon.Runtime.Serialization.Formatters.md new file mode 100644 index 000000000..12e011e2d --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Runtime.Serialization.Formatters.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Runtime.Serialization.Formatters +summary: *content +--- +The Cuemon.Runtime.Serialization.Formatters namespace contains types that are used to serialize and deserialize objects into and from a generic type. The namespace is an addition to the System.Runtime.Serialization namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Runtime.Serialization.Formatters namespace](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Runtime/Serialization/Formatters)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Runtime/Serialization/Formatters)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Runtime/Serialization/Formatters) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Runtime.Serialization.md b/docfx/api/namespaces/Cuemon.Runtime.Serialization.md new file mode 100644 index 000000000..7471f8a38 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Runtime.Serialization.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Runtime.Serialization +summary: *content +--- +The Cuemon.Runtime.Serialization namespace contains types that are used to serialize objects into a hierarchy of nodes. The namespace is an addition to the System.Runtime.Serialization namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Runtime.Serialization namespace](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Runtime/Serialization)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Runtime/Serialization)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Runtime/Serialization) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Runtime.md b/docfx/api/namespaces/Cuemon.Runtime.md new file mode 100644 index 000000000..82a037734 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Runtime.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Runtime +summary: *content +--- +The Cuemon.Runtime namespace contains types that support different namespaces such as the Cuemon, Cuemon.Data, Cuemon.Net, and the Cuemon.Runtime.Caching namespaces. The namespace is an addition to the System.Runtime namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Runtime namespace](https://docs.microsoft.com/en-us/dotnet/api/system.runtime?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Runtime)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Runtime)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Runtime) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Security.Cryptography.md b/docfx/api/namespaces/Cuemon.Security.Cryptography.md new file mode 100644 index 000000000..0cbdf5ed1 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Security.Cryptography.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Security.Cryptography +summary: *content +--- +The Cuemon.Security.Cryptography namespace contains types related to cryptographic hashing (both keyed and non-keyed) and a ready-to-use implementation of the Advanced Encryption Standard (AES) symmetric algorithm. The namespace is an addition to the System.Security.Cryptography namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Security.Cryptography namespace](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Security.Cryptography)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Security.Cryptography)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Security.Cryptography) + +NuGet packages 📦\ +[Cuemon.Security.Cryptography (CI)](https://nuget.cuemon.net/packages/Cuemon.Security.Cryptography)\ +[Cuemon.Security.Cryptography (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Security.Cryptography) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Security.md b/docfx/api/namespaces/Cuemon.Security.md new file mode 100644 index 000000000..8ee178beb --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Security.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Security +summary: *content +--- +The Cuemon.Security namespace contains types related to hashing (both non-cryptographic and CRC) and has the base class from which all implementations of hash algorithms and checksums should derive. The namespace is an addition to the System.Security namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Security namespace](https://docs.microsoft.com/en-us/dotnet/api/system.security?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Security)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Security)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Security) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Text.md b/docfx/api/namespaces/Cuemon.Text.md new file mode 100644 index 000000000..89cc3dc70 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Text.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Text +summary: *content +--- +The Cuemon.Text namespace contains types tailored to ease the pain of working with encodings, BOM, parsing, preamble sequences and stems. Also includes way to conform to a uniform way of turning strings into objects of a particular type. The namespace is an addition to the System.Text namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Text namespace](https://docs.microsoft.com/en-us/dotnet/api/system.text?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core/Text)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core/Text)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core/Text) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Threading.md b/docfx/api/namespaces/Cuemon.Threading.md new file mode 100644 index 000000000..b30396695 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Threading.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.Threading +summary: *content +--- +The Cuemon.Threading namespace contains types related to working with long-running concurrent loops and regions that utilizes both synchronous and asynchronous delegates. The namespace is an addition to the System.Threading namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Threading namespace](https://docs.microsoft.com/en-us/dotnet/api/system.threading?view=netstandard-2.0) 🔗 + +Related: [Cuemon.Extensions.Threading.Tasks namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Threading.Tasks.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Threading)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Threading)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Threading) + +NuGet packages 📦\ +[Cuemon.Threading (CI)](https://nuget.cuemon.net/packages/Cuemon.Threading)\ +[Cuemon.Threading (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Threading) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Xml.Linq.md b/docfx/api/namespaces/Cuemon.Xml.Linq.md new file mode 100644 index 000000000..a478802c8 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Xml.Linq.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.Xml.Linq +summary: *content +--- +The Cuemon.Xml.Linq namespace contains types that is used internally by this and related assemblies and is not intended to be used directly from your code. The namespace is an addition to the System.Xml.Linq namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Xml.Linq namespace](https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq?view=netstandard-2.0) 🔗 + +Related: [Cuemon.Extensions.Xml.Linq namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Xml.Linq.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Xml/Extensions/Linq)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Xml/Extensions/Linq)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Xml/Extensions/Linq) + +NuGet packages 📦\ +[Cuemon.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Xml)\ +[Cuemon.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Xml) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Xml.Serialization.Converters.md b/docfx/api/namespaces/Cuemon.Xml.Serialization.Converters.md new file mode 100644 index 000000000..fef54ded0 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Xml.Serialization.Converters.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Xml.Serialization.Converters +summary: *content +--- +The Cuemon.Xml.Serialization.Converters namespace contains types tailored to resemble the [JsonConverter](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonConverter.htm) except we convert objects to and from XML. + +Availability: NET Standard 2.0, .NET 5.0 + +Related: [Cuemon.Extensions.Xml.Serialization.Converters namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Xml.Serialization.Converters.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Extensions.Xml/Serialization/Converters)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Extensions.Xml/Serialization/Converters)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Extensions.Xml/Serialization/Converters) + +NuGet packages 📦\ +[Cuemon.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Xml)\ +[Cuemon.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Xml) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Xml.Serialization.Formatters.md b/docfx/api/namespaces/Cuemon.Xml.Serialization.Formatters.md new file mode 100644 index 000000000..4713fe8ba --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Xml.Serialization.Formatters.md @@ -0,0 +1,16 @@ +--- +uid: Cuemon.Xml.Serialization.Formatters +summary: *content +--- +The Cuemon.Xml.Serialization.Formatters namespace contains types that are used to serialize and deserialize objects into and from XML format using a generic signature. + +Availability: NET Standard 2.0, .NET 5.0 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Xml/Serialization/Formatters)\ +[release](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Xml/Serialization/Formatters)\ +[master](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Xml/Serialization/Formatters) + +NuGet packages 📦\ +[Cuemon.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Xml)\ +[Cuemon.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Xml) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Xml.Serialization.md b/docfx/api/namespaces/Cuemon.Xml.Serialization.md new file mode 100644 index 000000000..0a8ed138a --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Xml.Serialization.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.Xml.Serialization +summary: *content +--- +The Cuemon.Xml.Serialization namespace contains types that are used to serialize and deserialize objects into and from XML format. The namespace is an addition to the System.Xml.Serialization namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Xml.Serialization namespace](https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization?view=netstandard-2.0) 🔗 + +Related: [Cuemon.Extensions.Xml.Serialization namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Xml.Serialization.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Xml/Serialization)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Xml/Serialization)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Xml/Serialization) + +NuGet packages 📦\ +[Cuemon.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Xml)\ +[Cuemon.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Xml) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Xml.XPath.md b/docfx/api/namespaces/Cuemon.Xml.XPath.md new file mode 100644 index 000000000..1f13bf0b5 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Xml.XPath.md @@ -0,0 +1,18 @@ +--- +uid: Cuemon.Xml.XPath +summary: *content +--- +The Cuemon.Xml.XPath namespace contains types related to easing creation of XPathDocument instances. The namespace is an addition to the System.Xml.XPath namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Xml.XPath namespace](https://docs.microsoft.com/en-us/dotnet/api/system.xml.xpath?view=netstandard-2.0) 🔗 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Xml/XPath)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Xml/XPath)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Xml/XPath) + +NuGet packages 📦\ +[Cuemon.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Xml)\ +[Cuemon.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Xml) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.Xml.md b/docfx/api/namespaces/Cuemon.Xml.md new file mode 100644 index 000000000..0d9fbd408 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.Xml.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon.Xml +summary: *content +--- +The Cuemon.Xml namespace contains types related to encoding, converting and serialization. The included lightweight XML serializer framework offers same flexibility as the one provided by the JSON equivalent from Newtonsoft. The namespace is an addition to the System.Xml namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System.Xml namespace](https://docs.microsoft.com/en-us/dotnet/api/system.xml?view=netstandard-2.0) 🔗 + +Related: [Cuemon.Extensions.Xml namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.Xml.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Xml)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Xml)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Xml) + +NuGet packages 📦\ +[Cuemon.Xml (CI)](https://nuget.cuemon.net/packages/Cuemon.Xml)\ +[Cuemon.Xml (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Xml) \ No newline at end of file diff --git a/docfx/api/namespaces/Cuemon.md b/docfx/api/namespaces/Cuemon.md new file mode 100644 index 000000000..a1a68a211 --- /dev/null +++ b/docfx/api/namespaces/Cuemon.md @@ -0,0 +1,20 @@ +--- +uid: Cuemon +summary: *content +--- +The Cuemon namespace contains fundamental types such as value and reference types, factories and utility classes, interfaces, attributes and feature rich delegates to support functional programming to a whole new level. The namespace is an addition to the System namespace. + +Availability: NET Standard 2.0, .NET 5.0 + +Complements: [System namespace](https://docs.microsoft.com/en-us/dotnet/api/system?view=netstandard-2.0) 🔗 + +Related: [Cuemon.Extensions namespace](https://docs.cuemon.net/api/dotnet/ext/Cuemon.Extensions.html) 📘 + +Github branches 🌱\ +[development](https://github.com/gimlichael/Cuemon/tree/development/src/Cuemon.Core)\ +[release](https://github.com/gimlichael/Cuemon/tree/release/src/Cuemon.Core)\ +[master](https://github.com/gimlichael/Cuemon/tree/master/src/Cuemon.Core) + +NuGet packages 📦\ +[Cuemon.Core (CI)](https://nuget.cuemon.net/packages/Cuemon.Core)\ +[Cuemon.Core (Stable and Preview)](https://www.nuget.org/packages/Cuemon.Core) \ No newline at end of file diff --git a/docfx/docfx.json b/docfx/docfx.json new file mode 100644 index 000000000..d1006e714 --- /dev/null +++ b/docfx/docfx.json @@ -0,0 +1,162 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ + "Cuemon.Core/**.csproj", + "Cuemon.Data/**.csproj", + "Cuemon.Data.Integrity/**.csproj", + "Cuemon.Data.SqlClient/**.csproj", + "Cuemon.Diagnostics/**.csproj", + "Cuemon.IO/**.csproj", + "Cuemon.Net/**.csproj", + "Cuemon.Resilience/**.csproj", + "Cuemon.Runtime.Caching/**.csproj", + "Cuemon.Security.Cryptography/**.csproj", + "Cuemon.Threading/**.csproj", + "Cuemon.Xml/**.csproj" + ], + "src": "../src" + } + ], + "dest": "api/dotnet", + "filter": "filterConfig.yml", + "properties": { + "TargetFramework": "netstandard2.1" + } + }, + { + "src": [ + { + "files": [ + "Cuemon.Extensions.Collections.Generic/**.csproj", + "Cuemon.Extensions.Collections.Specialized/**.csproj", + "Cuemon.Extensions.Core/**.csproj", + "Cuemon.Extensions.Data/**.csproj", + "Cuemon.Extensions.Data.Integrity/**.csproj", + "Cuemon.Extensions.DependencyInjection/**.csproj", + "Cuemon.Extensions.Diagnostics/**.csproj", + "Cuemon.Extensions.IO/**.csproj", + "Cuemon.Extensions.Net/**.csproj", + "Cuemon.Extensions.Newtonsoft.Json/**.csproj", + "Cuemon.Extensions.Reflection/**.csproj", + "Cuemon.Extensions.Runtime.Caching/**.csproj", + "Cuemon.Extensions.Text/**.csproj", + "Cuemon.Extensions.Threading/**.csproj", + "Cuemon.Extensions.Xml/**.csproj", + "Cuemon.Extensions.Xunit/**.csproj", + "Cuemon.Extensions.Xunit.Hosting/**.csproj", + "Cuemon.Extensions.Xunit.Hosting.AspNetCore/**.csproj", + "Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/**.csproj" + ], + "src": "../src" + } + ], + "dest": "api/dotnet/ext", + "filter": "filterConfig.yml", + "properties": { + "TargetFramework": "netstandard2.1" + } + }, + { + "src": [ + { + "files": [ + "Cuemon.AspNetCore/**.csproj", + "Cuemon.AspNetCore.Authentication/**.csproj", + "Cuemon.AspNetCore.Mvc/**.csproj", + "Cuemon.AspNetCore.Razor/**.csproj" + ], + "src": "../src" + } + ], + "dest": "api/aspnet", + "filter": "filterConfig.yml", + "properties": { + "TargetFramework": "netcoreapp3.0" + } + }, + { + "src": [ + { + "files": [ + "Cuemon.Extensions.AspNetCore/**.csproj", + "Cuemon.Extensions.AspNetCore.Authentication/**.csproj", + "Cuemon.Extensions.AspNetCore.Mvc/**.csproj", + "Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/**.csproj", + "Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/**.csproj" + ], + "src": "../src" + } + ], + "dest": "api/aspnet/ext", + "filter": "filterConfig.yml", + "properties": { + "TargetFramework": "netcoreapp3.0" + } + } + ], + "build": { + "content": [ + { + "files": [ + "api/**/*.yml", + "api/**/*.md", + "toc.yml", + "*.md" + ], + "exclude": [ + "bin/**", + "obj/**" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "globalMetadata": { + "_appTitle": "Cuemon for .NET", + "_appFooter": "Copyright 2008-2021 Geekle. All rights reserved. Code with passion; love your code; deliver with pride. 👨‍💻️🔥❤️🚀🤘
Generated by DocFX
", + "_appLogoPath": "images/50x50.png", + "_appFaviconPath": "images/favicon.ico", + "_enableSearch": false, + "_disableContribution": false, + "_gitContribute": { + "repo": "https://github.com/gimlichael/Cuemon", + "branch": "development" + }, + "_gitUrlPattern": "github" + }, + "dest": "wwwroot", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default", + "templates/cuemon" + ], + "overwrite": [ + { + "files": [ + "api/namespaces/**.md" + ], + "exclude": [ + "obj/**", + "wwwroot/**" + ] + } + ], + "postProcessors": [], + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false, + "xrefService": [ + "https://xref.docs.microsoft.com/query?uid={uid}" ] + } +} \ No newline at end of file diff --git a/docfx/filterConfig.yml b/docfx/filterConfig.yml new file mode 100644 index 000000000..837050d4a --- /dev/null +++ b/docfx/filterConfig.yml @@ -0,0 +1,4 @@ +apiRules: +- exclude: + uidRegex: ^System\.Object + type: Type \ No newline at end of file diff --git a/docfx/images/32x32.png b/docfx/images/32x32.png new file mode 100644 index 000000000..b6ea41cf6 Binary files /dev/null and b/docfx/images/32x32.png differ diff --git a/docfx/images/50x50.png b/docfx/images/50x50.png new file mode 100644 index 000000000..093c47b1a Binary files /dev/null and b/docfx/images/50x50.png differ diff --git a/docfx/images/favicon.ico b/docfx/images/favicon.ico new file mode 100644 index 000000000..6edab735b Binary files /dev/null and b/docfx/images/favicon.ico differ diff --git a/docfx/templates/cuemon/layout/_master.tmpl b/docfx/templates/cuemon/layout/_master.tmpl new file mode 100644 index 000000000..0c151594e --- /dev/null +++ b/docfx/templates/cuemon/layout/_master.tmpl @@ -0,0 +1,65 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} +{{!include(/^styles/.*/)}} +{{!include(/^fonts/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} +{{!include(search-stopwords.json)}} + + + + {{>partials/head}} + +
+
+ {{^_disableNavbar}} + {{>partials/navbar}} + {{/_disableNavbar}} + {{^_disableBreadcrumb}} + {{>partials/breadcrumb}} + {{/_disableBreadcrumb}} +
+ {{#_enableSearch}} +
+ {{>partials/searchResults}} +
+ {{/_enableSearch}} +
+ {{^_disableToc}} + {{>partials/toc}} +
+ {{/_disableToc}} + {{#_disableToc}} +
+ {{/_disableToc}} + {{#_disableAffix}} +
+ {{/_disableAffix}} + {{^_disableAffix}} +
+ {{/_disableAffix}} +
+ {{!body}} +
+ + +
+
+ {{^_disableAffix}} + {{>partials/affix}} + {{/_disableAffix}} +
+
+ {{^_disableFooter}} + {{>partials/footer}} + {{/_disableFooter}} +
+ {{>partials/scripts}} + + \ No newline at end of file diff --git a/docfx/templates/cuemon/partials/head.tmpl.partial b/docfx/templates/cuemon/partials/head.tmpl.partial new file mode 100644 index 000000000..f66a69b23 --- /dev/null +++ b/docfx/templates/cuemon/partials/head.tmpl.partial @@ -0,0 +1,28 @@ + + + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + \ No newline at end of file diff --git a/docfx/templates/cuemon/partials/logo.tmpl.partial b/docfx/templates/cuemon/partials/logo.tmpl.partial new file mode 100644 index 000000000..0e4e54801 --- /dev/null +++ b/docfx/templates/cuemon/partials/logo.tmpl.partial @@ -0,0 +1,5 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + {{_appName}} + \ No newline at end of file diff --git a/docfx/templates/cuemon/styles/main.css b/docfx/templates/cuemon/styles/main.css new file mode 100644 index 000000000..a42dcd26b --- /dev/null +++ b/docfx/templates/cuemon/styles/main.css @@ -0,0 +1,60 @@ +nav .container { + margin: 5px auto; +} + +nav .container .navbar-header { + padding-right: 25px; +} + +.hero { + height: 400px; + font-weight: 300; + text-align: center; + background-image: url("https://nblcdn.net/cuemon/i/splash.jpg"); + background-position: center; + background-size: cover; +} + +.caption.header.cuemon { + color: #000; + position: absolute; + padding: 0 10px 7px; + background: rgba(0,0,0,0.5); +} + +.wrap { + position: relative; + min-height: 400px; +} + +.bottom { + position: absolute; + bottom: 5px; + width: 100%; +} + +.btn { + margin: 5px 0; +} + + .btn a { + color: white; + } + +#wrapper .container.nuget { + padding-top: 25px; +} + +.btn-omen { + background-color: #A177B0; + color: #fff; +} + +.btn-omen-package { + background-color: #7C528B; + color: #fff; +} + +.container.nuget .row button { + margin-top: 30px; +} diff --git a/docfx/toc.yml b/docfx/toc.yml new file mode 100644 index 000000000..89bd30aab --- /dev/null +++ b/docfx/toc.yml @@ -0,0 +1,15 @@ +- name: Cuemon Conventions + href: api + topicHref: api/index.md +- name: Core API + href: api/dotnet + topicHref: api/dotnet/index.md +- name: Extensions for Core APIs + href: api/dotnet/ext + topicHref: api/dotnet/ext/index.md +- name: ASP.NET Core API + href: api/aspnet + topicHref: api/aspnet/index.md +- name: Extensions for ASP.NET Core APIs + href: api/aspnet/ext + topicHref: api/aspnet/ext/index.md \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..848892a50 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.8" + +services: + database: + image: gimlichael/aw-mssql-server:2019-GA-ubuntu-16.04 + container_name: "awsql" + hostname: "awsql" + ports: + - "1433:1433" + environment: + SA_PASSWORD: + ACCEPT_EULA: "Y" + SECONDS_TO_AWAIT_SQLSERVER: "45" + MSSQL_PID: "Express" \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/AuthenticationOptions.cs b/src/Cuemon.AspNetCore.Authentication/AuthenticationOptions.cs index a2f484b8a..b7bb2fb83 100644 --- a/src/Cuemon.AspNetCore.Authentication/AuthenticationOptions.cs +++ b/src/Cuemon.AspNetCore.Authentication/AuthenticationOptions.cs @@ -1,6 +1,6 @@ using System; -using System.Text; -using Cuemon.Text; +using System.Net; +using System.Net.Http; namespace Cuemon.AspNetCore.Authentication { @@ -12,16 +12,32 @@ public abstract class AuthenticationOptions /// /// Initializes a new instance of the class. /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// () => new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage) }; + /// + /// + /// + /// true + /// + /// + /// + /// The request has not been applied because it lacks valid authentication credentials for the target resource. + /// + /// + /// protected AuthenticationOptions() { - HttpNotAuthorizedBody = () => - { - return Convertible.GetBytes("401 Unauthorized", o => - { - o.Encoding = Encoding.UTF8; - o.Preamble = PreambleSequence.Remove; - }); - }; + ResponseHandler = () => new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage) }; + RequireSecureConnection = true; + UnauthorizedMessage = "The request has not been applied because it lacks valid authentication credentials for the target resource."; } /// @@ -31,9 +47,15 @@ protected AuthenticationOptions() public bool RequireSecureConnection { get; set; } /// - /// Gets or sets the function delegate for retrieving content for the body of an unauthorized request. + /// Gets or sets the function delegate that configures the unauthorized response in the form of a . /// - /// A for retrieving content for the body of an unauthorized request. - public Func HttpNotAuthorizedBody { get; set; } + /// The function delegate that configures the unauthorized response in the form of a . + public Func ResponseHandler { get; set; } + + /// + /// Gets or sets the message of an unauthorized request. + /// + /// The message of an unauthorized request. + public string UnauthorizedMessage { get; set; } } } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/AuthenticationUtility.cs b/src/Cuemon.AspNetCore.Authentication/Authenticator.cs similarity index 69% rename from src/Cuemon.AspNetCore.Authentication/AuthenticationUtility.cs rename to src/Cuemon.AspNetCore.Authentication/Authenticator.cs index 1447cf51e..ba5925867 100644 --- a/src/Cuemon.AspNetCore.Authentication/AuthenticationUtility.cs +++ b/src/Cuemon.AspNetCore.Authentication/Authenticator.cs @@ -1,42 +1,16 @@ using System; using System.Security; using System.Security.Claims; -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; namespace Cuemon.AspNetCore.Authentication { /// - /// Provides a set of generic ways to work with HTTP based authentication. + /// Provides a set of static methods for working with HTTP based authentication. /// - public static class AuthenticationUtility + public static class Authenticator { - /// - /// The value of the header credential separator of a HTTP Basic access authentication. - /// - public const char BasicAuthenticationCredentialSeparator = ':'; - - /// - /// The value of the header credential separator of a HTTP Digest access authentication. - /// - public const char DigestAuthenticationCredentialSeparator = ','; - - /// - /// The value of the header credential separator of a HTTP JSON Web Token authentication. - /// - public const char JwtAuthenticationCredentialSeparator = '.'; - - /// - /// The value of the status description associated with . - /// - public const string HttpNotAuthorizedStatus = "401 Unauthorized"; - - /// - /// Equivalent to HTTP status 401. Unauthorized indicates that the requested resource requires authentication. - /// - public const int HttpNotAuthorizedStatusCode = 401; - /// /// Provides a generic way to make authentication requests using the specified . /// @@ -92,19 +66,5 @@ private static bool TryGetPrincipal(HttpContext context, Func httpNotAuthorizedBody) - { - var bodyContent = httpNotAuthorizedBody?.Invoke(); - if (bodyContent != null) - { - await context.Response.Body.WriteAsync(bodyContent, 0, bodyContent.Length).ConfigureAwait(false); - } - } } } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/AuthorizationHeader.cs b/src/Cuemon.AspNetCore.Authentication/AuthorizationHeader.cs new file mode 100644 index 000000000..d49fd894a --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/AuthorizationHeader.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cuemon.Collections.Generic; + +namespace Cuemon.AspNetCore.Authentication +{ + /// + /// Represents the base class from which all implementations of authorization header should derive. + /// + public abstract class AuthorizationHeader + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the authentication scheme. + protected AuthorizationHeader(string authenticationScheme) + { + Validator.ThrowIfNullOrWhitespace(authenticationScheme, nameof(authenticationScheme)); + AuthenticationScheme = authenticationScheme; + } + + /// + /// Gets the name of the authentication scheme. + /// + /// The name of the authentication scheme. + public string AuthenticationScheme { get; } + + /// + /// Parses the specified . + /// + /// The authorization header to parse. + /// The which need to be configured. + /// An equivalent of . + public virtual AuthorizationHeader Parse(string authorizationHeader, Action setup) + { + Validator.ThrowIfNullOrWhitespace(authorizationHeader, nameof(authorizationHeader)); + Validator.ThrowIfFalse(() => authorizationHeader.StartsWith(AuthenticationScheme), nameof(authorizationHeader), $"Header did not start with {AuthenticationScheme}."); + + var options = Patterns.Configure(setup); + var headerWithoutScheme = authorizationHeader.Remove(0, AuthenticationScheme.Length + 1); + var credentials = DelimitedString.Split(headerWithoutScheme, o => o.Delimiter = options.CredentialsDelimiter).ToList(); + + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var credential in credentials) + { + var kvp = DelimitedString.Split(credential, o => o.Delimiter = options.CredentialsKeyValueDelimiter); + var key = kvp[0].Trim(); + var value = kvp[1].Trim('"'); + Decorator.Enclose(dictionary).TryAdd(key, value); + } + + return ParseCore(dictionary); + } + + /// + /// The core parser that resolves an from a set of . + /// + /// The credentials used in authentication. + /// An equivalent of . + protected abstract AuthorizationHeader ParseCore(IReadOnlyDictionary credentials); + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public abstract override string ToString(); + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/AuthorizationHeaderBuilder.cs b/src/Cuemon.AspNetCore.Authentication/AuthorizationHeaderBuilder.cs new file mode 100644 index 000000000..7d249ff14 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/AuthorizationHeaderBuilder.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Cuemon.Collections.Generic; + +namespace Cuemon.AspNetCore.Authentication +{ + /// + /// Represents the base class from which all implementations of authorization header builders should derive. + /// Implements the + /// + /// The type of the authorization header result. + /// The type of the authorization header builder. + public abstract class AuthorizationHeaderBuilder : AuthorizationHeaderBuilder + where TAuthorizationHeader : AuthorizationHeader + where TAuthorizationHeaderBuilder : AuthorizationHeaderBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the authentication scheme. + protected AuthorizationHeaderBuilder(string authenticationScheme) : base(authenticationScheme) + { + } + + /// + /// Attempts to add or update an existing field with the provided with the specified . + /// + /// The name of the field to add or update. + /// The value of the field to add or update. + /// that can be used to further build the header. + public TAuthorizationHeaderBuilder AddOrUpdate(string name, string value) + { + Validator.ThrowIfNullOrWhitespace(name, nameof(name)); + Decorator.Enclose(Data).AddOrUpdate(name, value); + return this as TAuthorizationHeaderBuilder; + } + + /// + /// Builds an instance of that implements . + /// + /// . + public abstract TAuthorizationHeader Build(); + } + + /// + /// The base class of an . + /// + public abstract class AuthorizationHeaderBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the authentication scheme. + protected AuthorizationHeaderBuilder(string authenticationScheme) + { + Validator.ThrowIfNullOrWhitespace(authenticationScheme, nameof(authenticationScheme)); + AuthenticationScheme = authenticationScheme; + } + + /// + /// Gets the fields added to this instance. + /// + /// The fields added to this instance. + protected IDictionary Data { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets the relations added to this instance. + /// + /// The relations added to this instance. + protected IDictionary Relation { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets the name of the authentication scheme. + /// + /// The name of the authentication scheme. + public string AuthenticationScheme { get; } + + /// + /// Maps the logical relation between a and the associated. + /// + /// The name of a member. + /// The field names to associate with . + protected void MapRelation(string memberName, params string[] fieldNames) + { + Validator.ThrowIfNullOrWhitespace(memberName, nameof(memberName)); + Validator.ThrowIfSequenceNullOrEmpty(fieldNames, nameof(fieldNames)); + foreach (var key in fieldNames) { Decorator.Enclose(Relation).AddOrUpdate(key, memberName); } + } + + /// + /// Validates that any has been added to . + /// + /// The required field names to validate. + /// + /// The required field is missing. + /// + protected void ValidateData(params string[] requiredFieldNames) + { + foreach (var rfn in requiredFieldNames) + { + var invalidState = !Data.ContainsKey(rfn) || Data[rfn] == null; + if (Relation.TryGetValue(rfn, out var member) && invalidState) { throw new ArgumentException($"Required field is missing. Did you forget to invoke {member}?", rfn); } + if (invalidState) { throw new ArgumentException("Required field is missing.", rfn); } + } + } + + /// + /// Converts this instance to an . + /// + /// An equivalent of this instance. + public ImmutableDictionary ToImmutableDictionary() + { + return Data.ToImmutableDictionary(); + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return DelimitedString.Create(Data.Keys.Select(key => $"{key}={Data[key]}")); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/AuthorizationHeaderOptions.cs b/src/Cuemon.AspNetCore.Authentication/AuthorizationHeaderOptions.cs new file mode 100644 index 000000000..7b8216109 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/AuthorizationHeaderOptions.cs @@ -0,0 +1,46 @@ +namespace Cuemon.AspNetCore.Authentication +{ + /// + /// Configuration options for . + /// + public class AuthorizationHeaderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// , + /// + /// + /// + /// = + /// + /// + /// + public AuthorizationHeaderOptions() + { + CredentialsDelimiter = ","; + CredentialsKeyValueDelimiter = "="; + } + + /// + /// Gets or sets the credentials delimiter. + /// + /// The credentials delimiter. + public string CredentialsDelimiter { get; set; } + + /// + /// Gets or sets the credentials key value delimiter. + /// + /// The credentials key value delimiter. + public string CredentialsKeyValueDelimiter { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticationMiddleware.cs b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticationMiddleware.cs new file mode 100644 index 000000000..4293d54f9 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticationMiddleware.cs @@ -0,0 +1,69 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Cuemon.IO; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.AspNetCore.Authentication.Basic +{ + /// + /// Provides a HTTP Basic Authentication middleware implementation for ASP.NET Core. + /// + public class BasicAuthenticationMiddleware : ConfigurableMiddleware + { + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The which need to be configured. + public BasicAuthenticationMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The middleware which need to be configured. + public BasicAuthenticationMiddleware(RequestDelegate next, Action setup) : base(next, setup) + { + } + + /// + /// Executes the . + /// + /// The context of the current request. + /// A task that represents the execution of this middleware. + public override async Task InvokeAsync(HttpContext context) + { + if (!Authenticator.TryAuthenticate(context, Options.RequireSecureConnection, AuthorizationHeaderParser, TryAuthenticate)) + { + await Decorator.Enclose(context).InvokeAuthenticationAsync(Options, async (message, response) => + { + context.Response.OnStarting(() => + { + context.Response.Headers.Add(HeaderNames.WWWAuthenticate, FormattableString.Invariant($"{BasicAuthorizationHeader.Scheme} realm=\"{Options.Realm}\"")); + return Task.CompletedTask; + }); + response.StatusCode = (int)message.StatusCode; + await Decorator.Enclose(response.Body).WriteAsync(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ConfigureAwait(false); + }).ConfigureAwait(false); + } + await Next(context).ConfigureAwait(false); + } + + private bool TryAuthenticate(HttpContext context, BasicAuthorizationHeader header, out ClaimsPrincipal result) + { + if (Options.Authenticator == null) { throw new InvalidOperationException(FormattableString.Invariant($"The {nameof(Options.Authenticator)} cannot be null.")); } + result = Options.Authenticator(header.UserName, header.Password); + return Condition.IsNotNull(result); + } + + private BasicAuthorizationHeader AuthorizationHeaderParser(HttpContext context, string authorizationHeader) + { + return BasicAuthorizationHeader.Create(authorizationHeader); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/BasicAuthenticationOptions.cs b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticationOptions.cs similarity index 56% rename from src/Cuemon.AspNetCore.Authentication/BasicAuthenticationOptions.cs rename to src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticationOptions.cs index 26599646f..16cbc1eef 100644 --- a/src/Cuemon.AspNetCore.Authentication/BasicAuthenticationOptions.cs +++ b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticationOptions.cs @@ -1,4 +1,4 @@ -namespace Cuemon.AspNetCore.Authentication +namespace Cuemon.AspNetCore.Authentication.Basic { /// /// Configuration options for . This class cannot be inherited. @@ -9,8 +9,26 @@ public sealed class BasicAuthenticationOptions : AuthenticationOptions /// /// Initializes a new instance of the class. /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// null + /// + /// + /// + /// AuthenticationServer + /// + /// + /// public BasicAuthenticationOptions() { + Realm = "AuthenticationServer"; } /// diff --git a/src/Cuemon.AspNetCore.Authentication/BasicAuthenticator.cs b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticator.cs similarity index 93% rename from src/Cuemon.AspNetCore.Authentication/BasicAuthenticator.cs rename to src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticator.cs index 2d92cd552..630644c46 100644 --- a/src/Cuemon.AspNetCore.Authentication/BasicAuthenticator.cs +++ b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthenticator.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -namespace Cuemon.AspNetCore.Authentication +namespace Cuemon.AspNetCore.Authentication.Basic { /// /// Represents the method that defines an Authenticator typically assigned on . diff --git a/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthorizationHeader.cs b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthorizationHeader.cs new file mode 100644 index 000000000..63b6ee829 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthorizationHeader.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Cuemon.Text; + +namespace Cuemon.AspNetCore.Authentication.Basic +{ + /// + /// Provides a representation of a HTTP Basic Authentication header. + /// Implements the + /// + /// + public class BasicAuthorizationHeader : AuthorizationHeader + { + /// + /// Creates an instance of from the specified parameters. + /// + /// The raw HTTP authorization header. + /// An instance of . + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + public static BasicAuthorizationHeader Create(string authorizationHeader) + { + Validator.ThrowIfNullOrWhitespace(authorizationHeader, nameof(authorizationHeader)); + return new BasicAuthorizationHeader().Parse(authorizationHeader, null) as BasicAuthorizationHeader; + } + + /// + /// The default authentication scheme of the . + /// + public const string Scheme = "Basic"; + + BasicAuthorizationHeader() : base(Scheme) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The username of the credentials. + /// The password of the credentials. + /// + /// cannot be null -or- + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters -or- + /// cannot be empty or consist only of white-space characters -or- + /// does not allow the presence of the colon character. + /// + public BasicAuthorizationHeader(string username, string password) : base(Scheme) + { + Validator.ThrowIfNullOrWhitespace(username, nameof(username)); + Validator.ThrowIfNullOrWhitespace(password, nameof(password)); + Validator.ThrowIfTrue(() => username.Contains(":"), nameof(username), $"Colon is not allowed as part of the {nameof(username)}."); + UserName = username; + Password = password; + } + + + /// + /// Gets the username of the credentials. + /// + /// The username of the credentials. + public string UserName { get; } + + /// + /// Gets the password of the credentials. + /// + /// The password of the credentials. + public string Password { get; } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + var credentials = Convert.ToBase64String(Decorator.Enclose($"{UserName}:{Password}").ToByteArray()); + return $"{AuthenticationScheme} {credentials}"; + } + + /// + /// Parses the specified . + /// + /// The authorization header to parse. + /// The which need to be configured. + /// An equivalent of . + public override AuthorizationHeader Parse(string authorizationHeader, Action setup) + { + Validator.ThrowIfNullOrWhitespace(authorizationHeader, nameof(authorizationHeader)); + Validator.ThrowIfFalse(() => authorizationHeader.StartsWith(AuthenticationScheme), nameof(authorizationHeader), $"Header did not start with {AuthenticationScheme}."); + + var headerWithoutScheme = authorizationHeader.Remove(0, AuthenticationScheme.Length + 1); + var credentials = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { BasicFields.Credentials, headerWithoutScheme.Trim() } + }; + return ParseCore(credentials); + } + + /// + /// The core parser that resolves an from a set of . + /// + /// The credentials used in authentication. + /// An equivalent of . + protected override AuthorizationHeader ParseCore(IReadOnlyDictionary credentials) + { + if (credentials.TryGetValue(BasicFields.Credentials, out var base64EncodedCredentials) && Condition.IsBase64(base64EncodedCredentials)) + { + var plainCredentials = Convertible.ToString(Convert.FromBase64String(base64EncodedCredentials), options => + { + options.Encoding = Encoding.ASCII; + options.Preamble = PreambleSequence.Remove; + }).Split(new [] { ':' }, 2); + + if (plainCredentials.Length == 2 && + !string.IsNullOrWhiteSpace(plainCredentials[0]) && + !string.IsNullOrWhiteSpace(plainCredentials[1])) + { + return new BasicAuthorizationHeader(plainCredentials[0], plainCredentials[1]); + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthorizationHeaderBuilder.cs b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthorizationHeaderBuilder.cs new file mode 100644 index 000000000..8737a4a45 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Basic/BasicAuthorizationHeaderBuilder.cs @@ -0,0 +1,48 @@ +namespace Cuemon.AspNetCore.Authentication.Basic +{ + /// + /// Provides a way to fluently represent a HTTP Basic Authentication header. + /// + public class BasicAuthorizationHeaderBuilder : AuthorizationHeaderBuilder + { + /// + /// Initializes a new instance of the class. + /// + public BasicAuthorizationHeaderBuilder() : base(BasicAuthorizationHeader.Scheme) + { + MapRelation(nameof(AddUserName), BasicFields.UserName); + MapRelation(nameof(AddPassword), BasicFields.Password); + } + + + /// + /// Adds the credential scope that defines the remote resource. + /// + /// The credential scope that defines the remote resource. + /// An that can be used to further build the HTTP HMAC Authentication header. + public BasicAuthorizationHeaderBuilder AddUserName(string username) + { + return AddOrUpdate(BasicFields.UserName, username); + } + + /// + /// Adds the client identifier that is the public key of the signing process. + /// + /// The client identifier that is the public key of the signing process. + /// An that can be used to further build the HTTP HMAC Authentication header. + public BasicAuthorizationHeaderBuilder AddPassword(string password) + { + return AddOrUpdate(BasicFields.Password, password); + } + + /// + /// Builds an instance of that implements . + /// + /// An instance of . + public override BasicAuthorizationHeader Build() + { + ValidateData(BasicFields.UserName, BasicFields.Password); + return new BasicAuthorizationHeader(Data[BasicFields.UserName], Data[BasicFields.Password]); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Basic/BasicFields.cs b/src/Cuemon.AspNetCore.Authentication/Basic/BasicFields.cs new file mode 100644 index 000000000..7824921ac --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Basic/BasicFields.cs @@ -0,0 +1,28 @@ +namespace Cuemon.AspNetCore.Authentication.Basic +{ + /// + /// A collection of constants for . + /// + public static class BasicFields + { + /// + /// The realm field of a HTTP Basic access authentication. + /// + public const string Realm = "realm"; + + /// + /// The username of the . + /// + public const string UserName = "username"; + + /// + /// The password of the . + /// + public const string Password = "password"; + + /// + /// The credentials of the HTTP Basic access authentication. + /// + public const string Credentials = "credentials"; + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/BasicAuthenticationMiddleware.cs b/src/Cuemon.AspNetCore.Authentication/BasicAuthenticationMiddleware.cs deleted file mode 100644 index 21e7d4341..000000000 --- a/src/Cuemon.AspNetCore.Authentication/BasicAuthenticationMiddleware.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; -using Cuemon.AspNetCore.Builder; -using Cuemon.Text; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Provides a HTTP Basic Authentication middleware implementation for ASP.NET Core. - /// - public class BasicAuthenticationMiddleware : ConfigurableMiddleware - { - /// - /// Initializes a new instance of the class. - /// - /// The delegate of the request pipeline to invoke. - /// The which need to be configured. - public BasicAuthenticationMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The delegate of the request pipeline to invoke. - /// The middleware which need to be configured. - public BasicAuthenticationMiddleware(RequestDelegate next, Action setup) : base(next, setup) - { - } - - /// - /// Executes the . - /// - /// The context of the current request. - /// A task that represents the execution of this middleware. - public override async Task InvokeAsync(HttpContext context) - { - if (!AuthenticationUtility.TryAuthenticate(context, Options.RequireSecureConnection, AuthorizationHeaderParser, TryAuthenticate)) - { - context.Response.StatusCode = AuthenticationUtility.HttpNotAuthorizedStatusCode; - context.Response.Headers.Add(HeaderNames.WWWAuthenticate, FormattableString.Invariant($"{AuthenticationScheme} realm=\"{Options.Realm}\"")); - await context.WriteHttpNotAuthorizedBody(Options.HttpNotAuthorizedBody).ConfigureAwait(false); - return; - } - await Next.Invoke(context).ConfigureAwait(false); - } - - /// - /// Gets the name of the authentication scheme. - /// - /// The name of the authentication scheme. - public string AuthenticationScheme => "Basic"; - - private bool TryAuthenticate(HttpContext context, Template credentials, out ClaimsPrincipal result) - { - if (Options.Authenticator == null) { throw new InvalidOperationException(FormattableString.Invariant($"The {nameof(Options.Authenticator)} cannot be null.")); } - result = Options.Authenticator(credentials.Arg1, credentials.Arg2); - return Condition.IsNotNull(result); - } - - private Template AuthorizationHeaderParser(HttpContext context, string authorizationHeader) - { - if (AuthenticationUtility.IsAuthenticationSchemeValid(authorizationHeader, AuthenticationScheme)) - { - var base64Credentials = authorizationHeader.Remove(0, AuthenticationScheme.Length + 1); - if (Condition.IsBase64(base64Credentials)) - { - var credentials = Convertible.ToString(Convert.FromBase64String(base64Credentials), options => - { - options.Encoding = Encoding.ASCII; - options.Preamble = PreambleSequence.Remove; - }).Split(AuthenticationUtility.BasicAuthenticationCredentialSeparator); - if (credentials.Length == 2 && - !string.IsNullOrEmpty(credentials[0]) && - !string.IsNullOrEmpty(credentials[1])) - { return Template.CreateTwo(credentials[0], credentials[1]); } - } - } - return null; - } - } - - /// - /// This is a factory implementation of the class. - /// - public static class BasicAuthenticationBuilderExtension - { - /// - /// Adds a HTTP Basic Authentication scheme to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The HTTP middleware which need to be configured. - /// A reference to this instance after the operation has completed. - public static IApplicationBuilder UseBasicAuthentication(this IApplicationBuilder builder, Action setup = null) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Cuemon.AspNetCore.Authentication.csproj b/src/Cuemon.AspNetCore.Authentication/Cuemon.AspNetCore.Authentication.csproj index 45690205d..c7c46be4e 100644 --- a/src/Cuemon.AspNetCore.Authentication/Cuemon.AspNetCore.Authentication.csproj +++ b/src/Cuemon.AspNetCore.Authentication/Cuemon.AspNetCore.Authentication.csproj @@ -1,36 +1,21 @@ - + - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netcoreapp3.0;netstandard2.0 a10adf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.AspNetCore.Authentication Cuemon.AspNetCore.Authentication - The Cuemon.AspNetCore.Authentication assembly provides supplemental ways of authentication forms to Microsoft ASP.NET Core. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.AspNetCore.Authentication namespace contains types that enable support for authentication using the concept of an Authenticator, AuthorizationHeader and (to tie the knots) an AuthorizationHeaderBuilder. The namespace is an addition to the Microsoft.AspNetCore.Authentication namespace. + basic-authentication basic-authentication-middleware digest-access-authentication digest-authentication-middleware hmac-authentication hmac-authentication-middleware authenticator authorization-header authorization-header-builder nonce-tracker + + \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationMiddleware.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationMiddleware.cs new file mode 100644 index 000000000..dcab39d81 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationMiddleware.cs @@ -0,0 +1,132 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Cuemon.IO; +using Cuemon.Security.Cryptography; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.AspNetCore.Authentication.Digest +{ + /// + /// Provides a HTTP Digest Access Authentication middleware implementation for ASP.NET Core. + /// + public class DigestAuthenticationMiddleware : ConfigurableMiddleware + { + private INonceTracker _nonceTracker; + + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The which need to be configured. + public DigestAuthenticationMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The middleware which need to be configured. + public DigestAuthenticationMiddleware(RequestDelegate next, Action setup) : base(next, setup) + { + } + + /// + /// Executes the . + /// + /// The context of the current request. + /// The dependency injected implementation of an . + /// A task that represents the execution of this middleware. + public override async Task InvokeAsync(HttpContext context, INonceTracker nonceTracker) + { + _nonceTracker = nonceTracker; + if (!Authenticator.TryAuthenticate(context, Options.RequireSecureConnection, AuthorizationHeaderParser, TryAuthenticate)) + { + await Decorator.Enclose(context).InvokeAuthenticationAsync(Options, async (message, response) => + { + context.Response.OnStarting(() => + { + string etag = context.Response.Headers[HeaderNames.ETag]; + if (string.IsNullOrEmpty(etag)) { etag = "no-entity-tag"; } + var opaqueGenerator = Options.OpaqueGenerator; + var nonceSecret = Options.NonceSecret; + var nonceGenerator = Options.NonceGenerator; + var staleNonce = context.Items[DigestFields.Stale] as string ?? "false"; + context.Response.Headers.Add(HeaderNames.WWWAuthenticate, FormattableString.Invariant($"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale=\"{staleNonce}\", algorithm=\"{ParseAlgorithm(Options.Algorithm)}\"")); + return Task.CompletedTask; + }); + response.StatusCode = (int)message.StatusCode; + await Decorator.Enclose(response.Body).WriteAsync(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ConfigureAwait(false); + }).ConfigureAwait(false); + } + await Next.Invoke(context).ConfigureAwait(false); + } + + private bool TryAuthenticate(HttpContext context, DigestAuthorizationHeader header, out ClaimsPrincipal result) + { + if (Options.Authenticator == null) { throw new InvalidOperationException(FormattableString.Invariant($"The {nameof(Options.Authenticator)} delegate cannot be null.")); } + + if (header == null) + { + result = null; + return false; + } + + result = null; + var nonceExpiredParser = Options.NonceExpiredParser; + var staleNonce = nonceExpiredParser(header.Nonce, TimeSpan.FromSeconds(30)); + if (staleNonce) + { + context.Items.Add(DigestFields.Stale, "true"); + return false; + } + + if (_nonceTracker != null) + { + var nc = Convert.ToInt32(header.NC, 16); + if (_nonceTracker.TryGetEntry(header.Nonce, out var previousNonce)) + { + if (previousNonce.Count == nc) + { + context.Items.Add(DigestFields.Stale, "true"); + return false; + } + } + else + { + _nonceTracker.TryAddEntry(header.Nonce, nc); + } + } + + result = Options.Authenticator(header.UserName, out var password); + + var db = new DigestAuthorizationHeaderBuilder().AddFromDigestAuthorizationHeader(header); + var ha1 = db.ComputeHash1(password); + var ha2 = db.ComputeHash2(context.Request.Method, Decorator.Enclose(context.Request.Body).ToEncodedString(o => o.LeaveOpen = true)); + var serverResponse = db.ComputeResponse(ha1, ha2); + + return serverResponse != null && serverResponse.Equals(header.Response, StringComparison.Ordinal) && Condition.IsNotNull(result); + } + + private DigestAuthorizationHeader AuthorizationHeaderParser(HttpContext context, string authorizationHeader) + { + return DigestAuthorizationHeader.Create(authorizationHeader); + } + + private static string ParseAlgorithm(UnkeyedCryptoAlgorithm algorithm) + { + switch (algorithm) + { + case UnkeyedCryptoAlgorithm.Sha256: + return "SHA-256"; + case UnkeyedCryptoAlgorithm.Sha512: + return "SHA-512-256"; + default: + return "MD5"; + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationOptions.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationOptions.cs new file mode 100644 index 000000000..9879a5146 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationOptions.cs @@ -0,0 +1,134 @@ +using System; +using System.Globalization; +using System.Text; +using Cuemon.Security.Cryptography; +using Cuemon.Text; + +namespace Cuemon.AspNetCore.Authentication.Digest +{ + /// + /// Configuration options for . This class cannot be inherited. + /// + /// + public sealed class DigestAuthenticationOptions : AuthenticationOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// + /// + /// + /// + /// null + /// + /// + /// + /// A default implementation of a nonce generator. + /// + /// + /// + /// A default implementation of an opaque generator. + /// + /// + /// + /// A default implementation of a nonce expiry parser. + /// + /// + /// + /// A default secret to get you started without overwhelming configuration. Do change when moving outside a development environment. + /// + /// + /// + /// AuthenticationServer + /// + /// + /// + public DigestAuthenticationOptions() + { + Algorithm = UnkeyedCryptoAlgorithm.Sha256; + OpaqueGenerator = () => Generate.RandomString(32, Alphanumeric.Hexadecimal).ToLowerInvariant(); + NonceExpiredParser = (nonce, timeToLive) => + { + Validator.ThrowIfNullOrEmpty(nonce, nameof(nonce)); + if (ParserFactory.FromBase64().TryParse(nonce, out var rawNonce)) + { + var nonceProtocol = Convertible.ToString(rawNonce, options => + { + options.Encoding = Encoding.UTF8; + options.Preamble = PreambleSequence.Remove; + }); + var nonceTimestamp = DateTime.ParseExact(nonceProtocol.Substring(0, nonceProtocol.LastIndexOf(':')), "u", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + var difference = (DateTime.UtcNow - nonceTimestamp); + return (difference > timeToLive); + } + return false; + }; + NonceGenerator = (timestamp, entityTag, privateKey) => + { + Validator.ThrowIfNullOrWhitespace(entityTag, nameof(entityTag)); + Validator.ThrowIfNull(privateKey, nameof(privateKey)); + var nonceHash = UnkeyedHashFactory.CreateCryptoSha256().ComputeHash(timestamp.Ticks, entityTag, Convert.ToBase64String(privateKey)).ToHexadecimalString(); + var nonceProtocol = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", timestamp.ToString("u", CultureInfo.InvariantCulture), nonceHash); + return Convert.ToBase64String(Convertible.GetBytes(nonceProtocol, options => + { + options.Encoding = Encoding.UTF8; + options.Preamble = PreambleSequence.Remove; + })); + }; + NonceSecret = () => Convert.FromBase64String("ZHBGWDRrVGVxbFlhVEpWQ3hoYUc5VUlZM05penNOaUk="); + Realm = "AuthenticationServer"; + } + + /// + /// Gets or sets the function delegate that will perform the authentication from the specified username. + /// + /// The function delegate that will perform the authentication. + public DigestAuthenticator Authenticator { get; set; } + + /// + /// Gets or sets the algorithm of the HTTP Digest Access Authentication. Default is . + /// + /// The algorithm of the HTTP Digest Access Authentication. + /// Allowed values are: , and . + public UnkeyedCryptoAlgorithm Algorithm { get; set; } + + /// + /// Gets the realm that defines the protection space. + /// + /// The realm that defines the protection space. + public string Realm { get; set; } + + /// + /// Gets or sets the function delegate for generating opaque string values. + /// + /// The function delegate for generating opaque string values. + public Func OpaqueGenerator { get; set; } + + /// + /// Gets or sets the function delegate for retrieving the cryptographic secret used in nonce string values. + /// + /// The function delegate for retrieving the cryptographic secret used in nonce string values. + public Func NonceSecret { get; set; } + + /// + /// Gets or sets the function delegate for generating nonce string values. + /// + /// The function delegate for generating nonce string values. + public Func NonceGenerator { get; set; } + + /// + /// Gets or sets the function delegate for parsing nonce string values for expiration. + /// + /// The function delegate for parsing nonce string values for expiration. + public Func NonceExpiredParser { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticator.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticator.cs similarity index 68% rename from src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticator.cs rename to src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticator.cs index f87945b2d..5ade72881 100644 --- a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticator.cs +++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticator.cs @@ -1,12 +1,12 @@ using System.Security.Claims; -namespace Cuemon.AspNetCore.Authentication +namespace Cuemon.AspNetCore.Authentication.Digest { /// - /// Represents the method that defines an Authenticator typically assigned on . + /// Represents the method that defines an Authenticator typically assigned on . /// /// The username to match and lookup the paired . /// The password paired with . /// A that is associated with the result of . - public delegate ClaimsPrincipal DigestAccessAuthenticator(string username, out string password); + public delegate ClaimsPrincipal DigestAuthenticator(string username, out string password); } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeader.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeader.cs new file mode 100644 index 000000000..5be734edd --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeader.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Cuemon.AspNetCore.Authentication.Digest +{ + /// + /// Provides a representation of a HTTP Digest Access Authentication header. + /// Implements the + /// + /// + public class DigestAuthorizationHeader : AuthorizationHeader + { + /// + /// Creates an instance of from the specified parameters. + /// + /// The raw HTTP authorization header. + /// An instance of . + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + public static DigestAuthorizationHeader Create(string authorizationHeader) + { + Validator.ThrowIfNullOrWhitespace(authorizationHeader, nameof(authorizationHeader)); + return new DigestAuthorizationHeader().Parse(authorizationHeader, o => o.CredentialsDelimiter = " ") as DigestAuthorizationHeader; + } + + /// + /// The authentication scheme of the . + /// + public const string Scheme = "Digest"; + + DigestAuthorizationHeader() : base(Scheme) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The realm/credential scope that defines the remote resource. + /// The unique server generated string. + /// The string of data specified by the server. + /// The case-insensitive flag indicating if the previous request from the client was rejected because the value was stale. + /// The algorithm used to produce the digest and an unkeyed digest. + /// The username of the specified . + /// The effective request URI. + /// The hexadecimal count of the number of requests the client has sent with the value. + /// The unique client generated string. + /// The "quality of protection" the client has applied to the message. + /// The computed response which proves that the user knows a password. + public DigestAuthorizationHeader(string realm, string nonce, string opaque, string stale, string algorithm, string userName, string uri, string nc, string cNonce, string qop, string response) : base(Scheme) + { + Realm = realm; + Nonce = nonce; + Opaque = opaque; + Stale = stale; + Algorithm = algorithm; + UserName = userName; + Uri = uri; + NC = nc; + CNonce = cNonce; + Qop = qop; + Response = response; + } + + /// + /// Gets the realm/credential scope that defines the remote resource. + /// + /// The realm/credential scope that defines the remote resource. + public string Realm { get; } + + /// + /// Gets the unique server generated string. + /// + /// The unique server generated string. + public string Nonce { get; } + + /// + /// Gets the string of data specified by the server. + /// + /// The string of data specified by the server. + public string Opaque { get; } + + /// + /// Gets the algorithm used to produce the digest and an unkeyed digest. + /// + /// The algorithm used to produce the digest and an unkeyed digest. + public string Algorithm { get; } + + /// + /// Gets the username of the specified . + /// + /// The username of the specified . + public string UserName { get; } + + /// + /// Gets the effective request URI. + /// + /// The effective request URI. + public string Uri { get; } + + /// + /// Gets the computed response which proves that the user knows a password. + /// + /// The computed response which proves that the user knows a password. + public string Response { get; } + + /// + /// Gets the "quality of protection" the client has applied to the message. + /// + /// The "quality of protection" the client has applied to the message. + public string Qop { get; } + + /// + /// Gets the unique client generated string. + /// + /// The unique client generated string. + public string CNonce { get; } + + /// + /// Gets the hexadecimal count of the number of requests the client has sent with the value. + /// + /// The hexadecimal count of the number of requests the client has sent with the value. + public string NC { get; } + + /// + /// Gets the case-insensitive flag indicating if the previous request from the client was rejected because the value was stale. + /// + /// The case-insensitive flag indicating if the previous request from the client was rejected because the value was stale. + public string Stale { get; } + + /// + /// The core parser that resolves an from a set of . + /// + /// The credentials used in authentication. + /// An equivalent of . + protected override AuthorizationHeader ParseCore(IReadOnlyDictionary credentials) + { + var valid = credentials.TryGetValue(DigestFields.Realm, out var realm); + valid |= credentials.TryGetValue(DigestFields.Nonce, out var nonce); + valid |= credentials.TryGetValue(DigestFields.Opaque, out var opaque); + valid |= credentials.TryGetValue(DigestFields.Algorithm, out var algorithm); + valid |= credentials.TryGetValue(DigestFields.UserName, out var userName); + valid |= credentials.TryGetValue(DigestFields.DigestUri, out var uri); + valid |= credentials.TryGetValue(DigestFields.Response, out var response); + valid |= credentials.TryGetValue(DigestFields.QualityOfProtection, out var qop); + valid |= credentials.TryGetValue(DigestFields.ClientNonce, out var cnonce); + valid |= credentials.TryGetValue(DigestFields.NonceCount, out var nc); + valid |= credentials.TryGetValue(DigestFields.Stale, out var stale); + return valid ? new DigestAuthorizationHeader(realm, nonce, opaque, stale, algorithm, userName, uri, nc, cnonce, qop, response) : null; + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + var sb = new StringBuilder(AuthenticationScheme); + AppendField(sb, DigestFields.UserName, UserName); + AppendField(sb, DigestFields.Realm, Realm); + AppendField(sb, DigestFields.Nonce, Nonce); + AppendField(sb, DigestFields.DigestUri, Uri); + AppendField(sb, DigestFields.QualityOfProtection, Qop); + AppendField(sb, DigestFields.NonceCount, NC); + AppendField(sb, DigestFields.ClientNonce, CNonce); + AppendField(sb, DigestFields.Response, Response); + AppendField(sb, DigestFields.Opaque, Opaque); + AppendField(sb, DigestFields.Stale, Stale); + AppendField(sb, DigestFields.Algorithm, Algorithm); + return sb.ToString(); + } + + private void AppendField(StringBuilder sb, string fn, string fv) + { + if (!string.IsNullOrWhiteSpace(fv)) { sb.Append($" {fn}=\"{fv}\""); } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeaderBuilder.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeaderBuilder.cs new file mode 100644 index 000000000..856110eb0 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeaderBuilder.cs @@ -0,0 +1,257 @@ +using System; +using System.Globalization; +using System.Text; +using Cuemon.Security.Cryptography; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.AspNetCore.Authentication.Digest +{ + /// + /// Provides a way to fluently represent a HTTP Digest Access Authentication header. + /// + public class DigestAuthorizationHeaderBuilder : AuthorizationHeaderBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The algorithm to use when computing HA1, HA2 and/or RESPONSE value(s). + /// Allowed values for are: , and . + public DigestAuthorizationHeaderBuilder(UnkeyedCryptoAlgorithm algorithm = UnkeyedCryptoAlgorithm.Sha256) : base(DigestAuthorizationHeader.Scheme) + { + Validator.ThrowIfEqual(algorithm, UnkeyedCryptoAlgorithm.Sha1, nameof(algorithm)); + Validator.ThrowIfEqual(algorithm, UnkeyedCryptoAlgorithm.Sha384, nameof(algorithm)); + Algorithm = algorithm; + MapRelation(nameof(AddResponse), DigestFields.Response); + MapRelation(nameof(AddRealm), DigestFields.Realm); + MapRelation(nameof(AddUserName), DigestFields.UserName); + MapRelation(nameof(AddUri), DigestFields.DigestUri); + MapRelation(nameof(AddNc), DigestFields.NonceCount); + MapRelation(nameof(AddCnonce), DigestFields.ClientNonce); + MapRelation(nameof(AddQopAuthentication), DigestFields.QualityOfProtection); + MapRelation(nameof(AddQopAuthenticationIntegrity), DigestFields.QualityOfProtection); + MapRelation(nameof(ComputeHash1), DigestFields.UserName, DigestFields.Realm); + MapRelation(nameof(ComputeHash2), DigestFields.QualityOfProtection, DigestFields.DigestUri); + MapRelation(nameof(ComputeResponse), DigestFields.Nonce, DigestFields.NonceCount, DigestFields.ClientNonce, DigestFields.QualityOfProtection); + } + + /// + /// Gets the algorithm of the HTTP Digest Access Authentication. + /// + /// The algorithm of the HTTP Digest Access Authentication. + public UnkeyedCryptoAlgorithm Algorithm { get; private set; } + + /// + /// Associates the field with the specified . + /// + /// The realm that defines the remote resource. + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddRealm(string realm) + { + return AddOrUpdate(DigestFields.Realm, realm); + } + + /// + /// Associates the field with the specified . + /// + /// The username to use in the authentication process. + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddUserName(string username) + { + Validator.ThrowIfNullOrWhitespace(username, nameof(username)); + return AddOrUpdate(DigestFields.UserName, username); + } + + /// + /// Associates the field with the specified . + /// + /// The effective request URI to use in the authentication process. + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddUri(string digestUri) + { + Validator.ThrowIfNullOrWhitespace(digestUri, nameof(digestUri)); + return AddOrUpdate(DigestFields.DigestUri, digestUri); + } + + /// + /// Associates the field with the specified . + /// + /// The count of the number of requests to use in the authentication process. + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddNc(int nonceCount) + { + Validator.ThrowIfLowerThan(nonceCount, 0, nameof(nonceCount)); + return AddOrUpdate(DigestFields.NonceCount, nonceCount.ToString("x8")); + } + + /// + /// Associates the field with the specified . + /// + /// The cryptographic client nonce to use in the authentication process. + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddCnonce(string clientNonce = null) + { + if (clientNonce == null) { clientNonce = Generate.RandomString(32); } + return AddOrUpdate(DigestFields.ClientNonce, clientNonce); + } + + /// + /// Associates the field with "auth". + /// + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddQopAuthentication() + { + return AddOrUpdate(DigestFields.QualityOfProtection, "auth"); + } + + /// + /// Associates the field with "auth-int". + /// + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddQopAuthenticationIntegrity() + { + return AddOrUpdate(DigestFields.QualityOfProtection, "auth-int"); + } + + /// + /// Associates any Digest fields found in the HTTP WWW-Authenticate header from the specified . + /// + /// An implementation of . + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddFromWwwAuthenticateHeader(IHeaderDictionary headers) + { + Validator.ThrowIfNull(headers, nameof(headers)); + string header = headers[HeaderNames.WWWAuthenticate]; + Validator.ThrowIfFalse(() => header.StartsWith(AuthenticationScheme), nameof(header), $"Header did not start with {AuthenticationScheme}."); + var headerWithoutScheme = header.Remove(0, AuthenticationScheme.Length + 1); + + var fields = DelimitedString.Split(headerWithoutScheme); + foreach (var field in fields) + { + var kvp = DelimitedString.Split(field, o => o.Delimiter = "="); + var key = kvp[0].Trim(); + var value = kvp[1].Trim('"'); + if (key == DigestFields.QualityOfProtection) { continue; } + AddOrUpdate(key, value); + } + return this; + } + + /// + /// Associates any Digest fields found in the HTTP Authorization header from the specified . + /// + /// An instance of . + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddFromDigestAuthorizationHeader(DigestAuthorizationHeader header) + { + Validator.ThrowIfNull(header, nameof(header)); + Algorithm = ParseAlgorithm(header.Algorithm); + AddUserName(header.UserName); + AddRealm(header.Realm); + AddUri(header.Uri); + AddNc(Convert.ToInt32(header.NC, 16)); + AddCnonce(header.CNonce); + AddOrUpdate(DigestFields.Nonce, header.Nonce); + Condition.FlipFlop(header.Qop.Equals("auth-int", StringComparison.OrdinalIgnoreCase), () => AddQopAuthenticationIntegrity(), () => AddQopAuthentication()); + return this; + } + + private static UnkeyedCryptoAlgorithm ParseAlgorithm(string algorithm) + { + return algorithm.StartsWith("SHA-512", StringComparison.OrdinalIgnoreCase) ? UnkeyedCryptoAlgorithm.Sha512 + : algorithm.StartsWith("SHA-256", StringComparison.OrdinalIgnoreCase) ? UnkeyedCryptoAlgorithm.Sha256 + : UnkeyedCryptoAlgorithm.Md5; + } + + /// + /// Associates the field with the computed values of , and . + /// + /// The password to include in the HA1 computed value. + /// The HTTP method to include in the HA2 computed value. + /// The entity body to apply in the signature when qop is set to auth-int. + /// An that can be used to further build the HTTP Digest Access Authentication header. + public DigestAuthorizationHeaderBuilder AddResponse(string password, string method, string entityBody = null) + { + Validator.ThrowIfNullOrWhitespace(password, nameof(password)); + Validator.ThrowIfNullOrWhitespace(method, nameof(method)); + ValidateData(DigestFields.UserName, DigestFields.Realm, DigestFields.QualityOfProtection, DigestFields.DigestUri, DigestFields.Nonce, DigestFields.NonceCount, DigestFields.ClientNonce); + return AddOrUpdate(DigestFields.Response, ComputeResponse(ComputeHash1(password), ComputeHash2(method, entityBody))); + } + + /// + /// Computes a by parameter defined hash value of the required values for the HTTP Digest access authentication HA1. + /// + /// The password to include in the HA1 computed value. + /// A in the format of H(::). H is determined by . + public virtual string ComputeHash1(string password) + { + Validator.ThrowIfNullOrWhitespace(password, nameof(password)); + ValidateData(DigestFields.UserName, DigestFields.Realm); + return UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", Data[DigestFields.UserName], Data[DigestFields.Realm], password), o => + { + o.Encoding = Encoding.UTF8; + }).ToHexadecimalString(); + } + + /// + /// Computes a by parameter defined hash value of the required values for the HTTP Digest access authentication HA2. + /// + /// The HTTP method to include in the HA2 computed value. + /// The entity body to apply in the signature when qop is set to auth-int. + /// A in the format of H(:) OR H(::H()). H is determined by . + public virtual string ComputeHash2(string method, string entityBody = null) + { + Validator.ThrowIfNullOrWhitespace(method, nameof(method)); + method = method.ToUpperInvariant(); + ValidateData(DigestFields.QualityOfProtection, DigestFields.DigestUri); + var qop = Data[DigestFields.QualityOfProtection]; + var hasIntegrityProtection = qop.Equals("auth-int", StringComparison.OrdinalIgnoreCase); + if (hasIntegrityProtection && entityBody == null) { throw new ArgumentNullException(nameof(entityBody), "The entity body cannot be null when qop is set to auth-int."); } + + var hashFields = !hasIntegrityProtection + ? FormattableString.Invariant($"{method}:{Data[DigestFields.DigestUri]}") + : FormattableString.Invariant($"{method}:{Data[DigestFields.DigestUri]}:{UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(entityBody, o => o.Encoding = Encoding.UTF8).ToHexadecimalString()}"); + return UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(hashFields, o => + { + o.Encoding = Encoding.UTF8; + }).ToHexadecimalString(); + } + + /// + /// Computes a by parameter defined hash value of the required values for the HTTP Digest access authentication RESPONSE. + /// + /// The HA1 to include in the RESPONSE computed value. + /// The HA2 to include in the RESPONSE computed value. + /// A in the format of H(:::::). H is determined by . + public virtual string ComputeResponse(string hash1, string hash2) + { + Validator.ThrowIfNullOrWhitespace(hash1, nameof(hash1)); + Validator.ThrowIfNullOrWhitespace(hash2, nameof(hash2)); + ValidateData(DigestFields.Nonce, DigestFields.NonceCount, DigestFields.ClientNonce, DigestFields.QualityOfProtection); + return UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(FormattableString.Invariant($"{hash1}:{Data[DigestFields.Nonce]}:{Data[DigestFields.NonceCount]}:{Data[DigestFields.ClientNonce]}:{Data[DigestFields.QualityOfProtection]}:{hash2}"), o => + { + o.Encoding = Encoding.UTF8; + }).ToHexadecimalString(); + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override DigestAuthorizationHeader Build() + { + ValidateData(DigestFields.Realm, DigestFields.Nonce, DigestFields.UserName, DigestFields.QualityOfProtection, DigestFields.DigestUri, DigestFields.NonceCount, DigestFields.ClientNonce, DigestFields.QualityOfProtection, DigestFields.Response); + return new DigestAuthorizationHeader(Data[DigestFields.Realm], + Data[DigestFields.Nonce], + Data[DigestFields.Opaque], + Data[DigestFields.Stale], + Data[DigestFields.Algorithm], + Data[DigestFields.UserName], + Data[DigestFields.DigestUri], + Data[DigestFields.NonceCount], + Data[DigestFields.ClientNonce], + Data[DigestFields.QualityOfProtection], + Data[DigestFields.Response]); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestFields.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestFields.cs new file mode 100644 index 000000000..b80d423e1 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestFields.cs @@ -0,0 +1,63 @@ +namespace Cuemon.AspNetCore.Authentication.Digest +{ + /// + /// A collection of constants for . + /// + public static class DigestFields + { + /// + /// The username field of a HTTP Digest access authentication. + /// + public const string UserName = "username"; + + /// + /// The realm field of a HTTP Digest access authentication. + /// + public const string Realm = "realm"; + + /// + /// The response field of a HTTP Digest access authentication. + /// + public const string Response = "response"; + + /// + /// The qop (quality of protection) field of a HTTP Digest access authentication. + /// + public const string QualityOfProtection = "qop"; + + /// + /// The client nonce (cnonce) field of a HTTP Digest access authentication. + /// + public const string ClientNonce = "cnonce"; + + /// + /// The nc (nonce count) field of a HTTP Digest access authentication. + /// + public const string NonceCount = "nc"; + + /// + /// The nonce field of a HTTP Digest access authentication. + /// + public const string Nonce = "nonce"; + + /// + /// The uri (digest URI) field of a HTTP Digest access authentication. + /// + public const string DigestUri = "uri"; + + /// + /// The opaque field of a HTTP Digest access authentication. + /// + public const string Opaque = "opaque"; + + /// + /// The algorithm field of a HTTP Digest access authentication. + /// + public const string Algorithm = "algorithm"; + + /// + /// The stale field of a HTTP Digest access authentication. + /// + public const string Stale = "stale"; + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationMiddleware.cs b/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationMiddleware.cs deleted file mode 100644 index a9b201ba6..000000000 --- a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationMiddleware.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; -using Cuemon.AspNetCore.Builder; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Provides a HTTP Digest Access Authentication middleware implementation for ASP.NET Core. - /// - public class DigestAccessAuthenticationMiddleware : ConfigurableMiddleware - { - private static readonly ConcurrentDictionary> NonceCounter = new ConcurrentDictionary>(); - private static Timer _nonceCounterSweeper; - - /// - /// Initializes a new instance of the class. - /// - /// The delegate of the request pipeline to invoke. - /// The which need to be configured. - public DigestAccessAuthenticationMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) - { - InitializeNonceCounterSweeper(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The delegate of the request pipeline to invoke. - /// The middleware which need to be configured. - public DigestAccessAuthenticationMiddleware(RequestDelegate next, Action setup) : base(next, setup) - { - InitializeNonceCounterSweeper(); - } - - private static void InitializeNonceCounterSweeper() - { - _nonceCounterSweeper = new Timer(s => - { - var utcStaleTimestamp = DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(5)); - var staledEntries = NonceCounter.Where(pair => pair.Value.Arg1 <= utcStaleTimestamp).ToList(); - foreach (var staledEntry in staledEntries) - { - NonceCounter.TryRemove(staledEntry.Key, out _); - } - }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(2)); - } - - /// - /// Executes the . - /// - /// The context of the current request. - /// A task that represents the execution of this middleware. - public override async Task InvokeAsync(HttpContext context) - { - if (!AuthenticationUtility.TryAuthenticate(context, Options.RequireSecureConnection, AuthorizationHeaderParser, TryAuthenticate)) - { - context.Response.StatusCode = AuthenticationUtility.HttpNotAuthorizedStatusCode; - string etag = context.Response.Headers[HeaderNames.ETag]; - if (string.IsNullOrEmpty(etag)) { etag = "no-entity-tag"; } - var opaqueGenerator = Options.OpaqueGenerator; - var nonceSecret = Options.NonceSecret; - var nonceGenerator = Options.NonceGenerator; - var staleNonce = context.Items["staleNonce"] as string ?? "FALSE"; - context.Response.Headers.Add(HeaderNames.WWWAuthenticate, FormattableString.Invariant($"{AuthenticationScheme} realm=\"{Options.Realm}\", qop=\"{DigestAuthenticationUtility.CredentialQualityOfProtectionOptions}\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale=\"{staleNonce}\", algorithm=\"{DigestAuthenticationUtility.ParseAlgorithm(Options.Algorithm)}\"")); - await context.WriteHttpNotAuthorizedBody(Options.HttpNotAuthorizedBody).ConfigureAwait(false); - return; - } - await Next.Invoke(context).ConfigureAwait(false); - } - - /// - /// Gets the name of the authentication scheme. - /// - /// The name of the authentication scheme. - public string AuthenticationScheme => "Digest"; - - private bool TryAuthenticate(HttpContext context, Dictionary credentials, out ClaimsPrincipal result) - { - if (Options.Authenticator == null) { throw new InvalidOperationException(FormattableString.Invariant($"The {nameof(Options.Authenticator)} delegate cannot be null.")); } - credentials.TryGetValue(DigestAuthenticationUtility.CredentialUserName, out var userName); - credentials.TryGetValue(DigestAuthenticationUtility.CredentialResponse, out var clientResponse); - credentials.TryGetValue(DigestAuthenticationUtility.CredentialNonceCount, out var nonceCount); - if (credentials.TryGetValue(DigestAuthenticationUtility.CredentialNonce, out var nonce)) - { - result = null; - var nonceExpiredParser = Options.NonceExpiredParser; - var staleNonce = nonceExpiredParser(nonce, TimeSpan.FromSeconds(30)); - context.Items["staleNonce"] = staleNonce.ToString().ToUpperInvariant(); - if (staleNonce) { return false; } - - if (NonceCounter.TryGetValue(nonce, out var previousNonce)) - { - if (previousNonce.Arg2.Equals(nonceCount, StringComparison.Ordinal)) { return false; } - } - else - { - NonceCounter.TryAdd(nonce, Template.CreateTwo(DateTime.UtcNow, nonceCount)); - } - } - result = Options.Authenticator(userName, out var password); - - var serverResponse = Options?.DigestAccessSigner(new DigestAccessAuthenticationParameters(credentials.ToImmutableDictionary(), context.Request.Method, password, Options.Algorithm)); - return serverResponse != null && StringFactory.CreateHexadecimal(serverResponse).Equals(clientResponse, StringComparison.Ordinal) && Condition.IsNotNull(result); - } - - private Dictionary AuthorizationHeaderParser(HttpContext context, string authorizationHeader) - { - if (AuthenticationUtility.IsAuthenticationSchemeValid(authorizationHeader, AuthenticationScheme)) - { - var digestCredentials = authorizationHeader.Remove(0, AuthenticationScheme.Length + 1); - var credentials = digestCredentials.Split(AuthenticationUtility.DigestAuthenticationCredentialSeparator); - if (IsDigestCredentialsValid(credentials)) - { - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - for (var i = 0; i < credentials.Length; i++) - { - var credentialPair = DelimitedString.Split(credentials[i], o => o.Qualifier = "="); - result.Add(credentialPair[0].Trim(), QuotedStringParser(credentialPair[1])); - } - return IsDigestCredentialsValid(result) ? result : null; - } - } - return new Dictionary(); - } - - private static bool IsDigestCredentialsValid(Dictionary credentials) - { - var valid = credentials.ContainsKey("username"); - valid |= credentials.ContainsKey("realm"); - valid |= credentials.ContainsKey("nonce"); - valid |= credentials.ContainsKey("uri"); - valid |= credentials.ContainsKey("response"); - return valid; - } - - private string QuotedStringParser(string value) - { - if (value.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && - value.EndsWith("\"", StringComparison.OrdinalIgnoreCase)) - { - value = value.Trim('"'); - } - return value.Trim(); - } - - private static bool IsDigestCredentialsValid(string[] credentials) - { - var valid = (credentials.Length >= 5 && credentials.Length <= 10); - for (var i = 0; i < credentials.Length; i++) - { - valid |= !string.IsNullOrEmpty(credentials[i]); - } - return valid; - } - } - - /// - /// This is a factory implementation of the class. - /// - public static class DigestAccessAuthenticationBuilderExtension - { - /// - /// Adds a HTTP Digest Authentication scheme to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The HTTP middleware which need to be configured. - /// A reference to this instance after the operation has completed. - public static IApplicationBuilder UseDigestAccessAuthentication(this IApplicationBuilder builder, Action setup = null) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationOptions.cs b/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationOptions.cs deleted file mode 100644 index c7cb36ee2..000000000 --- a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationOptions.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using Cuemon.Security.Cryptography; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Configuration options for . This class cannot be inherited. - /// - /// - public sealed class DigestAccessAuthenticationOptions : AuthenticationOptions - { - /// - /// Initializes a new instance of the class. - /// - public DigestAccessAuthenticationOptions() - { - Algorithm = CryptoAlgorithm.Md5; - OpaqueGenerator = DigestAuthenticationUtility.DefaultOpaqueGenerator; - NonceExpiredParser = DigestAuthenticationUtility.DefaultNonceExpiredParser; - NonceGenerator = DigestAuthenticationUtility.DefaultNonceGenerator; - NonceSecret = () => DigestAuthenticationUtility.DefaultPrivateKey; - DigestAccessSigner = parameters => - { - var ha1 = DigestAuthenticationUtility.ComputeHash1(parameters.Credentials, parameters.Password, parameters.Algorithm); - var ha2 = DigestAuthenticationUtility.ComputeHash2(parameters.Credentials, parameters.HttpMethod, parameters.Algorithm); - return DigestAuthenticationUtility.ComputeResponse(parameters.Credentials, ha1, ha2, parameters.Algorithm); - }; - } - - /// - /// Gets or sets the function delegate that will perform the authentication from the specified username. - /// - /// The function delegate that will perform the authentication. - public DigestAccessAuthenticator Authenticator { get; set; } - - /// - /// Gets or sets the function delegate that will sign a message retrieved from a HTTP request. - /// - /// The function delegate that will sign a message. - public Func DigestAccessSigner { get; set; } - - /// - /// Gets or sets the algorithm of the HTTP Digest Access Authentication. Default is . - /// - /// The algorithm of the HTTP Digest Access Authentication. - public CryptoAlgorithm Algorithm { get; set; } - - /// - /// Gets the realm that defines the protection space. - /// - /// The realm that defines the protection space. - public string Realm { get; set; } - - /// - /// Gets or sets the function delegate for generating opaque string values. - /// - /// The function delegate for generating opaque string values. - public Func OpaqueGenerator { get; set; } - - /// - /// Gets or sets the function delegate for retrieving the cryptographic secret used in nonce string values. - /// - /// The function delegate for retrieving the cryptographic secret used in nonce string values. - public Func NonceSecret { get; set; } - - /// - /// Gets or sets the function delegate for generating nonce string values. - /// - /// The function delegate for generating nonce string values. - public Func NonceGenerator { get; set; } - - /// - /// Gets or sets the function delegate for parsing nonce string values for expiration. - /// - /// The function delegate for parsing nonce string values for expiration. - public Func NonceExpiredParser { get; set; } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationParameters.cs b/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationParameters.cs deleted file mode 100644 index 534429b33..000000000 --- a/src/Cuemon.AspNetCore.Authentication/DigestAccessAuthenticationParameters.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Immutable; -using Cuemon.Security.Cryptography; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Represents a set of parameters that is needed for creating an application of cryptographic hashing with usage of nonce values to prevent replay attacks. - /// - public class DigestAccessAuthenticationParameters - { - /// - /// Initializes a new instance of the class. - /// - /// The credentials used in the computation of HA1-, HA2-, and response hash values. - /// The HTTP method to include in the HA2 computed value. - /// The password to include in the HA1 computed value. - /// The algorithm to use when computing the HA1-, HA2-, and response hash values. - internal DigestAccessAuthenticationParameters(ImmutableDictionary credentials, string httpMethod, string password, CryptoAlgorithm algorithm) - { - Credentials = credentials; - HttpMethod = httpMethod; - Password = password; - Algorithm = algorithm; - } - - /// - /// Gets the credentials used in the computation of HA1-, HA2-, and response hash values. - /// - /// The credentials used in the computation of HA1-, HA2-, and response hash values. - public ImmutableDictionary Credentials { get; } - - /// - /// Gets the HTTP method to include in the HA2 computed value. - /// - /// The HTTP method to include in the HA2 computed value. - public string HttpMethod { get; } - - /// - /// Gets the password to include in the HA1 computed value. - /// - /// The password to include in the HA1 computed value. - public string Password { get; } - - /// - /// Gets the algorithm to use when computing the HA1-, HA2-, and response hash values. - /// - /// The algorithm to use when computing the HA1-, HA2-, and response hash values. - public CryptoAlgorithm Algorithm { get; } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/DigestAuthenticationUtility.cs b/src/Cuemon.AspNetCore.Authentication/DigestAuthenticationUtility.cs deleted file mode 100644 index a80ddfc46..000000000 --- a/src/Cuemon.AspNetCore.Authentication/DigestAuthenticationUtility.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; -using Cuemon.Security.Cryptography; -using Cuemon.Text; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Provides an isolated set of members to work with HTTP Digest access authentication. - /// - public static class DigestAuthenticationUtility - { - internal static readonly byte[] DefaultPrivateKey = Convert.FromBase64String("ZHBGWDRrVGVxbFlhVEpWQ3hoYUc5VUlZM05penNOaUk="); - - /// - /// The value of the header credential user name of a HTTP Digest access authentication. - /// - public const string CredentialUserName = "username"; - - /// - /// The value of the header credential realm of a HTTP Digest access authentication. - /// - public const string CredentialRealm = "realm"; - - /// - /// The value of the header credential response of a HTTP Digest access authentication. - /// - public const string CredentialResponse = "response"; - - /// - /// The value of the header credential quality of protection of a HTTP Digest access authentication. - /// - public const string CredentialQualityOfProtection = "qop"; - - /// - /// The value of the header credential quality of protection options of a HTTP Digest access authentication. - /// - public const string CredentialQualityOfProtectionOptions = "auth,auth-int"; - - /// - /// The value of the header credential client nonce of a HTTP Digest access authentication. - /// - public const string CredentialClientNonce = "cnonce"; - - /// - /// The value of the header credential nonce count of a HTTP Digest access authentication. - /// - public const string CredentialNonceCount = "nc"; - - /// - /// The value of the header credential nonce of a HTTP Digest access authentication. - /// - public const string CredentialNonce = "nonce"; - - /// - /// The value of the header credential digest URI of a HTTP Digest access authentication. - /// - public const string CredentialDigestUri = "uri"; - - /// - /// The value of the header credential opaque of a HTTP Digest access authentication. - /// - public const string CredentialOpaque = "opaque"; - - /// - /// The value of the header credential algorithm of a HTTP Digest access authentication. - /// - public const string CredentialAlgorithm = "algorithm"; - - /// - /// Computes a by parameter defined hash value of the required values for the HTTP Digest access authentication HA1. - /// - /// The credentials of the HA1 computed value (, ). - /// The password to include in the HA1 computed value. - /// The algorithm to use when computing the HA1 value. - /// A in the format of H('[CredentialUserName]:[CredentialRealm]:'). - public static string ComputeHash1(IDictionary credentials, string password, CryptoAlgorithm algorithm) - { - ValidateCredentials(credentials, CredentialUserName, CredentialRealm); - return HashFactory.CreateCrypto(algorithm).ComputeHash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", credentials[CredentialUserName], credentials[CredentialRealm], password), o => - { - o.Encoding = Encoding.UTF8; - }).ToHexadecimalString(); - } - - /// - /// Computes a by parameter defined hash value of the required values for the HTTP Digest access authentication HA2. - /// - /// The credentials of the HA2 computed value (). - /// The HTTP method to include in the HA2 computed value. - /// The algorithm to use when computing the HA2 value. - /// A in the format of H(':[CredentialDigestUri]'). - public static string ComputeHash2(IDictionary credentials, string httpMethod, CryptoAlgorithm algorithm) - { - ValidateCredentials(credentials, CredentialDigestUri); - return HashFactory.CreateCrypto(algorithm).ComputeHash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}", httpMethod, credentials[CredentialDigestUri]), o => - { - o.Encoding = Encoding.UTF8; - }).ToHexadecimalString(); - } - - /// - /// Computes a by parameter defined hash value of the required values for the HTTP Digest access authentication RESPONSE. - /// - /// The credentials of the RESPONSE computed value (, , , ). - /// The HA1 to include in the RESPONSE computed value. - /// The HA2 to include in the RESPONSE computed value. - /// The algorithm to use when computing the RESPONSE value. - /// A in the format of H(':[CredentialNonce]:[CredentialNonceCount]:[CredentialClientNonce]:[CredentialQualityOfProtection]:'). - public static byte[] ComputeResponse(IDictionary credentials, string hash1, string hash2, CryptoAlgorithm algorithm) - { - ValidateCredentials(credentials, CredentialNonce, CredentialNonceCount, CredentialClientNonce, CredentialQualityOfProtection); - return HashFactory.CreateCrypto(algorithm).ComputeHash(FormattableString.Invariant($"{hash1}:{credentials[CredentialNonce]}:{credentials[CredentialNonceCount]}:{credentials[CredentialClientNonce]}:{credentials[CredentialQualityOfProtection]}:{hash2}"), o => - { - o.Encoding = Encoding.UTF8; - }).GetBytes(); - } - - /// - /// A default implementation of a nonce parser. - /// - /// The nonce protocol. - /// The time-to-live (ttl) of the . - /// true if the specified has expired compared to ; otherwise, false. - public static bool DefaultNonceExpiredParser(string nonce, TimeSpan timeToLive) - { - Validator.ThrowIfNullOrEmpty(nonce, nameof(nonce)); - if (ParserFactory.FromBase64().TryParse(nonce, out var rawNonce)) - { - var nonceProtocol = Convertible.ToString(rawNonce, options => - { - options.Encoding = Encoding.UTF8; - options.Preamble = PreambleSequence.Remove; - }); - var nonceTimestamp = DateTime.ParseExact(nonceProtocol.Substring(0, nonceProtocol.LastIndexOf(':')), "u", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); - var difference = (DateTime.UtcNow - nonceTimestamp); - return (difference > timeToLive); - } - return false; - } - - /// - /// A default implementation of a nonce generator. - /// - /// The value to include in the generated nonce. - /// An opaque identifier to include in the generated nonce. - /// A cryptographic private key to include as a cipher in the generated nonce. - /// A nonce protocol in the format of ':H()'. - public static string DefaultNonceGenerator(DateTime timestamp, string entityTag, byte[] privateKey) - { - Validator.ThrowIfNullOrEmpty(entityTag, nameof(entityTag)); - Validator.ThrowIfNull(privateKey, nameof(privateKey)); - var nonceHash = ComputeNonceHash(timestamp, entityTag, privateKey); - var nonceProtocol = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", timestamp.ToString("u", CultureInfo.InvariantCulture), nonceHash); - return Convert.ToBase64String(Convertible.GetBytes(nonceProtocol, options => - { - options.Encoding = Encoding.UTF8; - options.Preamble = PreambleSequence.Remove; - })); - } - - /// - /// A default implementation of opaque generator. - /// - /// An opaque value consisting of hexadecimal characters with a length of 32 bytes. - public static string DefaultOpaqueGenerator() - { - return Generate.RandomString(32, Alphanumeric.Hexadecimal).ToLowerInvariant(); - } - - /// - /// Converts the specified to its HTTP Digest access authentication header credential algorithm equivalent. - /// - /// The algorithm to convert. - /// A string containing either MD5, SHA-256 or SHA-512-256. - public static string ParseAlgorithm(CryptoAlgorithm algorithm) - { - switch (algorithm) - { - case CryptoAlgorithm.Sha256: - return "SHA-256"; - case CryptoAlgorithm.Sha512: - return "SHA-512-256"; - default: - return "MD5"; - } - } - - private static void ValidateCredentials(IDictionary credentials, params string[] requiredCredentials) - { - Validator.ThrowIfNull(credentials, nameof(credentials)); - foreach (var requiredCredential in requiredCredentials) - { - if (!credentials.ContainsKey(requiredCredential)) { throw new ArgumentException("One or more required credentials are missing.", nameof(credentials)); } - } - } - - private static string ComputeNonceHash(DateTime timeStamp, string entityTag, byte[] privateKey) - { - return HashFactory.CreateCryptoSha256().ComputeHash(timeStamp, entityTag, Convert.ToBase64String(privateKey)).ToHexadecimalString(); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Extensions/HttpContextDecoratorExtensions.cs b/src/Cuemon.AspNetCore.Authentication/Extensions/HttpContextDecoratorExtensions.cs new file mode 100644 index 000000000..d9d5275c3 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Extensions/HttpContextDecoratorExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Cuemon.AspNetCore.Authentication +{ + internal static class HttpContextDecoratorExtensions + { + internal static async Task InvokeAuthenticationAsync(this IDecorator decorator, TOptions options, Action transformer) where TOptions : AuthenticationOptions + { + var message = options.ResponseHandler?.Invoke(); + if (message != null) + { + transformer?.Invoke(message, decorator.Inner.Response); + throw new UnauthorizedException((int)message.StatusCode, await message.Content.ReadAsStringAsync().ConfigureAwait(false)); + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/GlobalSuppressions.cs b/src/Cuemon.AspNetCore.Authentication/GlobalSuppressions.cs new file mode 100644 index 000000000..f678d64ee --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design to support the Digest protocol.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Authentication.Digest.DigestAuthorizationHeader.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String)")] \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticationMiddleware.cs b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticationMiddleware.cs new file mode 100644 index 000000000..8f687d15d --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticationMiddleware.cs @@ -0,0 +1,101 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Cuemon.IO; +using Cuemon.Security.Cryptography; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.AspNetCore.Authentication.Hmac +{ + /// + /// Provides a HTTP HMAC Authentication middleware implementation for ASP.NET Core. + /// + public class HmacAuthenticationMiddleware : ConfigurableMiddleware + { + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The which need to be configured. + public HmacAuthenticationMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The middleware which need to be configured. + public HmacAuthenticationMiddleware(RequestDelegate next, Action setup) : base(next, setup) + { + } + + /// + /// Executes the . + /// + /// The context of the current request. + /// A task that represents the execution of this middleware. + public override async Task InvokeAsync(HttpContext context) + { + if (!Authenticator.TryAuthenticate(context, Options.RequireSecureConnection, AuthorizationHeaderParser, TryAuthenticate)) + { + await Decorator.Enclose(context).InvokeAuthenticationAsync(Options, async (message, response) => + { + context.Response.OnStarting(() => + { + context.Response.Headers.Add(HeaderNames.WWWAuthenticate, Options.AuthenticationScheme); + return Task.CompletedTask; + }); + response.StatusCode = (int)message.StatusCode; + await Decorator.Enclose(response.Body).WriteAsync(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ConfigureAwait(false); + }).ConfigureAwait(false); + } + await Next.Invoke(context).ConfigureAwait(false); + } + + private bool TryAuthenticate(HttpContext context, HmacAuthorizationHeader header, out ClaimsPrincipal result) + { + if (Options.Authenticator == null) { throw new InvalidOperationException(FormattableString.Invariant($"The {nameof(Options.Authenticator)} cannot be null.")); } + + if (header == null) + { + result = null; + return false; + } + + var requestBodyMd5 = context.Request.Headers[HeaderNames.ContentMD5].FirstOrDefault()?.ToLowerInvariant(); + if (!string.IsNullOrWhiteSpace(requestBodyMd5) && !UnkeyedHashFactory.CreateCrypto(UnkeyedCryptoAlgorithm.Md5).ComputeHash(context.Request.Body).ToHexadecimalString().Equals(requestBodyMd5, StringComparison.Ordinal)) + { + result = null; + return false; + } + + var clientId = header.ClientId; + result = Options.Authenticator(clientId, out var clientSecret); + if (clientSecret == null) + { + result = null; + return false; + } + var signature = header.Signature; + + var hb = new HmacAuthorizationHeaderBuilder(Options.AuthenticationScheme) + .AddCredentialScope(header.CredentialScope) + .AddClientId(clientId) + .AddClientSecret(clientSecret) + .AddSignedHeaders(header.SignedHeaders) + .AddFromRequest(context.Request); + + var computedSignature = hb.ComputeSignature(); + return computedSignature != null && signature.Equals(computedSignature, StringComparison.Ordinal) && Condition.IsNotNull(result); + } + + private HmacAuthorizationHeader AuthorizationHeaderParser(HttpContext context, string authorizationHeader) + { + return HmacAuthorizationHeader.Create(Options.AuthenticationScheme, authorizationHeader); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticationOptions.cs b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticationOptions.cs new file mode 100644 index 000000000..2bb6ad860 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticationOptions.cs @@ -0,0 +1,38 @@ +using Cuemon.Security.Cryptography; + +namespace Cuemon.AspNetCore.Authentication.Hmac +{ + /// + /// Configuration options for . This class cannot be inherited. + /// + /// + public sealed class HmacAuthenticationOptions : AuthenticationOptions + { + /// + /// Initializes a new instance of the class. + /// + public HmacAuthenticationOptions() + { + AuthenticationScheme = HmacAuthorizationHeader.Scheme; + Algorithm = KeyedCryptoAlgorithm.HmacSha1; + } + + /// + /// Gets the name of the authentication scheme. Default is . + /// + /// The name of the authentication scheme. + public string AuthenticationScheme { get; set; } + + /// + /// Gets or sets the algorithm of the HMAC Authentication. Default is . + /// + /// The algorithm of the HMAC Authentication. + public KeyedCryptoAlgorithm Algorithm { get; set; } + + /// + /// Gets or sets the function delegate that will perform the authentication from the specified publicKey. + /// + /// The function delegate that will perform the authentication. + public HmacAuthenticator Authenticator { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticator.cs b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticator.cs new file mode 100644 index 000000000..24d49516a --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthenticator.cs @@ -0,0 +1,12 @@ +using System.Security.Claims; + +namespace Cuemon.AspNetCore.Authentication.Hmac +{ + /// + /// Represents the method that defines an Authenticator typically assigned on . + /// + /// The public key to match and lookup the paired shared . + /// The shared secret-private key paired with . + /// A that is associated with the result of . + public delegate ClaimsPrincipal HmacAuthenticator(string clientId, out string clientSecret); +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthorizationHeader.cs b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthorizationHeader.cs new file mode 100644 index 000000000..92ef66728 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthorizationHeader.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Cuemon.AspNetCore.Authentication.Hmac +{ + /// + /// Provides a representation of a HTTP HMAC Authentication header. + /// Implements the + /// + /// + public class HmacAuthorizationHeader : AuthorizationHeader + { + /// + /// Creates an instance of from the specified parameters. + /// + /// The name of the authentication scheme. + /// The raw HTTP authorization header. + /// The which may be configured. + /// An instance of . + /// + /// cannot be null -or- + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters -or- + /// cannot be empty or consist only of white-space characters. + /// + public static HmacAuthorizationHeader Create(string authenticationScheme, string authorizationHeader, Action setup = null) + { + Validator.ThrowIfNullOrWhitespace(authenticationScheme, nameof(authenticationScheme)); + Validator.ThrowIfNullOrWhitespace(authorizationHeader, nameof(authorizationHeader)); + return new HmacAuthorizationHeader(authenticationScheme).Parse(authorizationHeader, setup) as HmacAuthorizationHeader; + } + + /// + /// The default authentication scheme of the . + /// + public const string Scheme = "HMAC"; + + HmacAuthorizationHeader(string authenticationScheme) : base(authenticationScheme) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The client identifier that is the public key of the signing process. + /// The credential scope that defines the remote resource. + /// The headers that will be part of the signing process. + /// The signature that represents the integrity of this header. + /// The authentication scheme of this header. Default is (HMAC). + public HmacAuthorizationHeader(string clientId, string credentialScope, string signedHeaders, string signature, string authenticationScheme = Scheme) : base(authenticationScheme) + { + ClientId = clientId; + CredentialScope = credentialScope; + SignedHeaders = signedHeaders.Split(';'); + Signature = signature; + } + + /// + /// Gets the client identifier that is the public key of the signing process. + /// + /// The client identifier that is the public key of the signing process. + public string ClientId { get; } + + /// + /// Gets the credential scope that defines the remote resource. + /// + /// The credential scope that defines the remote resource. + public string CredentialScope { get; } + + /// + /// Gets the headers that will be part of the signing process. + /// + /// The headers that will be part of the signing process. + public string[] SignedHeaders { get; } + + /// + /// Gets the signature that represents the integrity of this header. + /// + /// The signature that represents the integrity of this header. + public string Signature { get; } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return $"{AuthenticationScheme} Credential={ClientId}/{CredentialScope}, SignedHeaders={string.Join(";", SignedHeaders)}, Signature={Signature}"; + } + + /// + /// The core parser that resolves an from a set of . + /// + /// The credentials used in authentication. + /// An equivalent of . + protected override AuthorizationHeader ParseCore(IReadOnlyDictionary credentials) + { + string clientId = null, credentialScope = null, signedHeaders = null, signature = null; + foreach (var kvp in credentials) + { + var key = kvp.Key; + var value = kvp.Value; + + if (key == "Credential") + { + var cs = value.Split(new [] { '/' }, 2); + clientId = cs[0]; + credentialScope = cs[1]; + } + + if (key == "SignedHeaders") + { + signedHeaders = value; + } + + if (key == "Signature") + { + signature = value; + } + } + + if (!string.IsNullOrWhiteSpace(clientId) && + credentialScope != null && + signedHeaders != null && signedHeaders.Any() && + !string.IsNullOrWhiteSpace(signature)) + { + return new HmacAuthorizationHeader(clientId, credentialScope, signedHeaders, signature); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthorizationHeaderBuilder.cs b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthorizationHeaderBuilder.cs new file mode 100644 index 000000000..2c37d60f1 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacAuthorizationHeaderBuilder.cs @@ -0,0 +1,180 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using Cuemon.AspNetCore.Authentication.Digest; +using Cuemon.Collections.Generic; +using Cuemon.Net; +using Cuemon.Security.Cryptography; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.AspNetCore.Authentication.Hmac +{ + /// + /// Provides a way to fluently represent a HTTP HMAC Authentication header. + /// Inspired by AWS Signature Version 4 (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html, https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html). + /// + public class HmacAuthorizationHeaderBuilder : AuthorizationHeaderBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the authentication scheme. Default is HMAC. + /// The algorithm to use when computing the final signature of the HMAC Authentication header. + /// The algorithm to use when computing in-between signatures as part of the final signing of the HMAC Authentication header. + public HmacAuthorizationHeaderBuilder(string authenticationScheme = HmacAuthorizationHeader.Scheme, KeyedCryptoAlgorithm hmacAlgorithm = KeyedCryptoAlgorithm.HmacSha256, UnkeyedCryptoAlgorithm algorithm = UnkeyedCryptoAlgorithm.Sha256) : base(authenticationScheme) + { + HmacAlgorithm = hmacAlgorithm; + Algorithm = algorithm; + MapRelation(nameof(AddCredentialScope), HmacFields.CredentialScope); + MapRelation(nameof(AddClientId), HmacFields.ClientId); + MapRelation(nameof(AddClientSecret), HmacFields.ClientSecret); + MapRelation(nameof(AddFromRequest), HmacFields.UriPath, HmacFields.UriQuery, HmacFields.HttpHeaders, HmacFields.Payload, HmacFields.ServerDateTime); + } + + /// + /// Gets the non-keyed algorithm of the HTTP HMAC Authentication. + /// + /// The non-keyed algorithm of the HTTP HMAC Authentication. + public UnkeyedCryptoAlgorithm Algorithm { get; } + + /// + /// Gets the keyed algorithm of the HTTP HMAC Authentication. + /// + /// The keyed algorithm of the HTTP HMAC Authentication. + public KeyedCryptoAlgorithm HmacAlgorithm { get; } + + + /// + /// Adds the credential scope that defines the remote resource. + /// + /// The credential scope that defines the remote resource. + /// An that can be used to further build the HTTP HMAC Authentication header. + public HmacAuthorizationHeaderBuilder AddCredentialScope(string credentialScope) + { + return AddOrUpdate(HmacFields.CredentialScope, credentialScope); + } + + /// + /// Adds the client identifier that is the public key of the signing process. + /// + /// The client identifier that is the public key of the signing process. + /// An that can be used to further build the HTTP HMAC Authentication header. + public HmacAuthorizationHeaderBuilder AddClientId(string clientId) + { + return AddOrUpdate(HmacFields.ClientId, clientId); + } + + /// + /// Adds the client secret that is the private key of the signing process. + /// + /// The client secret that is the private key of the signing process. + /// An that can be used to further build the HTTP HMAC Authentication header. + public HmacAuthorizationHeaderBuilder AddClientSecret(string clientSecret) + { + return AddOrUpdate(HmacFields.ClientSecret, clientSecret); + } + + /// + /// Adds the necessary fields that is part of an HTTP request. + /// + /// An instance of the object. + /// An that can be used to further build the HTTP HMAC Authentication header. + public HmacAuthorizationHeaderBuilder AddFromRequest(HttpRequest request) + { + Validator.ThrowIfNull(request, nameof(request)); + return AddOrUpdate(HmacFields.HttpMethod, request.Method) + .AddOrUpdate(HmacFields.UriPath, request.Path.ToUriComponent()) + .AddOrUpdate(HmacFields.UriQuery, string.Concat(request.Query.OrderBy(pair => pair.Key).Select(pair => $"{Decorator.Enclose(pair.Value.ToString()).UrlEncode()}"))) + .AddOrUpdate(HmacFields.HttpHeaders, request.Headers.Count == 0 ? null : string.Concat(request.Headers.OrderBy(pair => pair.Key).Select(pair => $"{pair.Key.ToLowerInvariant()}:{DelimitedString.Create(pair.Value, o => o.StringConverter = s => $"{s.Trim()}{Alphanumeric.Linefeed}")}"))) + .AddOrUpdate(HmacFields.Payload, UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(request.Body).ToHexadecimalString()) + .AddOrUpdate(HmacFields.ServerDateTime, DateTime.Parse(request.Headers[HeaderNames.Date].ToString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).ToString("O")); + } + + /// + /// Adds the headers that will be part of the signing process. + /// + /// The headers that will be part of the signing process. Default is host and date + /// An that can be used to further build the HTTP HMAC Authentication header. + public HmacAuthorizationHeaderBuilder AddSignedHeaders(params string[] signedHeaders) + { + if (signedHeaders == null) { return this; } + return AddOrUpdate(HmacFields.SignedHeaders, DelimitedString.Create(signedHeaders, o => + { + o.Delimiter = ";"; + o.StringConverter = s => s.ToLowerInvariant(); + })); + } + + /// + /// Converts the request to a standardized (canonical) format and computes a message digest using . + /// + /// A representation, in hexadecimal, of the computed canonical request. + public virtual string ComputeCanonicalRequest() + { + ValidateData(HmacFields.UriPath, HmacFields.UriQuery, HmacFields.HttpHeaders, HmacFields.Payload); + EnsureSignedHeaders(out var signedHeaders); + var signedHeadersLookup = signedHeaders.Split(';').ToList(); + var headersToSign = DelimitedString.Create(Data[HmacFields.HttpHeaders].Split(Alphanumeric.Linefeed.ToCharArray()).Where(header => + { + var kvp = header.Split(':'); + return signedHeadersLookup.Contains(kvp[0]); + }), o => o.Delimiter = Alphanumeric.Linefeed) + Alphanumeric.Linefeed; + + var stringToSign = new StringBuilder(Data[HmacFields.HttpMethod]) + .Append(Alphanumeric.Linefeed) + .Append(Data[HmacFields.UriPath]) + .Append(Alphanumeric.Linefeed) + .Append(Data[HmacFields.UriQuery]) + .Append(Alphanumeric.Linefeed) + .Append(headersToSign) + .Append(Alphanumeric.Linefeed) + .Append(signedHeaders) + .Append(Alphanumeric.Linefeed) + .Append(Data[HmacFields.Payload]).ToString(); + + return UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(stringToSign).ToHexadecimalString(); + } + + /// + /// Computes the signature of this instance using a series of hash-based message authentication codes (HMACs) using . + /// + /// A representation, in hexadecimal, of the computed signature of this instance. + public virtual string ComputeSignature() + { + ValidateData(HmacFields.ServerDateTime, HmacFields.ClientSecret); + var secret = Decorator.Enclose(Data[HmacFields.ClientSecret]).ToByteArray(); + var stringToSign = string.Concat(Algorithm, + Alphanumeric.Linefeed, + Data[HmacFields.ServerDateTime], + Alphanumeric.Linefeed, + Decorator.Enclose(Data).GetValueOrDefault(HmacFields.CredentialScope), + Alphanumeric.Linefeed, + ComputeCanonicalRequest()); + var date = DateTime.Parse(Data[HmacFields.ServerDateTime], CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).Date.ToString("yyyyMMdd"); + var dateSecret = KeyedHashFactory.CreateHmacCrypto(secret, HmacAlgorithm).ComputeHash(date).GetBytes(); + return KeyedHashFactory.CreateHmacCrypto(dateSecret, HmacAlgorithm).ComputeHash(stringToSign).ToHexadecimalString(); + } + + /// + /// Builds an instance of that implements . + /// + /// An instance of . + public override HmacAuthorizationHeader Build() + { + ValidateData(HmacFields.ClientId, HmacFields.ServerDateTime, HmacFields.ClientSecret, HmacFields.UriPath, HmacFields.UriQuery, HmacFields.HttpHeaders, HmacFields.Payload); + EnsureSignedHeaders(out var signedHeaders); + return new HmacAuthorizationHeader(Data[HmacFields.ClientId], Decorator.Enclose(Data).GetValueOrDefault(HmacFields.CredentialScope), signedHeaders, ComputeSignature(), AuthenticationScheme); + } + + private void EnsureSignedHeaders(out string signedHeaders) + { + if (!Data.TryGetValue(HmacFields.SignedHeaders, out signedHeaders)) + { + signedHeaders = "host;date"; + AddSignedHeaders(signedHeaders); + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Hmac/HmacFields.cs b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacFields.cs new file mode 100644 index 000000000..ec3bd782d --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Hmac/HmacFields.cs @@ -0,0 +1,58 @@ +namespace Cuemon.AspNetCore.Authentication.Hmac +{ + /// + /// A collection of constants for . + /// + public static class HmacFields + { + /// + /// The HTTP request method. + /// + public const string HttpMethod = "httpRequestMethod"; + + /// + /// The canonical URI that is the URI-encoded version of the absolute path component of an URI. + /// + public const string UriPath = "canonicalUri"; + + /// + /// The canonical query string. + /// + public const string UriQuery = "canonicalQueryString"; + + /// + /// The canonical headers. + /// + public const string HttpHeaders = "canonicalHeaders"; + + /// + /// The headers that must be part of the signing process. + /// + public const string SignedHeaders = "signedHeaders"; + + /// + /// The request payload. + /// + public const string Payload = "requestPayload"; + + /// + /// The server date time expressed in ISO 8601 format. + /// + public const string ServerDateTime = "serverDateTime"; + + /// + /// The public key of the signing process. + /// + public const string ClientId = "clientId"; + + /// + /// The private key of the signing process. + /// + public const string ClientSecret = "clientSecret"; + + /// + /// The credential scope that defines the remote resource. + /// + public const string CredentialScope = "credentialScope"; + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationMiddleware.cs b/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationMiddleware.cs deleted file mode 100644 index 47ef8603d..000000000 --- a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationMiddleware.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using Cuemon.AspNetCore.Builder; -using Cuemon.Security.Cryptography; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Provides a HTTP HMAC Authentication middleware implementation for ASP.NET Core. - /// - public class HmacAuthenticationMiddleware : ConfigurableMiddleware - { - /// - /// Initializes a new instance of the class. - /// - /// The delegate of the request pipeline to invoke. - /// The which need to be configured. - public HmacAuthenticationMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The delegate of the request pipeline to invoke. - /// The middleware which need to be configured. - public HmacAuthenticationMiddleware(RequestDelegate next, Action setup) : base(next, setup) - { - } - - /// - /// Executes the . - /// - /// The context of the current request. - /// A task that represents the execution of this middleware. - public override async Task InvokeAsync(HttpContext context) - { - if (!AuthenticationUtility.TryAuthenticate(context, Options.RequireSecureConnection, AuthorizationHeaderParser, TryAuthenticate)) - { - context.Response.StatusCode = AuthenticationUtility.HttpNotAuthorizedStatusCode; - context.Response.Headers.Add(HeaderNames.WWWAuthenticate, Options.AuthenticationScheme); - await context.WriteHttpNotAuthorizedBody(Options.HttpNotAuthorizedBody).ConfigureAwait(false); - return; - } - await Next.Invoke(context).ConfigureAwait(false); - } - - private bool TryAuthenticate(HttpContext context, Template credentials, out ClaimsPrincipal result) - { - if (Options.Authenticator == null) { throw new InvalidOperationException(FormattableString.Invariant($"The {nameof(Options.Authenticator)} cannot be null.")); } - var requestBodyMd5 = context.Request.Headers[HeaderNames.ContentMD5].FirstOrDefault()?.ToLowerInvariant(); - if (!string.IsNullOrWhiteSpace(requestBodyMd5) && !HashFactory.CreateCrypto(CryptoAlgorithm.Md5).ComputeHash(context.Request.Body).ToHexadecimalString().Equals(requestBodyMd5, StringComparison.Ordinal)) - { - result = null; - return false; - } - var publicKey = credentials.Arg1; - var signature = credentials.Arg2; - var stringToSign = Options.MessageDescriptor(context); - var privateKey = new byte[0]; - result = Options?.Authenticator(publicKey, out privateKey); - if (privateKey == null) - { - result = null; - return false; - } - var computedSignature = Options?.HmacSigner(new HmacAuthenticationParameters(Options.Algorithm, privateKey, stringToSign)); - return computedSignature != null && signature.Equals(Convert.ToBase64String(computedSignature), StringComparison.Ordinal) && Condition.IsNotNull(result); - } - - private Template AuthorizationHeaderParser(HttpContext context, string authorizationHeader) - { - if (AuthenticationUtility.IsAuthenticationSchemeValid(authorizationHeader, Options.AuthenticationScheme) && authorizationHeader.Length > Options.AuthenticationScheme.Length) - { - var credentials = authorizationHeader.Remove(0, Options.AuthenticationScheme.Length + 1).Split(':'); - if (credentials.Length == 2) - { - var publicKey = credentials[0]; - var signature = credentials[1]; - if (!string.IsNullOrWhiteSpace(publicKey) && !string.IsNullOrWhiteSpace(signature)) { return Template.CreateTwo(publicKey, signature); } - } - } - return null; - } - } - - /// - /// This is a factory implementation of the class. - /// - public static class HmacAuthenticationBuilderExtension - { - /// - /// Adds a HTTP HMAC Authentication scheme to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The HTTP middleware which need to be configured. - /// A reference to this instance after the operation has completed. - public static IApplicationBuilder UseHmacAuthentication(this IApplicationBuilder builder, Action setup = null) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationOptions.cs b/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationOptions.cs deleted file mode 100644 index 9399a8564..000000000 --- a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationOptions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using Cuemon.Security.Cryptography; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Net.Http.Headers; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Configuration options for . This class cannot be inherited. - /// - /// - public sealed class HmacAuthenticationOptions : AuthenticationOptions - { - /// - /// Initializes a new instance of the class. - /// - public HmacAuthenticationOptions() - { - AuthenticationScheme = "HMAC"; - Algorithm = KeyedCryptoAlgorithm.HmacSha1; - MessageDescriptor = context => FormattableString.Invariant($"{context.Request.Method}:{context.Request.GetDisplayUrl()}:{context.Request.Headers[HeaderNames.ContentMD5].FirstOrDefault()}:{context.Request.Headers[HeaderNames.ContentType].FirstOrDefault()}:{context.Request.Headers[HeaderNames.Date].FirstOrDefault()}:{context.Request.Headers[HeaderNames.UserAgent].FirstOrDefault()}"); - HmacSigner = parameters => HashFactory.CreateHmacCrypto(parameters.PrivateKey, parameters.Algorithm).ComputeHash(parameters.Message, o => - { - o.Encoding = Encoding.UTF8; - }).GetBytes(); - } - - /// - /// Gets the name of the authentication scheme. Default is "HMAC". - /// - /// The name of the authentication scheme. - public string AuthenticationScheme { get; set; } - - /// - /// Gets or sets the algorithm of the HMAC Authentication. Default is . - /// - /// The algorithm of the HMAC Authentication. - public KeyedCryptoAlgorithm Algorithm { get; set; } - - /// - /// Gets or sets the function delegate that provides information about the message to be signed. - /// - /// The function delegate that provides information about the message to be signed. - public Func MessageDescriptor { get; set; } - - /// - /// Gets or sets the function delegate that will perform the authentication from the specified publicKey. - /// - /// The function delegate that will perform the authentication. - public HmacAuthenticator Authenticator { get; set; } - - /// - /// Gets or sets the function delegate that will sign a message retrieved by . - /// - /// The function delegate that will sign a message. - public Func HmacSigner { get; set; } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationParameters.cs b/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationParameters.cs deleted file mode 100644 index 3bfb37914..000000000 --- a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticationParameters.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Cuemon.Security.Cryptography; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Represents a set of parameters that is needed for creating a keyed-hash message authentication code (HMAC). - /// - public class HmacAuthenticationParameters - { - /// - /// Initializes a new instance of the class. - /// - /// The hash algorithm to use for the computation. - /// The secret key for the hashed encryption. The key can be any length, but it is strongly recommended to use a size of either 64 bytes (for and ) or 128 bytes (for and ). - /// The value to compute a hash code for. - internal HmacAuthenticationParameters(KeyedCryptoAlgorithm algorithm, byte[] privateKey, string message) - { - Algorithm = algorithm; - Message = message; - PrivateKey = privateKey; - } - - /// - /// Gets the algorithm of the HMAC. Default is . - /// - /// The algorithm of the HMAC. - public KeyedCryptoAlgorithm Algorithm { get; } - - /// - /// Gets the secret key for the hashed encryption. - /// - /// The secret key for the hashed encryption. - public byte[] PrivateKey { get; } - - /// - /// Gets the message to compute a hash code for. - /// - /// The message to compute a hash code for. - public string Message { get; } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticator.cs b/src/Cuemon.AspNetCore.Authentication/HmacAuthenticator.cs deleted file mode 100644 index c85196dec..000000000 --- a/src/Cuemon.AspNetCore.Authentication/HmacAuthenticator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Security.Claims; - -namespace Cuemon.AspNetCore.Authentication -{ - /// - /// Represents the method that defines an Authenticator typically assigned on . - /// - /// The public key to match and lookup the paired shared secret-. - /// The shared secret-private key paired with . - /// A that is associated with the result of . - public delegate ClaimsPrincipal HmacAuthenticator(string publicKey, out byte[] privateKey); -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/INonceTracker.cs b/src/Cuemon.AspNetCore.Authentication/INonceTracker.cs new file mode 100644 index 000000000..d015b936c --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/INonceTracker.cs @@ -0,0 +1,34 @@ +using System; + +namespace Cuemon.AspNetCore.Authentication +{ + /// + /// Represents tracking of server-generated nonce values. + /// + /// + public interface INonceTracker + { + /// + /// Attempts to get the associated with the specified from the tracker. + /// + /// The unique identifier of the tracker. + /// When this method returns, contains the entry associated with the specified , or null if the operation failed. + /// true if the was found in the tracker; otherwise, false. + bool TryGetEntry(string nonce, out NonceTrackerEntry entry); + + /// + /// Attempts to insert a into the tracker. + /// + /// The unique identifier of the tracker. + /// The number or bit string that should be used only once. + /// true if insertion succeeded; otherwise, false when there is already an entry in the tracker with the same key. + bool TryAddEntry(string nonce, int count); + + /// + /// Attempts to remove an entry from the tracker. + /// + /// The unique identifier of the tracker. + /// true if the entry is removed from the tracker; otherwise, false. + bool TryRemoveEntry(string nonce); + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/MemoryNonceTracker.cs b/src/Cuemon.AspNetCore.Authentication/MemoryNonceTracker.cs new file mode 100644 index 000000000..be7e4fd31 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/MemoryNonceTracker.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using Cuemon.Threading; + +namespace Cuemon.AspNetCore.Authentication +{ + /// + /// Provides a default in-memory implementation of the interface. + /// + /// + /// + public class MemoryNonceTracker : Disposable, INonceTracker + { + private readonly ConcurrentDictionary _entries = new ConcurrentDictionary(); + private readonly Timer _expirationTimer; + + /// + /// Initializes a new instance of the class. + /// + public MemoryNonceTracker() + { + _expirationTimer = TimerFactory.CreateNonCapturingTimer(state => ((MemoryNonceTracker)state).OnAutomatedSweepCleanup(), this, TimeSpan.FromMinutes(15), TimeSpan.FromHours(1)); + } + + /// + /// Attempts to get the associated with the specified from the tracker. + /// + /// The unique identifier of the tracker. + /// When this method returns, contains the entry associated with the specified , or null if the operation failed. + /// true if the was found in the tracker; otherwise, false. + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + public bool TryGetEntry(string nonce, out NonceTrackerEntry entry) + { + Validator.ThrowIfNullOrWhitespace(nonce, nameof(nonce)); + return _entries.TryGetValue(nonce, out entry); + } + + /// + /// Attempts to insert a into the tracker. + /// + /// The unique identifier of the tracker. + /// The number or bit string that should be used only once. + /// true if insertion succeeded; otherwise, false when there is already an entry in the tracker with the same key. + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + public bool TryAddEntry(string nonce, int count) + { + Validator.ThrowIfNullOrWhitespace(nonce, nameof(nonce)); + return _entries.TryAdd(nonce, new NonceTrackerEntry(count, DateTime.UtcNow)); + } + + /// + /// Attempts to remove an entry from the tracker. + /// + /// The unique identifier of the tracker. + /// true if the entry is removed from the tracker; otherwise, false. + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + public bool TryRemoveEntry(string nonce) + { + Validator.ThrowIfNullOrWhitespace(nonce, nameof(nonce)); + return _entries.TryRemove(nonce, out _); + } + + private void OnAutomatedSweepCleanup() + { + var utcStaleTime = DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(5)); + var entries = _entries.Where(pair => pair.Value.Created <= utcStaleTime).ToList(); + if (entries.Count > 0) + { + foreach (var entry in entries) + { + _entries.TryRemove(entry.Key, out _); + } + } + } + + /// + /// Called when this object is being disposed by either or having disposing set to true and is false. + /// + protected override void OnDisposeManagedResources() + { + _expirationTimer?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/NonceTrackerEntry.cs b/src/Cuemon.AspNetCore.Authentication/NonceTrackerEntry.cs new file mode 100644 index 000000000..0b23745bb --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/NonceTrackerEntry.cs @@ -0,0 +1,33 @@ +using System; + +namespace Cuemon.AspNetCore.Authentication +{ + /// + /// Represents an individual nonce entry in the . + /// + public class NonceTrackerEntry + { + /// + /// Initializes a new instance of the class. + /// + /// The number that should be used only once. + /// The timestamp from when this entry was created. + public NonceTrackerEntry(int count, DateTime created) + { + Count = count; + Created = created; + } + + /// + /// Gets the number that should be used only once. + /// + /// The number that should be used only once. + public int Count { get; } + + /// + /// Gets the timestamp from when this entry was created. + /// + /// The timestamp from when this entry was created. + public DateTime Created { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Properties/AssemblyInfo.cs b/src/Cuemon.AspNetCore.Authentication/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..20b98e714 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("cbba5566-2383-459e-86d5-8a1fc232e8ed")] \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Authentication/Properties/PackageReleaseNotes.txt b/src/Cuemon.AspNetCore.Authentication/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..61c98ba14 --- /dev/null +++ b/src/Cuemon.AspNetCore.Authentication/Properties/PackageReleaseNotes.txt @@ -0,0 +1,37 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Upgrade Steps +- Any former extension methods of the Cuemon.AspNetCore.Authentication namespace was merged into the Cuemon.Extensions.AspNetCore.Authentication namespace +  +# Breaking Changes +- RENAMED AuthenticationUtility class in the Cuemon.AspNetCore.Authentication namespace to Authenticator and removed all constants +- REMOVED DigestAccessAuthenticationParameters class from the Cuemon.AspNetCore.Authentication namespace +- REMOVED DigestAuthenticationUtility class from the Cuemon.AspNetCore.Authentication namespace +- REMOVED HmacAuthenticationParameters class from the Cuemon.AspNetCore.Authentication namespace +- MOVED BasicAuthenticationMiddleware class from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Basic namespace (including refactoring) +- MOVED BasicAuthenticationOptions class from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Basic namespace +- MOVED BasicAuthenticator delegate from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Basic namespace +- MOVED DigestAccessAuthenticationMiddleware class from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Digest namespace (including refactoring) +- MOVED DigestAccessAuthenticationOptions class from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Digest namespace (including refactoring) +- MOVED DigestAccessAuthenticator delegate from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Digest namespace +- RENAMED DigestAccessAuthenticationMiddleware class in the Cuemon.AspNetCore.Authentication.Digest namespace to DigestAuthenticationMiddleware +- RENAMED DigestAccessAuthenticationOptions class in the Cuemon.AspNetCore.Authentication.Digest namespace to DigestAuthenticationOptions +- RENAMED DigestAccessAuthenticator class in the Cuemon.AspNetCore.Authentication.Digest namespace to DigestAuthenticator +- MOVED HmacAuthenticationMiddleware class from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Hmac namespace (including refactoring) +- MOVED HmacAuthenticationOptions class from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Hmac namespace (including refactoring) +- MOVED HmacAuthenticator delegate from the Cuemon.AspNetCore.Authentication namespace to Cuemon.AspNetCore.Authentication.Hmac namespace +  +# New Features +- ADDED BasicAuthorizationHeader class in the Cuemon.AspNetCore.Authentication.Basic namespace that provides a representation of a HTTP Basic Authentication header +- ADDED BasicAuthorizationHeaderBuilder class in the Cuemon.AspNetCore.Authentication.Basic namespace that provides a way to fluently represent a HTTP Basic Authentication header +- ADDED BasicFields class in the Cuemon.AspNetCore.Authentication.Basic namespace that holds a collection of constants for BasicAuthorizationHeaderBuilder +- ADDED DigestAuthorizationHeader class in the Cuemon.AspNetCore.Authentication.Basic namespace that provides a representation of a HTTP Digest Access Authentication header +- ADDED DigestAuthorizationHeaderBuilder class in the Cuemon.AspNetCore.Authentication.Basic namespace that provides a way to fluently represent a HTTP Digest Access Authentication header +- ADDED DigestFields class in the Cuemon.AspNetCore.Authentication.Basic namespace that holds a collection of constants for DigestAuthorizationHeaderBuilder +- ADDED AuthorizationHeader class in the Cuemon.AspNetCore.Authentication namespace that represents the base class from which all implementations of authorization header should derive +- ADDED AuthorizationHeaderBuilder class in the Cuemon.AspNetCore.Authentication namespace that represents the base class from which all implementations of authorization header builders should derive +- ADDED AuthorizationHeaderOptions class in the Cuemon.AspNetCore.Authentication namespace that specifies options related to AuthorizationHeader +- ADDED INonceTracker interface in the Cuemon.AspNetCore.Authentication namespace that represents tracking of server-generated nonce values +- ADDED MemoryNonceTracker class in the Cuemon.AspNetCore.Authentication namespace that provides a default in-memory implementation of the INonceTracker interface +  \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json.csproj b/src/Cuemon.AspNetCore.Mvc.Formatters.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json.csproj deleted file mode 100644 index 590579815..000000000 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true - a60adf91-e7c7-4cb4-a39d-e1a5374c5602 - - - - Cuemon .NET Standard - Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json - Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json - The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json assembly is a serialization supplement to Microsoft ASP.NET Core. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/DefaultJsonSerializerSettings.cs b/src/Cuemon.AspNetCore.Mvc.Formatters.Json/DefaultJsonSerializerSettings.cs deleted file mode 100644 index 205de9af8..000000000 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/DefaultJsonSerializerSettings.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Cuemon.Diagnostics; -using Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json.Converters; -using Cuemon.Extensions.Newtonsoft.Json.Converters; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json -{ - /// - /// Specifies the default settings on a object as interpreted by this framework. - /// - public class DefaultJsonSerializerSettings : JsonSerializerSettings - { - /// - /// Initializes a new instance of the class. - /// - public DefaultJsonSerializerSettings() - { - IncludeExceptionDescriptorFailure = true; - IncludeExceptionDescriptorEvidence = true; - IncludeExceptionStackTrace = false; - Formatting = Formatting.Indented; - NullValueHandling = NullValueHandling.Ignore; - MissingMemberHandling = MissingMemberHandling.Ignore; - ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - DateParseHandling = DateParseHandling.DateTimeOffset; - DateFormatHandling = DateFormatHandling.IsoDateFormat; - DateTimeZoneHandling = DateTimeZoneHandling.Utc; - ContractResolver = new CamelCasePropertyNamesContractResolver(); - Converters.AddStringValuesConverter() - .AddHttpExceptionDescriptorConverter(o => - { - o.IncludeEvidence = IncludeExceptionDescriptorEvidence; - o.IncludeFailure = IncludeExceptionDescriptorFailure; - }) - .AddExceptionConverter(() => IncludeExceptionStackTrace) - .AddDataPairConverter() - .AddStringEnumConverter() - .AddStringFlagsEnumConverter() - .AddTimeSpanConverter(); - } - - /// - /// Gets or sets a value indicating whether the stack of an is included in the converter that handles exceptions. - /// - /// true if the stack of an is included in the converter that handles exceptions; otherwise, false. - public bool IncludeExceptionStackTrace { get; set; } - - /// - /// Gets or sets a value indicating whether the failure of an is included in the converter that handles exception descriptors. - /// - /// true if the failure of an is included in the converter that handles exception descriptors; otherwise, false. - public bool IncludeExceptionDescriptorFailure { get; set; } - - /// - /// Gets or sets a value indicating whether the evidence of an is included in the converter that handles exception descriptors. - /// - /// true if the evidence of an is included in the converter that handles exception descriptors; otherwise, false. - public bool IncludeExceptionDescriptorEvidence { get; set; } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj b/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj deleted file mode 100644 index b00d076d2..000000000 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true - a70adf91-e7c7-4cb4-a39d-e1a5374c5602 - - - - Cuemon .NET Standard - Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml - Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml - The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml assembly is a serialization supplement to Microsoft ASP.NET Core. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlMvcCoreBuilderExtensions.cs b/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlMvcCoreBuilderExtensions.cs deleted file mode 100644 index 6e6ff3299..000000000 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlMvcCoreBuilderExtensions.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using Cuemon.Collections.Generic; -using Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters; -using Cuemon.Xml.Serialization.Formatters; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; - -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml -{ - /// - /// Extension methods for adding XML formatters to MVC. - /// - public static class XmlMvcCoreBuilderExtensions - { - /// - /// Adds the XML Serializer formatters to MVC. - /// - /// The . - /// The . - public static IMvcCoreBuilder AddXmlSerializationFormatters(this IMvcCoreBuilder builder) - { - Validator.ThrowIfNull(builder, nameof(builder)); - builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, XmlSerializationMvcOptionsSetup>()); - return builder; - } - - /// - /// Adds the XML Serializer formatters to MVC. - /// - /// The . - /// The . - public static IMvcBuilder AddXmlSerializationFormatters(this IMvcBuilder builder) - { - Validator.ThrowIfNull(builder, nameof(builder)); - builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, XmlSerializationMvcOptionsSetup>()); - return builder; - } - - /// - /// Adds configuration of for the application. - /// - /// The . - /// The which need to be configured. - /// The . - public static IMvcCoreBuilder AddXmlFormatterOptions(this IMvcCoreBuilder builder, Action setup) - { - Validator.ThrowIfNull(builder, nameof(builder)); - Validator.ThrowIfNull(setup, nameof(setup)); - builder.Services.Configure(DefaultXmlFormatterOptions(setup)); - return builder; - } - - /// - /// Adds configuration of for the application. - /// - /// The . - /// The which need to be configured. - /// The . - public static IMvcBuilder AddXmlFormatterOptions(this IMvcBuilder builder, Action setup) - { - Validator.ThrowIfNull(builder, nameof(builder)); - Validator.ThrowIfNull(setup, nameof(setup)); - builder.Services.Configure(DefaultXmlFormatterOptions(setup)); - return builder; - } - - private static Action DefaultXmlFormatterOptions(Action setup) - { - var options = Patterns.Configure(setup); - return o => - { - o.IncludeExceptionStackTrace = options.IncludeExceptionStackTrace; - o.SynchronizeWithXmlConvert = options.SynchronizeWithXmlConvert; - o.Settings.Writer = options.Settings.Writer; - o.Settings.RootName = options.Settings.RootName; - o.Settings.Reader = options.Settings.Reader; - Decorator.Enclose(o.Settings.Converters).AddRange(options.Settings.Converters); - o.Settings.Converters.AddStringValuesConverter(); - o.Settings.Converters.AddHeaderDictionaryConverter(); - o.Settings.Converters.AddQueryCollectionConverter(); - o.Settings.Converters.AddFormCollectionConverter(); - o.Settings.Converters.AddCookieCollectionConverter(); - o.Settings.Converters.AddHttpExceptionDescriptorConverter(); - }; - } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc/CacheableObjectFactory.cs b/src/Cuemon.AspNetCore.Mvc/CacheableObjectFactory.cs index f7ac1db7e..e60a5f1e1 100644 --- a/src/Cuemon.AspNetCore.Mvc/CacheableObjectFactory.cs +++ b/src/Cuemon.AspNetCore.Mvc/CacheableObjectFactory.cs @@ -86,7 +86,7 @@ public static ICacheableObjectResult CreateCacheableObjectResult(T instance, /// An implementation. /// /// - /// + /// /// /// /// @@ -110,7 +110,7 @@ public static ICacheableObjectResult CreateCacheableObjectResult(object instance /// An implementation. /// /// - /// + /// /// /// /// diff --git a/src/Cuemon.AspNetCore.Mvc/ContentBasedObjectResult.cs b/src/Cuemon.AspNetCore.Mvc/ContentBasedObjectResult.cs index 40168ad01..39ae30f86 100644 --- a/src/Cuemon.AspNetCore.Mvc/ContentBasedObjectResult.cs +++ b/src/Cuemon.AspNetCore.Mvc/ContentBasedObjectResult.cs @@ -1,5 +1,5 @@ using Cuemon.Data.Integrity; -using Cuemon.Security.Cryptography; +using Cuemon.Security; namespace Cuemon.AspNetCore.Mvc { @@ -9,13 +9,13 @@ internal ContentBasedObjectResult(object instance, byte[] checksum, bool isWeak { Checksum = new HashResult(checksum); Validation = checksum == null || checksum.Length == 0 - ? EntityDataIntegrityStrength.Unspecified + ? EntityDataIntegrityValidation.Unspecified : isWeak - ? EntityDataIntegrityStrength.Weak - : EntityDataIntegrityStrength.Strong; + ? EntityDataIntegrityValidation.Weak + : EntityDataIntegrityValidation.Strong; } - public EntityDataIntegrityStrength Validation { get; } + public EntityDataIntegrityValidation Validation { get; } public HashResult Checksum { get; } } diff --git a/src/Cuemon.AspNetCore.Mvc/ContentTimeBasedObjectResult.cs b/src/Cuemon.AspNetCore.Mvc/ContentTimeBasedObjectResult.cs index 6a0d56def..878ebecaa 100644 --- a/src/Cuemon.AspNetCore.Mvc/ContentTimeBasedObjectResult.cs +++ b/src/Cuemon.AspNetCore.Mvc/ContentTimeBasedObjectResult.cs @@ -1,10 +1,10 @@ using System; using Cuemon.Data.Integrity; -using Cuemon.Security.Cryptography; +using Cuemon.Security; namespace Cuemon.AspNetCore.Mvc { - internal class ContentTimeBasedObjectResult : CacheableObjectResult, IEntityData + internal class ContentTimeBasedObjectResult : CacheableObjectResult, IEntityInfo { internal ContentTimeBasedObjectResult(object instance, IEntityDataTimestamp timestamp, IEntityDataIntegrity dataIntegrity) : base(instance) { @@ -18,7 +18,7 @@ internal ContentTimeBasedObjectResult(object instance, IEntityDataTimestamp time public DateTime? Modified { get; set; } - public EntityDataIntegrityStrength Validation { get; set; } + public EntityDataIntegrityValidation Validation { get; set; } public HashResult Checksum { get; set; } } diff --git a/src/Cuemon.AspNetCore.Mvc/Cuemon.AspNetCore.Mvc.csproj b/src/Cuemon.AspNetCore.Mvc/Cuemon.AspNetCore.Mvc.csproj index 270f62836..12c9fe0ca 100644 --- a/src/Cuemon.AspNetCore.Mvc/Cuemon.AspNetCore.Mvc.csproj +++ b/src/Cuemon.AspNetCore.Mvc/Cuemon.AspNetCore.Mvc.csproj @@ -1,32 +1,15 @@  - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netcoreapp3.0;netstandard2.0 a20adf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.AspNetCore.Mvc Cuemon.AspNetCore.Mvc - The Cuemon.AspNetCore.Mvc assembly is a fit companion to the Microsoft.AspNetCore.Mvc namespace that provides an abundant range of filters and action results to your codebelt. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.AspNetCore,Mvc namespace contains types that specializes in cache expiration and validation models and an abundant range of ready-to-use filters in the ASP.NET Core MVC pipeline. The namespace is an addition to the Microsoft.AspNetCore.Mvc namespace. + cacheable-object-factory cacheable-object-result content-based-object-result content-time-based-object-result see-other-result http-cacheable-filter http-entity-tag-header-filter http-last-modified-header-filter fault-descriptor-filter fault-resolver http-request-evidence server-timing-filter user-agent-sentinel-filter throttling-sentinel-filter @@ -35,7 +18,7 @@ - + \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc/ExceptionDescriptorResult.cs b/src/Cuemon.AspNetCore.Mvc/ExceptionDescriptorResult.cs index f0e5e218c..f65a70015 100644 --- a/src/Cuemon.AspNetCore.Mvc/ExceptionDescriptorResult.cs +++ b/src/Cuemon.AspNetCore.Mvc/ExceptionDescriptorResult.cs @@ -1,5 +1,5 @@ using System; -using Cuemon.AspNetCore.Http; +using Cuemon.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; namespace Cuemon.AspNetCore.Mvc diff --git a/src/Cuemon.AspNetCore.Mvc/Extensions/Filters/Diagnostics/FaultResolverDecoratorExtensions.cs b/src/Cuemon.AspNetCore.Mvc/Extensions/Filters/Diagnostics/FaultResolverDecoratorExtensions.cs index d683840f1..5ebd07f20 100644 --- a/src/Cuemon.AspNetCore.Mvc/Extensions/Filters/Diagnostics/FaultResolverDecoratorExtensions.cs +++ b/src/Cuemon.AspNetCore.Mvc/Extensions/Filters/Diagnostics/FaultResolverDecoratorExtensions.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using Cuemon.AspNetCore.Diagnostics; using Cuemon.AspNetCore.Http; -using Cuemon.AspNetCore.Mvc.Filters.Diagnostics; -namespace Cuemon.AspNetCore.Mvc.Extensions.Filters.Diagnostics +namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics { /// /// Extension methods for the class tailored to adhere the decorator pattern. diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpCacheableFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpCacheableFilter.cs index 1bb4878c9..f3ecbacdf 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpCacheableFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpCacheableFilter.cs @@ -30,7 +30,7 @@ public override async Task OnResultExecutionAsync(ResultExecutingContext context { foreach (var filter in Options.Filters) { - await filter.OnResultExecutionAsync(context, next); + await filter.OnResultExecutionAsync(context, next).ConfigureAwait(false); } if (context.Result is ObjectResult result && result.Value is ICacheableObjectResult cacheableObjectResult) diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderFilter.cs index ff8578238..466492263 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderFilter.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Cuemon.AspNetCore.Http; using Cuemon.Configuration; -using Cuemon.Data; using Cuemon.Data.Integrity; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -20,8 +19,8 @@ public class HttpEntityTagHeaderFilter : IConfigurable /// Initializes a new instance of the class. /// - /// The which need to be configured. - public HttpEntityTagHeaderFilter(Action setup) + /// The which may be configured. + public HttpEntityTagHeaderFilter(Action setup = null) { Options = Patterns.Configure(setup); } @@ -61,12 +60,12 @@ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultE originalValue = result.Value; result.Value = cacheableObjectResult.Value; } - await InvokeEntityTagResponseParser(context, next, statusCodeBeforeBodyRead); + await InvokeEntityTagResponseParser(context, next, statusCodeBeforeBodyRead).ConfigureAwait(false); result.Value = originalValue; } else { - await InvokeEntityTagResponseParser(context, next, statusCodeBeforeBodyRead); + await InvokeEntityTagResponseParser(context, next, statusCodeBeforeBodyRead).ConfigureAwait(false); } } } diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderOptions.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderOptions.cs index 31799ce30..71bd0edf4 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderOptions.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpEntityTagHeaderOptions.cs @@ -1,8 +1,9 @@ using System; using System.IO; using Cuemon.AspNetCore.Http; -using Cuemon.Data; using Cuemon.Data.Integrity; +using Cuemon.IO; +using Cuemon.Security; using Cuemon.Security.Cryptography; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -31,8 +32,8 @@ public class HttpEntityTagHeaderOptions /// /// (integrity, context) => /// { - /// var builder = new ChecksumBuilder(integrity.Checksum.Value); - /// context.Response.SetEntityTagHeaderInformation(context.Request, builder, integrity.ChecksumStrength == ChecksumStrength.Weak); + /// var builder = new ChecksumBuilder(integrity.Checksum.GetBytes(), () => HashFactory.CreateFnv128()); + /// Decorator.Enclose(context.Response).TryAddOrUpdateEntityTagHeader(context.Request, builder, integrity.Validation == EntityDataIntegrityValidation.Weak); /// }; /// /// @@ -41,12 +42,10 @@ public class HttpEntityTagHeaderOptions /// /// (body, request, response) => /// { - /// var builder = new ChecksumBuilder(body.ComputeHash(o => - /// { - /// o.AlgorithmType = HashAlgorithmType.MD5; - /// o.LeaveStreamOpen = true; - /// }).Value); - /// response.SetEntityTagHeaderInformation(request, builder); + /// var ms = new MemoryStream(); + /// Decorator.Enclose(body).CopyStream(ms); + /// var builder = new ChecksumBuilder(ms.ToArray(), () => UnkeyedHashFactory.CreateCryptoMd5()); + /// Decorator.Enclose(response).TryAddOrUpdateEntityTagHeader(request, builder); /// }; /// /// @@ -60,13 +59,15 @@ public HttpEntityTagHeaderOptions() { EntityTagProvider = (integrity, context) => { - var builder = new ChecksumBuilder(integrity.Checksum.GetBytes()); - Decorator.Enclose(context.Response).TryAddOrUpdateEntityTagHeader(context.Request, builder, integrity.Validation == EntityDataIntegrityStrength.Weak); + var builder = new ChecksumBuilder(integrity.Checksum.GetBytes(), () => HashFactory.CreateFnv128()); + Decorator.Enclose(context.Response).AddOrUpdateEntityTagHeader(context.Request, builder, integrity.Validation == EntityDataIntegrityValidation.Weak); }; EntityTagResponseParser = (body, request, response) => { - var builder = new ChecksumBuilder(HashFactory.CreateCrypto(CryptoAlgorithm.Md5).ComputeHash(body).GetBytes()); - Decorator.Enclose(response).TryAddOrUpdateEntityTagHeader(request, builder); + var ms = new MemoryStream(); + Decorator.Enclose(body).CopyStream(ms); + var builder = new ChecksumBuilder(ms.ToArray(), () => UnkeyedHashFactory.CreateCryptoMd5()); + Decorator.Enclose(response).AddOrUpdateEntityTagHeader(request, builder); }; UseEntityTagResponseParser = false; } diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderFilter.cs index c932c0b3b..11b7dc967 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderFilter.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Cuemon.AspNetCore.Http; using Cuemon.Configuration; -using Cuemon.Data; using Cuemon.Data.Integrity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -18,8 +17,8 @@ public class HttpLastModifiedHeaderFilter : IConfigurable /// Initializes a new instance of the class. /// - /// The which need to be configured. - public HttpLastModifiedHeaderFilter(Action setup) + /// The which may be configured. + public HttpLastModifiedHeaderFilter(Action setup = null) { Options = Patterns.Configure(setup); } diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderOptions.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderOptions.cs index 617a860b5..eb30642b1 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderOptions.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/HttpLastModifiedHeaderOptions.cs @@ -1,6 +1,5 @@ using System; using Cuemon.AspNetCore.Http; -using Cuemon.Data; using Cuemon.Data.Integrity; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -29,7 +28,7 @@ public class HttpLastModifiedHeaderOptions /// /// (timestamp, context) => /// { - /// context.Response.SetLastModifiedHeaderInformation(context.Request, timestamp.Modified ?? timestamp.Created); + /// Decorator.Enclose(context.Response).TryAddOrUpdateLastModifiedHeader(context.Request, timestamp.Modified ?? timestamp.Created); /// }; /// /// @@ -39,7 +38,7 @@ public HttpLastModifiedHeaderOptions() { LastModifiedProvider = (timestamp, context) => { - Decorator.Enclose(context.Response).TryAddOrUpdateLastModifiedHeader(context.Request, timestamp.Modified ?? timestamp.Created); + Decorator.Enclose(context.Response).AddOrUpdateLastModifiedHeader(context.Request, timestamp.Modified ?? timestamp.Created); }; } diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/ICacheableAsyncResultFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/ICacheableAsyncResultFilter.cs index 7421e615f..797d5b534 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/ICacheableAsyncResultFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Cacheable/ICacheableAsyncResultFilter.cs @@ -1,19 +1,11 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Filters; namespace Cuemon.AspNetCore.Mvc.Filters.Cacheable { /// - /// A filter that asynchronously surrounds execution of action results successfully returned from an action. + /// A filter tailored to the cacheable flows, that asynchronously surrounds execution of action results successfully returned from an action. /// - public interface ICacheableAsyncResultFilter + public interface ICacheableAsyncResultFilter : IAsyncResultFilter { - /// - /// Called asynchronously before the action result. - /// - /// The . - /// The . Invoked to execute the next result filter or the result itself. - /// A that on completion indicates the filter has executed. - Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next); } } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorFilter.cs index dbe45c613..a8c965cec 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorFilter.cs @@ -12,7 +12,6 @@ namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics { /// /// A filter that, after an action has faulted, provides developer friendly information about an along with a correct . - /// Implements the /// /// /// @@ -21,6 +20,11 @@ namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics /// public class FaultDescriptorFilter : Configurable, IExceptionFilter { + /// + /// The key to set or get a copy of a captured request body. + /// + public const string HttpContextItemsKeyForCapturedRequestBody = "CuemonAspNetCoreMvcFiltersDiagnostics_HttpContextItemsKeyForCapturedRequestBody"; + /// /// Initializes a new instance of the class. /// @@ -32,7 +36,7 @@ public FaultDescriptorFilter(IOptions setup) : base(setu /// /// Called after an action has thrown an . /// - /// The . + /// The . public virtual void OnException(ExceptionContext context) { if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor) diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorOptions.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorOptions.cs index da3243bd4..4011a56d8 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorOptions.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultDescriptorOptions.cs @@ -3,10 +3,9 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Reflection; -using Cuemon.AspNetCore.Http; +using Cuemon.AspNetCore.Diagnostics; using Cuemon.AspNetCore.Http.Headers; using Cuemon.AspNetCore.Http.Throttling; -using Cuemon.AspNetCore.Mvc.Extensions.Filters.Diagnostics; using Microsoft.AspNetCore.Http; namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultResolver.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultResolver.cs index 3279e6b0c..2809302ca 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultResolver.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/FaultResolver.cs @@ -1,5 +1,5 @@ using System; -using Cuemon.AspNetCore.Http; +using Cuemon.AspNetCore.Diagnostics; namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics { diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/HttpRequestEvidence.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/HttpRequestEvidence.cs index 01ebcac1e..3418b1b63 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/HttpRequestEvidence.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/HttpRequestEvidence.cs @@ -11,22 +11,23 @@ namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics /// public class HttpRequestEvidence { - // TODO: REMEMBER TO TEST THIS THOROUGHLY ON ASPNET CORE 3 - internal HttpRequestEvidence(HttpRequest request, Func bodyParser = null) + internal HttpRequestEvidence(HttpRequest request, Func bodyConverter = null) { var hasMultipartContentType = request.GetMultipartBoundary().Length > 0; - if (bodyParser == null) { bodyParser = body => hasMultipartContentType ? null : Decorator.Enclose(body).ToEncodedString(); } + if (bodyConverter == null) { bodyConverter = body => hasMultipartContentType ? null : Decorator.Enclose(body).ToEncodedString(); } Location = request.GetDisplayUrl(); Method = request.Method; Headers = request.Headers; Query = request.Query; if (request.HasFormContentType && !hasMultipartContentType) { Form = request.Form; } Cookies = request.Cookies; - #if NETCOREAPP - Body = bodyParser(request.BodyReader.AsStream(true)); - #else - Body = bodyParser(request.Body); - #endif + var requestBody = new MemoryStream(); + if (request.HttpContext.Items.TryGetValue(FaultDescriptorFilter.HttpContextItemsKeyForCapturedRequestBody, out var capturedRequestBody) && capturedRequestBody is MemoryStream crb) + { + crb.Position = 0; + requestBody = crb; + } + Body = bodyConverter(requestBody); } /// diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasureAttribute.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingAttribute.cs similarity index 62% rename from src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasureAttribute.cs rename to src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingAttribute.cs index 7dd9af602..463acad24 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasureAttribute.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingAttribute.cs @@ -10,37 +10,38 @@ namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics /// Represents an attribute that is used to mark an action method for time measure profiling. /// /// - public class TimeMeasureAttribute : ActionFilterAttribute, IFilterFactory + public class ServerTimingAttribute : ActionFilterAttribute, IFilterFactory { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public TimeMeasureAttribute() : this(0, TimeUnit.Ticks) + public ServerTimingAttribute() { } /// - /// Initializes a new instance of the class. + /// Gets or sets the server-specified metric name. /// - /// The value that in combination with specifies the threshold of the action method. - /// One of the enumeration values that specifies the time unit of . - public TimeMeasureAttribute(double threshold, TimeUnit thresholdTimeUnit) - { - Threshold = threshold; - ThresholdTimeUnit = thresholdTimeUnit; - } + /// The server-specified metric name. + public string Name { get; set; } + + /// + /// Gets or sets the server-specified metric description. + /// + /// The server-specified metric description. + public string Description { get; set; } /// /// Gets or sets the value that in combination with specifies the threshold of the action method. /// /// The threshold value of the action method. - public double Threshold { get; set; } + public double Threshold { get; set; } = 0; /// /// Gets or sets one of the enumeration values that specifies the time unit of . /// /// The that defines the actual . - public TimeUnit ThresholdTimeUnit { get; set; } + public TimeUnit ThresholdTimeUnit { get; set; } = TimeUnit.Ticks; /// /// Creates an instance of the executable filter. @@ -54,10 +55,21 @@ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) #elif NETCOREAPP var he = serviceProvider.GetRequiredService(); #endif - return new TimeMeasuringFilter(Options.Create(new TimeMeasuringOptions() + var filter = new ServerTimingFilter(Options.Create(new ServerTimingOptions() { TimeMeasureCompletedThreshold = Decorator.Enclose(Threshold).ToTimeSpan(ThresholdTimeUnit) - }), he); + }), he) + { + Name = Name, + Description = Description + }; + var stOptions = serviceProvider.GetService>(); + if (stOptions?.Value?.SuppressHeaderPredicate != null) + { + filter.Options.SuppressHeaderPredicate = stOptions.Value.SuppressHeaderPredicate; + } + + return filter; } /// diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasuringFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingFilter.cs similarity index 75% rename from src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasuringFilter.cs rename to src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingFilter.cs index c6386d512..70ace5571 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasuringFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingFilter.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; -using Cuemon.AspNetCore.Http.Headers; +using Cuemon.AspNetCore.Diagnostics; using Cuemon.Diagnostics; using Cuemon.Reflection; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; @@ -17,26 +18,26 @@ namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics /// /// /// - public class TimeMeasuringFilter : ConfigurableActionFilter + public class ServerTimingFilter : ConfigurableActionFilter { #if NETSTANDARD /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The which need to be configured. /// The dependency injected . - public TimeMeasuringFilter(IOptions setup, IHostingEnvironment he) : base(setup) + public ServerTimingFilter(IOptions setup, IHostingEnvironment he) : base(setup) { Profiler = new TimeMeasureProfiler(); Environment = he; } #elif NETCOREAPP /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The which need to be configured. /// The dependency injected . - public TimeMeasuringFilter(IOptions setup, IHostEnvironment he) : base(setup) + public ServerTimingFilter(IOptions setup, IHostEnvironment he) : base(setup) { Profiler = new TimeMeasureProfiler(); Environment = he; @@ -51,6 +52,10 @@ public TimeMeasuringFilter(IOptions setup, IHostEnvironmen private TimeMeasureProfiler Profiler { get; } + internal string Name { get; set; } + + internal string Description { get; set; } + /// /// Called before the action executes, after model binding is complete. /// @@ -76,13 +81,14 @@ public override void OnActionExecuting(ActionExecutingContext context) public override void OnActionExecuted(ActionExecutedContext context) { Profiler.Timer.Stop(); + var serverTiming = context.HttpContext.RequestServices.GetRequiredService(); + serverTiming.AddServerTiming(Name ?? "mvc", Profiler.Elapsed, Description ?? $"[{Decorator.Enclose(Profiler.Member.MethodName).ToAsciiEncodedString()}@{Decorator.Enclose(Profiler.Member.Caller.Name).ToAsciiEncodedString()}]({context.HttpContext.Request.GetEncodedUrl().ToLowerInvariant()})"); if (Options.TimeMeasureCompletedThreshold == TimeSpan.Zero || Profiler.Elapsed > Options.TimeMeasureCompletedThreshold) { TimeMeasure.CompletedCallback?.Invoke(Profiler); if (!Options.SuppressHeaderPredicate(Environment)) { - if (Options.UseServerTimingHeader) { Decorator.Enclose(context.HttpContext.Response.Headers).TryAddOrUpdateHeader("Server-Timing", FormattableString.Invariant($"CPU;dur={Profiler.Elapsed.TotalMilliseconds.ToString("N1", CultureInfo.InvariantCulture)}")); } - if (Options.UseCustomHeader) { Decorator.Enclose(context.HttpContext.Response.Headers).TryAddOrUpdateHeader(Options.HeaderName, Profiler.ToString()); } + context.HttpContext.Response.Headers.Add(ServerTiming.HeaderName, serverTiming.Metrics.Select(metric => metric.ToString()).ToArray()); } } } diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingOptions.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingOptions.cs new file mode 100644 index 000000000..e6a2afc95 --- /dev/null +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/ServerTimingOptions.cs @@ -0,0 +1,48 @@ +using System; +using Cuemon.Diagnostics; +using Microsoft.Extensions.Hosting; + +namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics +{ + /// + /// Configuration options for . + /// + /// + public class ServerTimingOptions : TimeMeasureOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// + /// + /// + /// + /// he => he.IsProduction() + /// + /// + /// + public ServerTimingOptions() + { + SuppressHeaderPredicate = he => he.IsProduction(); + } + + /// + /// Gets or sets the predicate that can suppress the Server-Timing HTTP header(s). + /// + /// The function delegate that can determine if the Server-Timing HTTP header(s) should be suppressed. + #if NETSTANDARD + public Func SuppressHeaderPredicate { get; set; } + #elif NETCOREAPP + public Func SuppressHeaderPredicate { get; set; } + #endif + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasuringOptions.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasuringOptions.cs deleted file mode 100644 index 20cbb0c5e..000000000 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Diagnostics/TimeMeasuringOptions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using Cuemon.Diagnostics; -using Microsoft.Extensions.Hosting; - -namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics -{ - /// - /// Configuration options for . - /// - /// - public class TimeMeasuringOptions : TimeMeasureOptions - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The following table shows the initial property values for an instance of . - /// - /// - /// Property - /// Initial Value - /// - /// - /// - /// - /// - /// - /// - /// X-Action-Profiler - /// - /// - /// - /// _ => false - /// - /// - /// - /// true - /// - /// - /// - public TimeMeasuringOptions() - { - HeaderName = "X-Action-Profiler"; - SuppressHeaderPredicate = _ => false; - UseServerTimingHeader = true; - UseCustomHeader = true; - } - - /// - /// Gets or sets the name of the custom time-measured HTTP header. - /// - /// The name of the custom time-measured HTTP header. - public string HeaderName { get; set; } - - /// - /// Gets or sets the predicate that can suppress either of the time-measured HTTP headers. - /// - /// The function delegate that can determine if either of the time-measured HTTP headers should be suppressed. - #if NETSTANDARD - public Func SuppressHeaderPredicate { get; set; } - #elif NETCOREAPP - public Func SuppressHeaderPredicate { get; set; } - #endif - - /// - /// Gets or sets a value indicating whether to include a Server-Timing HTTP header specifying how long an action took to execute. - /// - /// true to include a Server-Timing HTTP header specifying how long an action took to execute; otherwise, false. - public bool UseServerTimingHeader { get; set; } - - /// - /// Gets or sets a value indicating whether to include the custom specifying how long an action took to execute. - /// - /// true to include the custom specifying how long an action took to execute; otherwise, false. - public bool UseCustomHeader { get; set; } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Headers/UserAgentSentinelFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Headers/UserAgentSentinelFilter.cs index 61744fa8b..601fd7a4f 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Headers/UserAgentSentinelFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Headers/UserAgentSentinelFilter.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; +using Cuemon.AspNetCore.Http; using Cuemon.AspNetCore.Http.Headers; -using Cuemon.AspNetCore.Infrastructure; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; @@ -29,10 +29,10 @@ public UserAgentSentinelFilter(IOptions setup) : base( /// A that on completion indicates the filter has executed. public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - await AspNetCoreInfrastructure.InvokeUserAgentSentinelAsync(context.HttpContext, Options, (message, response) => + await Decorator.Enclose(context.HttpContext).InvokeUserAgentSentinelAsync(Options, (message, response) => { response.StatusCode = (int) message.StatusCode; - Decorator.Enclose(response.Headers).TryAddOrUpdateHeaders(message.Headers); + Decorator.Enclose(response.Headers).AddOrUpdateHeaders(message.Headers); }).ConfigureAwait(false); await next().ConfigureAwait(false); } diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelAttribute.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelAttribute.cs index d3c03c833..6b7c9767b 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelAttribute.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelAttribute.cs @@ -1,6 +1,7 @@ using System; -using System.Text; +using Cuemon.AspNetCore.Http.Headers; using Cuemon.AspNetCore.Http.Throttling; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -11,7 +12,7 @@ namespace Cuemon.AspNetCore.Mvc.Filters.Throttling /// Represents an attribute that is used to mark an action method to be protected by a throttling sentinel. /// /// - public class ThrottlingSentinelAttribute : ActionFilterAttribute, IFilterFactory + public abstract class ThrottlingSentinelAttribute : ActionFilterAttribute, IFilterFactory { /// /// Initializes a new instance of the class. @@ -19,18 +20,19 @@ public class ThrottlingSentinelAttribute : ActionFilterAttribute, IFilterFactory /// The allowed rate from within a given . /// The duration of the window. /// One of the enumeration values that specifies the time unit of . - public ThrottlingSentinelAttribute(int rateLimit, double window, TimeUnit windowUnit) + protected ThrottlingSentinelAttribute(int rateLimit, double window, TimeUnit windowUnit) { var options= new ThrottlingSentinelOptions(); RateLimit = rateLimit; Window = window; WindowUnit = windowUnit; UseRetryAfterHeader = options.UseRetryAfterHeader; - RetryAfterHeader = options.RetryAfterHeader; + RetryAfterScope = options.RetryAfterScope; TooManyRequestsMessage = options.TooManyRequestsMessage; RateLimitHeaderName = options.RateLimitHeaderName; RateLimitRemainingHeaderName = options.RateLimitRemainingHeaderName; RateLimitResetHeaderName = options.RateLimitResetHeaderName; + RateLimitResetScope = options.RateLimitResetScope; } private int RateLimit { get; } @@ -55,7 +57,7 @@ public ThrottlingSentinelAttribute(int rateLimit, double window, TimeUnit window /// Gets or sets the preferred Retry-After HTTP header value that conforms with RFC 2616. /// /// The preferred Retry-After HTTP header value that conforms with RFC 2616. - public ThrottlingRetryAfterHeader RetryAfterHeader { get; set; } + public RetryConditionScope RetryAfterScope { get; set; } /// /// Gets or sets the name of the rate limit remaining HTTP header. @@ -75,6 +77,12 @@ public ThrottlingSentinelAttribute(int rateLimit, double window, TimeUnit window /// The name of the rate limit reset HTTP header. public string RateLimitResetHeaderName { get; set; } + /// + /// Gets or sets the preferred rate limit reset HTTP header value that conforms with RFC 7231. + /// + /// The preferred rate limit reset HTTP header value that conforms with RFC 7231. + public RetryConditionScope RateLimitResetScope { get; set; } + /// /// Creates an instance of the executable filter. /// @@ -86,16 +94,24 @@ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) return new ThrottlingSentinelFilter(Options.Create(new ThrottlingSentinelOptions() { Quota = new ThrottleQuota(RateLimit, Window, WindowUnit), - ContextResolver = context => new StringBuilder().Append(context.Request.Scheme).Append("://").Append(context.Request.Host).Append(context.Request.PathBase).Append(context.Request.Path).ToString().ToLowerInvariant(), + ContextResolver = UniqueContextResolver, UseRetryAfterHeader = UseRetryAfterHeader, - RetryAfterHeader = RetryAfterHeader, + RetryAfterScope = RetryAfterScope, TooManyRequestsMessage = TooManyRequestsMessage, RateLimitHeaderName = RateLimitHeaderName, RateLimitRemainingHeaderName = RateLimitRemainingHeaderName, - RateLimitResetHeaderName = RateLimitResetHeaderName + RateLimitResetHeaderName = RateLimitResetHeaderName, + RateLimitResetScope = RateLimitResetScope }), tc); } + /// + /// Resolves a unique context of the throttling middleware (eg. IP-address, Authorization header, etc.). + /// + /// The to extract a unique context from. + /// A string that uniquely identifies the requester in need of throttling. + public abstract string UniqueContextResolver(HttpContext context); + /// /// Gets a value that indicates if the result of can be reused across requests. /// diff --git a/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelFilter.cs b/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelFilter.cs index 64ca787be..561fd254d 100644 --- a/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelFilter.cs +++ b/src/Cuemon.AspNetCore.Mvc/Filters/Throttling/ThrottlingSentinelFilter.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; +using Cuemon.AspNetCore.Http; using Cuemon.AspNetCore.Http.Headers; using Cuemon.AspNetCore.Http.Throttling; -using Cuemon.AspNetCore.Infrastructure; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; @@ -34,10 +34,10 @@ public ThrottlingSentinelFilter(IOptions setup, IThro /// A that on completion indicates the filter has executed. public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - await AspNetCoreInfrastructure.InvokeThrottlerSentinelAsync(context.HttpContext, ThrottlingCache, Options, (message, response) => + await Decorator.Enclose(context.HttpContext).InvokeThrottlerSentinelAsync(ThrottlingCache, Options, (message, response) => { response.StatusCode = (int) message.StatusCode; - Decorator.Enclose(response.Headers).TryAddOrUpdateHeaders(message.Headers); + Decorator.Enclose(response.Headers).AddOrUpdateHeaders(message.Headers); }).ConfigureAwait(false); await next().ConfigureAwait(false); } diff --git a/src/Cuemon.AspNetCore.Mvc/GlobalSuppressions.cs b/src/Cuemon.AspNetCore.Mvc/GlobalSuppressions.cs new file mode 100644 index 000000000..9b5d87846 --- /dev/null +++ b/src/Cuemon.AspNetCore.Mvc/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S3358:Ternary operators should not be nested", Justification = "Clear enough.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Mvc.ContentBasedObjectResult.#ctor(System.Object,System.Byte[],System.Boolean)")] +[assembly: SuppressMessage("Major Code Smell", "S1066:Collapsible \"if\" statements should be merged", Justification = "By design; easier for debug purposes and with clear scope.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Mvc.Filters.Cacheable.HttpLastModifiedHeaderFilter.OnResultExecutionAsync(Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext,Microsoft.AspNetCore.Mvc.Filters.ResultExecutionDelegate)~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S1066:Collapsible \"if\" statements should be merged", Justification = "By design; easier for debug purposes and with clear scope.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Mvc.Filters.Cacheable.HttpEntityTagHeaderFilter.OnResultExecutionAsync(Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext,Microsoft.AspNetCore.Mvc.Filters.ResultExecutionDelegate)~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Critical Code Smell", "S3776:Cognitive Complexity of methods should not be too high", Justification = "If i invert the if-statement, the warning goes away - but the code becomes harder to read. So for now, i exclude it as 'by design'.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Mvc.Filters.Diagnostics.FaultDescriptorFilter.OnException(Microsoft.AspNetCore.Mvc.Filters.ExceptionContext)")] diff --git a/src/Cuemon.AspNetCore.Mvc/ICacheableObjectResult.cs b/src/Cuemon.AspNetCore.Mvc/ICacheableObjectResult.cs index 68b12a07b..ae9ed9c80 100644 --- a/src/Cuemon.AspNetCore.Mvc/ICacheableObjectResult.cs +++ b/src/Cuemon.AspNetCore.Mvc/ICacheableObjectResult.cs @@ -7,7 +7,7 @@ namespace Cuemon.AspNetCore.Mvc /// /// . /// . - /// . + /// . public interface ICacheableObjectResult { /// diff --git a/src/Cuemon.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt b/src/Cuemon.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..b9612900c --- /dev/null +++ b/src/Cuemon.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt @@ -0,0 +1,24 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Upgrade Steps +- Any former extension methods of the Cuemon.AspNetCore.Mvc namespace was merged either into the Cuemon.Extensions.AspNetCore.Mvc namespace or Cuemon.Extensions.AspNetCore namespace +  +# Breaking Changes +- MOVED ICacheBusting interface (and related) from the Cuemon.AspNetCore.Mvc.Configuration namespace to Cuemon.AspNetCore.Configuration namespace +- MOVED AssemblyCacheBusting class (and related) from the Cuemon.AspNetCore.Mvc.Configuration namespace to Cuemon.Extensions.AspNetCore.Configuration namespace +- MOVED ICacheableObjectResult interface (and related) from the Cuemon.AspNetCore.Mvc.Filters.Cacheable namespace to Cuemon.AspNetCore.Mvc namespace +- RENAMED HttpEntityTagHeader class in the Cuemon.AspNetCore.Mvc.Filters.Cacheable namespace to HttpEntityTagHeaderFilter +- RENAMED HttpLastModifiedHeader class in the Cuemon.AspNetCore.Mvc.Filters.Cacheable namespace to HttpLastModifiedHeaderFilter +- RENAMED TimeMeasureAttribute class in the Cuemon.AspNetCore.Mvc.Filters.Diagnostics namespace to ServerTimingAttribute (including refactoring) +- RENAMED TimeMeasuringFilter class in the Cuemon.AspNetCore.Mvc.Filters.Diagnostics namespace to ServerTimingFilter (including refactoring) +- RENAMED TimeMeasuringOptions class in the Cuemon.AspNetCore.Mvc.Filters.Diagnostics namespace to ServerTimingOptions (including refactoring) +  +# New Features +- ADDED TooManyRequestsObjectResult class in the Cuemon.AspNetCore.Mvc namespace that is an ObjectResult that when executed will produce a Too Many Requests (429) response +- ADDED TooManyRequestsResult class in the Cuemon.AspNetCore.Mvc namespace that is an ActionResult that returns a TooManyRequests (429) response +- ADDED CacheableObjectFactory class in the Cuemon.AspNetCore.Mvc namespace that provides access to factory methods for creating and configuring objects implementing the ICacheableObjectResult interface +  +# Improvements +- COMPATIBLE with the changes applied to NET Core 3 in regards to only allowing asynchronous I/O +  \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc/TimeBasedObjectResult.cs b/src/Cuemon.AspNetCore.Mvc/TimeBasedObjectResult.cs index aa750b4eb..6e5631f93 100644 --- a/src/Cuemon.AspNetCore.Mvc/TimeBasedObjectResult.cs +++ b/src/Cuemon.AspNetCore.Mvc/TimeBasedObjectResult.cs @@ -1,5 +1,4 @@ using System; -using Cuemon.Data; using Cuemon.Data.Integrity; namespace Cuemon.AspNetCore.Mvc diff --git a/src/Cuemon.AspNetCore.Mvc/TooManyRequestsObjectResult.cs b/src/Cuemon.AspNetCore.Mvc/TooManyRequestsObjectResult.cs new file mode 100644 index 000000000..a64e2371f --- /dev/null +++ b/src/Cuemon.AspNetCore.Mvc/TooManyRequestsObjectResult.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Cuemon.AspNetCore.Mvc +{ + /// + /// An that when executed will produce a Too Many Requests (429) response. + /// + public class TooManyRequestsObjectResult : ObjectResult + { + /// + /// Initializes a new instance of the class. + /// + /// Contains the errors to be returned to the client. + public TooManyRequestsObjectResult(object error) : base(error) + { + StatusCode = StatusCodes.Status429TooManyRequests; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc/TooManyRequestsResult.cs b/src/Cuemon.AspNetCore.Mvc/TooManyRequestsResult.cs new file mode 100644 index 000000000..c72f75dc5 --- /dev/null +++ b/src/Cuemon.AspNetCore.Mvc/TooManyRequestsResult.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Cuemon.AspNetCore.Mvc +{ + /// + /// An that returns a TooManyRequests (429) response. + /// + public class TooManyRequestsResult : StatusCodeResult + { + /// + /// Initializes a new instance of the class. + /// + public TooManyRequestsResult() : base(StatusCodes.Status429TooManyRequests) + { + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Razor/TagHelpers/CdnTagHelper.cs b/src/Cuemon.AspNetCore.Razor.TagHelpers/CdnTagHelper.cs similarity index 100% rename from src/Cuemon.AspNetCore.Razor/TagHelpers/CdnTagHelper.cs rename to src/Cuemon.AspNetCore.Razor.TagHelpers/CdnTagHelper.cs diff --git a/src/Cuemon.AspNetCore.Razor/TagHelpers/CdnTagHelperOptions.cs b/src/Cuemon.AspNetCore.Razor.TagHelpers/CdnTagHelperOptions.cs similarity index 100% rename from src/Cuemon.AspNetCore.Razor/TagHelpers/CdnTagHelperOptions.cs rename to src/Cuemon.AspNetCore.Razor.TagHelpers/CdnTagHelperOptions.cs diff --git a/src/Cuemon.AspNetCore.Razor/TagHelpers/CdnUriScheme.cs b/src/Cuemon.AspNetCore.Razor.TagHelpers/CdnUriScheme.cs similarity index 100% rename from src/Cuemon.AspNetCore.Razor/TagHelpers/CdnUriScheme.cs rename to src/Cuemon.AspNetCore.Razor.TagHelpers/CdnUriScheme.cs diff --git a/src/Cuemon.AspNetCore.Razor.TagHelpers/Cuemon.AspNetCore.Razor.TagHelpers.csproj b/src/Cuemon.AspNetCore.Razor.TagHelpers/Cuemon.AspNetCore.Razor.TagHelpers.csproj new file mode 100644 index 000000000..780dd263d --- /dev/null +++ b/src/Cuemon.AspNetCore.Razor.TagHelpers/Cuemon.AspNetCore.Razor.TagHelpers.csproj @@ -0,0 +1,19 @@ + + + + net5.0;netcoreapp3.0;netstandard2.0 + a30adf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.AspNetCore.Razor.TagHelpers + Cuemon.AspNetCore.Razor.TagHelpers + The Cuemon.AspNetCore.Razor.TagHelpers namespace contains types tailored tag helper implementations. The namespace is an addition to the Microsoft.AspNetCore.Razor.TagHelpers namespace. + cdn-tag-helper cdn-uri-scheme image-cdn-tag-helper link-cdn-tag-helper script-cdn-tag-helper + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Razor/TagHelpers/ImageCdnTagHelper.cs b/src/Cuemon.AspNetCore.Razor.TagHelpers/ImageCdnTagHelper.cs similarity index 100% rename from src/Cuemon.AspNetCore.Razor/TagHelpers/ImageCdnTagHelper.cs rename to src/Cuemon.AspNetCore.Razor.TagHelpers/ImageCdnTagHelper.cs diff --git a/src/Cuemon.AspNetCore.Razor/TagHelpers/LinkCdnTagHelper.cs b/src/Cuemon.AspNetCore.Razor.TagHelpers/LinkCdnTagHelper.cs similarity index 100% rename from src/Cuemon.AspNetCore.Razor/TagHelpers/LinkCdnTagHelper.cs rename to src/Cuemon.AspNetCore.Razor.TagHelpers/LinkCdnTagHelper.cs diff --git a/src/Cuemon.AspNetCore.Razor.TagHelpers/Properties/AssemblyInfo.cs b/src/Cuemon.AspNetCore.Razor.TagHelpers/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8db3562bc --- /dev/null +++ b/src/Cuemon.AspNetCore.Razor.TagHelpers/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("74d499ee-862e-412b-9a2d-fb2fe30d518e")] \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Razor.TagHelpers/Properties/PackageReleaseNotes.txt b/src/Cuemon.AspNetCore.Razor.TagHelpers/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..ffcac726d --- /dev/null +++ b/src/Cuemon.AspNetCore.Razor.TagHelpers/Properties/PackageReleaseNotes.txt @@ -0,0 +1,6 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Upgrade Steps +- Any former extension methods of the Cuemon.AspNetCore.Razor.TagHelpers namespace was merged into the Cuemon.Extensions.Core namespace +  \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Razor/TagHelpers/ScriptCdnTagHelper.cs b/src/Cuemon.AspNetCore.Razor.TagHelpers/ScriptCdnTagHelper.cs similarity index 100% rename from src/Cuemon.AspNetCore.Razor/TagHelpers/ScriptCdnTagHelper.cs rename to src/Cuemon.AspNetCore.Razor.TagHelpers/ScriptCdnTagHelper.cs diff --git a/src/Cuemon.AspNetCore.Razor/Cuemon.AspNetCore.Razor.csproj b/src/Cuemon.AspNetCore.Razor/Cuemon.AspNetCore.Razor.csproj deleted file mode 100644 index 43b37b886..000000000 --- a/src/Cuemon.AspNetCore.Razor/Cuemon.AspNetCore.Razor.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true - a30adf91-e7c7-4cb4-a39d-e1a5374c5602 - - - - Cuemon .NET Standard - Cuemon.AspNetCore.Razor - Cuemon.AspNetCore.Razor - The Cuemon.AspNetCore.Razor assembly is a supplement to Microsoft ASP.NET Core. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT - - - - - - - \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Builder/ApplicationBuilderFactory.cs b/src/Cuemon.AspNetCore/Builder/MiddlewareBuilderFactory.cs similarity index 90% rename from src/Cuemon.AspNetCore/Builder/ApplicationBuilderFactory.cs rename to src/Cuemon.AspNetCore/Builder/MiddlewareBuilderFactory.cs index a2520ff06..4d136e840 100644 --- a/src/Cuemon.AspNetCore/Builder/ApplicationBuilderFactory.cs +++ b/src/Cuemon.AspNetCore/Builder/MiddlewareBuilderFactory.cs @@ -7,7 +7,7 @@ namespace Cuemon.AspNetCore.Builder /// /// Provides support for creating, using and configuring or implementations. /// - public static class ApplicationBuilderFactory + public static class MiddlewareBuilderFactory { /// /// Adds a middleware type to the application request pipeline. @@ -28,7 +28,7 @@ public static IApplicationBuilder UseMiddleware(IApplicationBuilder /// The instance. /// The which need to be configured. /// The instance. - public static IApplicationBuilder UseMiddlewareConfigurable(IApplicationBuilder builder, Action setup = null) + public static IApplicationBuilder UseConfigurableMiddleware(IApplicationBuilder builder, Action setup = null) where TMiddleware : ConfigurableMiddlewareCore where TOptions : class, new() { diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/CacheBustingOptions.cs b/src/Cuemon.AspNetCore/Configuration/CacheBustingOptions.cs similarity index 95% rename from src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/CacheBustingOptions.cs rename to src/Cuemon.AspNetCore/Configuration/CacheBustingOptions.cs index 01be125aa..a16ac6fe6 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/CacheBustingOptions.cs +++ b/src/Cuemon.AspNetCore/Configuration/CacheBustingOptions.cs @@ -1,4 +1,4 @@ -namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration +namespace Cuemon.AspNetCore.Configuration { /// /// Specifies options that is related to operations. diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/DynamicCacheBusting.cs b/src/Cuemon.AspNetCore/Configuration/DynamicCacheBusting.cs similarity index 97% rename from src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/DynamicCacheBusting.cs rename to src/Cuemon.AspNetCore/Configuration/DynamicCacheBusting.cs index fed90ce4d..41cb66a82 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/DynamicCacheBusting.cs +++ b/src/Cuemon.AspNetCore/Configuration/DynamicCacheBusting.cs @@ -2,7 +2,7 @@ using Cuemon.Configuration; using Microsoft.Extensions.Options; -namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration +namespace Cuemon.AspNetCore.Configuration { /// /// Provides cache-busting capabilities on a duration based interval. This class cannot be inherited. diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/DynamicCacheBustingOptions.cs b/src/Cuemon.AspNetCore/Configuration/DynamicCacheBustingOptions.cs similarity index 91% rename from src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/DynamicCacheBustingOptions.cs rename to src/Cuemon.AspNetCore/Configuration/DynamicCacheBustingOptions.cs index b68d5ab2a..39a955cc0 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/DynamicCacheBustingOptions.cs +++ b/src/Cuemon.AspNetCore/Configuration/DynamicCacheBustingOptions.cs @@ -1,9 +1,9 @@ using System; -namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration +namespace Cuemon.AspNetCore.Configuration { /// - /// Specifies options that is related to operations. + /// Specifies options that is related to operations. /// /// public class DynamicCacheBustingOptions : CacheBustingOptions @@ -28,13 +28,13 @@ public class DynamicCacheBustingOptions : CacheBustingOptions /// /// /// - /// 20 minutes + /// 12 hours /// /// /// public DynamicCacheBustingOptions() { - TimeToLive = TimeSpan.FromMinutes(20); + TimeToLive = TimeSpan.FromHours(12); PreferredLength = 8; PreferredCharacters = Alphanumeric.LettersAndNumbers; } diff --git a/src/Cuemon.AspNetCore/Cuemon.AspNetCore.csproj b/src/Cuemon.AspNetCore/Cuemon.AspNetCore.csproj index 4c98a08dc..b6bd4d9e1 100644 --- a/src/Cuemon.AspNetCore/Cuemon.AspNetCore.csproj +++ b/src/Cuemon.AspNetCore/Cuemon.AspNetCore.csproj @@ -1,33 +1,20 @@  - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netcoreapp3.0;netstandard2.0 a00adf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.AspNetCore Cuemon.AspNetCore - The Cuemon.AspNetCore assembly is a supplement to Microsoft ASP.NET Core. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.AspNetCore namespace contains types focusing on providing means for easier plumber coding in the ASP.NET Core pipeline while serving some concrete implementation of the shell as well. The namespace is an addition to the Microsoft.AspNetCore namespace. + configurable-middleware middleware http-exception-descriptor throttling-sentinel-middleware user-agent-sentinel-middleware request-identifier-middleware correlation-identifier-middleware hosting-environment-middleware server-timing cache-busting middleware-builder-factory - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT - + + + @@ -43,8 +30,9 @@ - + + \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Http/HttpExceptionDescriptor.cs b/src/Cuemon.AspNetCore/Diagnostics/HttpExceptionDescriptor.cs similarity index 98% rename from src/Cuemon.AspNetCore/Http/HttpExceptionDescriptor.cs rename to src/Cuemon.AspNetCore/Diagnostics/HttpExceptionDescriptor.cs index 6924e7291..e2433fe23 100644 --- a/src/Cuemon.AspNetCore/Http/HttpExceptionDescriptor.cs +++ b/src/Cuemon.AspNetCore/Diagnostics/HttpExceptionDescriptor.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; -namespace Cuemon.AspNetCore.Http +namespace Cuemon.AspNetCore.Diagnostics { /// /// Provides information about an , in a developer friendly way, optimized for open- and otherwise public application programming interfaces (API). diff --git a/src/Cuemon.AspNetCore/Diagnostics/IServerTiming.cs b/src/Cuemon.AspNetCore/Diagnostics/IServerTiming.cs new file mode 100644 index 000000000..a0be43623 --- /dev/null +++ b/src/Cuemon.AspNetCore/Diagnostics/IServerTiming.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Cuemon.AspNetCore.Diagnostics +{ + /// + /// Represents the Server Timing as per W3C Working Draft 28 July 2020 (https://www.w3.org/TR/2020/WD-server-timing-20200728/). + /// + public interface IServerTiming + { + /// + /// Adds a to the . + /// + /// The server-specified metric name. + /// A reference to this instance after the operation has completed. + IServerTiming AddServerTiming(string name); + + /// + /// Adds a to the . + /// + /// The server-specified metric name. + /// The server-specified metric duration. + /// A reference to this instance after the operation has completed. + IServerTiming AddServerTiming(string name, TimeSpan duration); + + /// + /// Adds a to the . + /// + /// The server-specified metric name. + /// The server-specified metric duration. + /// The server-specified metric description. + /// A reference to this instance after the operation has completed. + IServerTiming AddServerTiming(string name, TimeSpan duration, string description); + + /// + /// Gets the entries used to communicate one or more metrics and descriptions for the given request-response cycle. + /// + /// The entries used to communicate one or more metrics and descriptions for the given request-response cycle. + IEnumerable Metrics { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Diagnostics/ServerTiming.cs b/src/Cuemon.AspNetCore/Diagnostics/ServerTiming.cs new file mode 100644 index 000000000..7fbe6fee7 --- /dev/null +++ b/src/Cuemon.AspNetCore/Diagnostics/ServerTiming.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +namespace Cuemon.AspNetCore.Diagnostics +{ + /// + /// Provides a default implementation of the interface. + /// + /// + public class ServerTiming : IServerTiming + { + /// + /// The name of the Server-Timing header field. + /// + public const string HeaderName = "Server-Timing"; + + private readonly List _metrics = new List(); + + /// + /// Initializes a new instance of the class. + /// + public ServerTiming() + { + } + + /// + /// Adds a to the . + /// + /// The server-specified metric name. + /// A reference to this instance after the operation has completed. + public IServerTiming AddServerTiming(string name) + { + _metrics.Add(new ServerTimingMetric(name)); + return this; + } + + /// + /// Adds a to the . + /// + /// The server-specified metric name. + /// The server-specified metric duration. + /// A reference to this instance after the operation has completed. + public IServerTiming AddServerTiming(string name, TimeSpan duration) + { + _metrics.Add(new ServerTimingMetric(name, duration)); + return this; + } + + /// + /// Adds a to the . + /// + /// The server-specified metric name. + /// The server-specified metric duration. + /// The server-specified metric description. + /// A reference to this instance after the operation has completed. + public IServerTiming AddServerTiming(string name, TimeSpan duration, string description) + { + _metrics.Add(new ServerTimingMetric(name, duration, description)); + return this; + } + + /// + /// Gets the entries used to communicate one or more metrics and descriptions for the given request-response cycle. + /// + /// The entries used to communicate one or more metrics and descriptions for the given request-response cycle. + public IEnumerable Metrics => _metrics; + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Diagnostics/ServerTimingMetric.cs b/src/Cuemon.AspNetCore/Diagnostics/ServerTimingMetric.cs new file mode 100644 index 000000000..3689185e4 --- /dev/null +++ b/src/Cuemon.AspNetCore/Diagnostics/ServerTimingMetric.cs @@ -0,0 +1,62 @@ +using System; +using System.Globalization; + +namespace Cuemon.AspNetCore.Diagnostics +{ + /// + /// Represents a HTTP Server-Timing header field entry to communicate one metric and description for the given request-response cycle. + /// + public class ServerTimingMetric + { + private readonly string _metric; + + /// + /// Initializes a new instance of the class. + /// + /// The server-specified metric name. + /// The server-specified metric duration. + /// The server-specified metric description. + public ServerTimingMetric(string name, TimeSpan? duration = null, string description = null) + { + Validator.ThrowIfNullOrWhitespace(name, nameof(name)); + if (duration.HasValue && duration <= TimeSpan.Zero) { duration = TimeSpan.Zero; } + + var metric = name; + if (duration.HasValue) { metric = string.Concat(metric, ";", FormattableString.Invariant($"dur={duration.Value.TotalMilliseconds.ToString("F1", CultureInfo.InvariantCulture)}")); } + if (description != null) { metric = string.Concat(metric, ";", $"desc=\"{description}\""); } + + Name = name; + Duration = duration; + Description = description; + + _metric = metric; + } + + /// + /// Gets the server-specified metric name. + /// + /// The server-specified metric name. + public string Name { get; } + + /// + /// Gets the server-specified metric duration. + /// + /// The server-specified metric duration. + public TimeSpan? Duration { get; } + + /// + /// Gets the server-specified metric description. + /// + /// The server-specified metric description. + public string Description { get; } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return _metric; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Extensions/Http/Headers/HeaderDictionaryDecoratorExtensions.cs b/src/Cuemon.AspNetCore/Extensions/Http/Headers/HeaderDictionaryDecoratorExtensions.cs index cd51ed490..882722cbf 100644 --- a/src/Cuemon.AspNetCore/Extensions/Http/Headers/HeaderDictionaryDecoratorExtensions.cs +++ b/src/Cuemon.AspNetCore/Extensions/Http/Headers/HeaderDictionaryDecoratorExtensions.cs @@ -24,14 +24,13 @@ public static class HeaderDictionaryDecoratorExtensions /// /// cannot be null. /// - public static bool TryAddOrUpdateHeader(this IDecorator decorator, string key, StringValues value, bool useAsciiEncodingConversion = true) + public static void AddOrUpdateHeader(this IDecorator decorator, string key, StringValues value, bool useAsciiEncodingConversion = true) { var headerValue = useAsciiEncodingConversion ? new StringValues(Decorator.Enclose(value).ToAsciiEncodedString()) : value; if (headerValue != StringValues.Empty) { - return decorator.TryAddOrUpdate(key, Decorator.Enclose(headerValue.ToString().Where(c => !char.IsControl(c))).ToStringEquivalent()); + decorator.AddOrUpdate(key, Decorator.Enclose(headerValue.ToString().Where(c => !char.IsControl(c))).ToStringEquivalent()); } - return false; } /// @@ -39,12 +38,12 @@ public static bool TryAddOrUpdateHeader(this IDecorator decor /// /// The to extend. /// The to copy. - public static void TryAddOrUpdateHeaders(this IDecorator decorator, HttpResponseHeaders responseHeaders) + public static void AddOrUpdateHeaders(this IDecorator decorator, HttpResponseHeaders responseHeaders) { if (decorator == null || responseHeaders == null) { return; } foreach (var header in responseHeaders) { - decorator.TryAddOrUpdate(header.Key, header.Value != null ? DelimitedString.Create(header.Value) : ""); + decorator.AddOrUpdate(header.Key, header.Value != null ? DelimitedString.Create(header.Value) : ""); } } } diff --git a/src/Cuemon.AspNetCore/Extensions/Http/HttpContextDecoratorExtensions.cs b/src/Cuemon.AspNetCore/Extensions/Http/HttpContextDecoratorExtensions.cs new file mode 100644 index 000000000..2330b8157 --- /dev/null +++ b/src/Cuemon.AspNetCore/Extensions/Http/HttpContextDecoratorExtensions.cs @@ -0,0 +1,108 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Http.Headers; +using Cuemon.AspNetCore.Http.Throttling; +using Cuemon.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.AspNetCore.Http +{ + /// + /// Extension methods for the class tailored to adhere the decorator pattern. + /// This API supports the product infrastructure and is not intended to be used directly from your code. + /// + /// + /// + public static class HttpContextDecoratorExtensions + { + private static readonly SemaphoreSlim ThrottleLocker = new SemaphoreSlim(1); + + /// + /// Common throttler operation logic for ASP.NET Core and ASP.NET Core MVC. Not intended to be used directly from your code. + /// + /// The to extend. + /// The implementation. + /// The configured options. + /// The delegate that merges an instance of into the pipeline. + public static async Task InvokeThrottlerSentinelAsync(this IDecorator decorator, IThrottlingCache tc, ThrottlingSentinelOptions options, Action transformer) + { + var utcNow = DateTime.UtcNow; + var throttlingContext = options.ContextResolver?.Invoke(decorator.Inner); + if (!string.IsNullOrWhiteSpace(throttlingContext)) + { + ThrottleRequest tr = null; + try + { + await ThrottleLocker.WaitAsync().ConfigureAwait(false); + + if (!tc.TryGetValue(throttlingContext, out tr)) + { + tr = new ThrottleRequest(options.Quota); + Decorator.Enclose(tc).TryAdd(throttlingContext, tr); + } + else + { + tr.Refresh(); + tr.IncrementTotal(); + } + + var window = new TimeRange(utcNow, tr.Expires); + var delta = window.Duration; + var reset = utcNow.Add(delta); + Decorator.Enclose(decorator.Inner.Response.Headers).AddOrUpdate(options.RateLimitHeaderName, tr.Quota.RateLimit.ToString(CultureInfo.InvariantCulture)); + Decorator.Enclose(decorator.Inner.Response.Headers).AddOrUpdate(options.RateLimitRemainingHeaderName, Math.Max(tr.Quota.RateLimit - tr.Total, 0).ToString(CultureInfo.InvariantCulture)); + if (options.UseRetryAfterHeader) { options.RateLimitResetScope = options.RetryAfterScope; } // if a response contains both the Retry-After and the RateLimit-Reset header fields, the value of RateLimit-Reset MUST be consistent with the one of Retry-After https://tools.ietf.org/id/draft-polli-ratelimit-headers-00.html#providing-ratelimit-headers + switch (options.RateLimitResetScope) + { + case RetryConditionScope.DeltaSeconds: + Decorator.Enclose(decorator.Inner.Response.Headers).AddOrUpdate(options.RateLimitResetHeaderName, new RetryConditionHeaderValue(delta).ToString()); + break; + case RetryConditionScope.HttpDate: + Decorator.Enclose(decorator.Inner.Response.Headers).AddOrUpdate(options.RateLimitResetHeaderName, new RetryConditionHeaderValue(reset).ToString()); + break; + } + if (tr.Total > tr.Quota.RateLimit && tr.Expires > utcNow) + { + var message = options.ResponseBroker?.Invoke(delta, reset); + if (message != null) + { + transformer?.Invoke(message, decorator.Inner.Response); + throw new ThrottlingException((int)message.StatusCode, await message.Content.ReadAsStringAsync().ConfigureAwait(false), tr.Quota.RateLimit, delta, reset); + } + } + } + finally + { + tc[throttlingContext] = tr; + ThrottleLocker.Release(); + } + } + } + + /// + /// Common user agent logic for ASP.NET Core and ASP.NET Core MVC. Not intended to be used directly from your code. + /// + /// The to extend. + /// The configured options. + /// The delegate that merges an instance of into the pipeline. + public static async Task InvokeUserAgentSentinelAsync(this IDecorator decorator, UserAgentSentinelOptions options, Action transformer) + { + var userAgent = decorator.Inner.Request.Headers[HeaderNames.UserAgent].FirstOrDefault(); + if (options.RequireUserAgentHeader) + { + var message = options.ResponseHandler?.Invoke(userAgent); + if (message != null) + { + transformer?.Invoke(message, decorator.Inner.Response); + throw new UserAgentException((int)message.StatusCode, await message.Content.ReadAsStringAsync().ConfigureAwait(false)); + } + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Extensions/Http/HttpResponseDecoratorExtensions.cs b/src/Cuemon.AspNetCore/Extensions/Http/HttpResponseDecoratorExtensions.cs index 23e270e54..a59ce7d23 100644 --- a/src/Cuemon.AspNetCore/Extensions/Http/HttpResponseDecoratorExtensions.cs +++ b/src/Cuemon.AspNetCore/Extensions/Http/HttpResponseDecoratorExtensions.cs @@ -28,14 +28,14 @@ public static class HttpResponseDecoratorExtensions /// cannot be null -or- /// cannot be null. /// - public static void TryAddOrUpdateEntityTagHeader(this IDecorator decorator, HttpRequest request, ChecksumBuilder builder, bool isWeak = false) + public static void AddOrUpdateEntityTagHeader(this IDecorator decorator, HttpRequest request, ChecksumBuilder builder, bool isWeak = false) { Validator.ThrowIfNull(decorator, nameof(decorator)); Validator.ThrowIfNull(request, nameof(request)); Validator.ThrowIfNull(builder, nameof(builder)); builder = Decorator.Enclose(builder).CombineWith(request.Headers[HeaderNames.Accept]); if (Decorator.Enclose(decorator.Inner.StatusCode).IsSuccessStatusCode() && Decorator.Enclose(request).IsClientSideResourceCached(builder)) { decorator.Inner.StatusCode = StatusCodes.Status304NotModified; } - Decorator.Enclose(decorator.Inner.Headers).TryAddOrUpdate(HeaderNames.ETag, new StringValues(Decorator.Enclose(builder).ToEntityTagHeaderValue(isWeak).ToString())); + Decorator.Enclose(decorator.Inner.Headers).AddOrUpdate(HeaderNames.ETag, new StringValues(Decorator.Enclose(builder).ToEntityTagHeaderValue(isWeak).ToString())); } /// @@ -48,12 +48,12 @@ public static void TryAddOrUpdateEntityTagHeader(this IDecorator d /// cannot be null -or- /// cannot be null. /// - public static void TryAddOrUpdateLastModifiedHeader(this IDecorator decorator, HttpRequest request, DateTime lastModified) + public static void AddOrUpdateLastModifiedHeader(this IDecorator decorator, HttpRequest request, DateTime lastModified) { Validator.ThrowIfNull(decorator, nameof(decorator)); Validator.ThrowIfNull(request, nameof(request)); if (Decorator.Enclose(decorator.Inner.StatusCode).IsSuccessStatusCode() && Decorator.Enclose(request).IsClientSideResourceCached(lastModified)) { decorator.Inner.StatusCode = StatusCodes.Status304NotModified; } - Decorator.Enclose(decorator.Inner.Headers).TryAddOrUpdate(HeaderNames.LastModified, new StringValues(lastModified.ToUniversalTime().ToString("R", DateTimeFormatInfo.InvariantInfo))); + Decorator.Enclose(decorator.Inner.Headers).AddOrUpdate(HeaderNames.LastModified, new StringValues(lastModified.ToUniversalTime().ToString("R", DateTimeFormatInfo.InvariantInfo))); } } } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/GlobalSuppressions.cs b/src/Cuemon.AspNetCore/GlobalSuppressions.cs index 01f8ad206..23a31c393 100644 --- a/src/Cuemon.AspNetCore/GlobalSuppressions.cs +++ b/src/Cuemon.AspNetCore/GlobalSuppressions.cs @@ -3,7 +3,16 @@ // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3442:\"abstract\" classes should not have \"public\" constructors", Justification = "Infrastructure class.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Infrastructure.ConfigurableMiddlewareCore`1.#ctor(Microsoft.AspNetCore.Http.RequestDelegate,System.Action{`0})")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3442:\"abstract\" classes should not have \"public\" constructors", Justification = "Infrastructure class.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Infrastructure.MiddlewareCore.#ctor(Microsoft.AspNetCore.Http.RequestDelegate)")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3442:\"abstract\" classes should not have \"public\" constructors", Justification = "Infrastructure class.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Infrastructure.ConfigurableMiddlewareCore`1.#ctor(Microsoft.AspNetCore.Http.RequestDelegate,Microsoft.Extensions.Options.IOptions{`0})")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Critical Code Smell", "S927:parameter names should match base declaration and other partial definitions", Justification = "Generic parameter; implementation reflects usage.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Hosting.HostingEnvironmentMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext,Microsoft.Extensions.Hosting.IHostEnvironment)~System.Threading.Tasks.Task")] +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S3442:\"abstract\" classes should not have \"public\" constructors", Justification = "Infrastructure class.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Infrastructure.ConfigurableMiddlewareCore`1.#ctor(Microsoft.AspNetCore.Http.RequestDelegate,System.Action{`0})")] +[assembly: SuppressMessage("Major Code Smell", "S3442:\"abstract\" classes should not have \"public\" constructors", Justification = "Infrastructure class.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Infrastructure.MiddlewareCore.#ctor(Microsoft.AspNetCore.Http.RequestDelegate)")] +[assembly: SuppressMessage("Major Code Smell", "S3442:\"abstract\" classes should not have \"public\" constructors", Justification = "Infrastructure class.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Infrastructure.ConfigurableMiddlewareCore`1.#ctor(Microsoft.AspNetCore.Http.RequestDelegate,Microsoft.Extensions.Options.IOptions{`0})")] +[assembly: SuppressMessage("Critical Code Smell", "S927:parameter names should match base declaration and other partial definitions", Justification = "Generic parameter; implementation reflects usage.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Hosting.HostingEnvironmentMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext,Microsoft.Extensions.Hosting.IHostEnvironment)~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up to a max. of 5 generic parameters.", Scope = "type", Target = "~T:Cuemon.AspNetCore.ConfigurableMiddleware`3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up to a max. of 5 generic parameters.", Scope = "type", Target = "~T:Cuemon.AspNetCore.ConfigurableMiddleware`4")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up to a max. of 5 generic parameters.", Scope = "type", Target = "~T:Cuemon.AspNetCore.ConfigurableMiddleware`5")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up to a max. of 5 generic parameters.", Scope = "type", Target = "~T:Cuemon.AspNetCore.ConfigurableMiddleware`6")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up to a max. of 5 generic parameters.", Scope = "type", Target = "~T:Cuemon.AspNetCore.Middleware`3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up to a max. of 5 generic parameters.", Scope = "type", Target = "~T:Cuemon.AspNetCore.Middleware`4")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up to a max. of 5 generic parameters.", Scope = "type", Target = "~T:Cuemon.AspNetCore.Middleware`5")] diff --git a/src/Cuemon.AspNetCore/Http/Headers/CorrelationIdentifierMiddleware.cs b/src/Cuemon.AspNetCore/Http/Headers/CorrelationIdentifierMiddleware.cs index 3262106c3..3d14e71ba 100644 --- a/src/Cuemon.AspNetCore/Http/Headers/CorrelationIdentifierMiddleware.cs +++ b/src/Cuemon.AspNetCore/Http/Headers/CorrelationIdentifierMiddleware.cs @@ -45,7 +45,7 @@ public override Task InvokeAsync(HttpContext context) Decorator.Enclose(context.Items).TryAdd(HttpContextItemsKey, correlationId); context.Response.OnStarting(() => { - Decorator.Enclose(context.Response.Headers).TryAddOrUpdate(Options.HeaderName, correlationId); + Decorator.Enclose(context.Response.Headers).AddOrUpdate(Options.HeaderName, correlationId); return Task.CompletedTask; }); return Next(context); diff --git a/src/Cuemon.AspNetCore/Http/Headers/RequestIdentifierMiddleware.cs b/src/Cuemon.AspNetCore/Http/Headers/RequestIdentifierMiddleware.cs index 439d42517..ced05a16e 100644 --- a/src/Cuemon.AspNetCore/Http/Headers/RequestIdentifierMiddleware.cs +++ b/src/Cuemon.AspNetCore/Http/Headers/RequestIdentifierMiddleware.cs @@ -45,7 +45,7 @@ public override Task InvokeAsync(HttpContext context) Decorator.Enclose(context.Items).TryAdd(HttpContextItemsKey, requestId); context.Response.OnStarting(() => { - Decorator.Enclose(context.Response.Headers).TryAddOrUpdate(Options.HeaderName, requestId); + Decorator.Enclose(context.Response.Headers).AddOrUpdate(Options.HeaderName, requestId); return Task.CompletedTask; }); return Next(context); diff --git a/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingRetryAfterHeader.cs b/src/Cuemon.AspNetCore/Http/Headers/RetryConditionScope.cs similarity index 57% rename from src/Cuemon.AspNetCore/Http/Throttling/ThrottlingRetryAfterHeader.cs rename to src/Cuemon.AspNetCore/Http/Headers/RetryConditionScope.cs index e2894c8dc..e8358e31f 100644 --- a/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingRetryAfterHeader.cs +++ b/src/Cuemon.AspNetCore/Http/Headers/RetryConditionScope.cs @@ -1,9 +1,9 @@ -namespace Cuemon.AspNetCore.Http.Throttling +namespace Cuemon.AspNetCore.Http.Headers { /// - /// Specifies a set of values defining what value to use with a HTTP Retry-After header. + /// Specifies a set of values defining what value to use with a given HTTP header in regards to a retry condition. Recommended value is always as it does not rely on clock synchronization and is resilient to clock skew between client and server. /// - public enum ThrottlingRetryAfterHeader + public enum RetryConditionScope { /// /// A non-negative decimal integer indicating the seconds to delay after the response is received. diff --git a/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelMiddleware.cs b/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelMiddleware.cs index f2af71337..69314ad9b 100644 --- a/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelMiddleware.cs +++ b/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelMiddleware.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Cuemon.AspNetCore.Infrastructure; using Cuemon.IO; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; @@ -37,21 +36,17 @@ public UserAgentSentinelMiddleware(RequestDelegate next, ActionA task that represents the execution of this middleware. public override async Task InvokeAsync(HttpContext context) { - var exception = false; - try + await Decorator.Enclose(context).InvokeUserAgentSentinelAsync(Options, async (message, response) => { - await AspNetCoreInfrastructure.InvokeUserAgentSentinelAsync(context, Options, async (message, response) => + context.Response.OnStarting(() => { - response.StatusCode = (int) message.StatusCode; - Decorator.Enclose(response.Headers).TryAddOrUpdateHeaders(message.Headers); - await Decorator.Enclose(response.Body).WriteAsync(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ConfigureAwait(false); - }).ConfigureAwait(false); - } - catch (UserAgentException) - { - exception = true; - } - if (!exception) { await Next(context).ConfigureAwait(false); } + Decorator.Enclose(response.Headers).AddOrUpdateHeaders(message.Headers); + return Task.CompletedTask; + }); + response.StatusCode = (int)message.StatusCode; + await Decorator.Enclose(response.Body).WriteAsync(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ConfigureAwait(false); + }).ConfigureAwait(false); + await Next(context).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelOptions.cs b/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelOptions.cs index 8d7a6ede9..c8775b9f2 100644 --- a/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelOptions.cs +++ b/src/Cuemon.AspNetCore/Http/Headers/UserAgentSentinelOptions.cs @@ -14,7 +14,7 @@ public class UserAgentSentinelOptions /// /// Initializes a new instance of the class. /// - /// + /// /// The following table shows the initial property values for an instance of . /// /// @@ -42,7 +42,7 @@ public class UserAgentSentinelOptions /// false /// /// - /// + /// /// A initialized to either a HTTP status code 400 or 403 and a body of either or . /// /// @@ -53,11 +53,10 @@ public class UserAgentSentinelOptions /// public UserAgentSentinelOptions() { - UseGenericResponse = false; BadRequestMessage = "The requirements of the HTTP User-Agent header was not met."; ForbiddenMessage = "The HTTP User-Agent specified was rejected."; AllowedUserAgents = new List(); - ResponseBroker = userAgent => + ResponseHandler = userAgent => { var userAgentIsNullOrWhiteSpace = string.IsNullOrWhiteSpace(userAgent); var forbidden = !userAgentIsNullOrWhiteSpace && @@ -65,14 +64,14 @@ public UserAgentSentinelOptions() AllowedUserAgents.Count > 0 && !AllowedUserAgents.Any(allowedUserAgent => userAgent.Equals(allowedUserAgent, StringComparison.OrdinalIgnoreCase)); - if (userAgentIsNullOrWhiteSpace || (forbidden && UseGenericResponse)) + if (userAgentIsNullOrWhiteSpace || forbidden && UseGenericResponse) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(BadRequestMessage) }; } - + if (forbidden) { return new HttpResponseMessage(HttpStatusCode.Forbidden) @@ -89,12 +88,12 @@ public UserAgentSentinelOptions() /// Gets or sets the function delegate that configures the response in the form of a . /// /// The function delegate that configures the response in the form of a . - public Func ResponseBroker { get; set; } + public Func ResponseHandler { get; set; } /// - /// Gets or sets a value indicating whether the produced should be as neutral as possible. + /// Gets or sets a value indicating whether the produced should be as neutral as possible. /// - /// true if the produced should be as neutral as possible; otherwise, false. + /// true if the produced should be as neutral as possible; otherwise, false. public bool UseGenericResponse { get; set; } /// diff --git a/src/Cuemon.AspNetCore/Http/Throttling/IThrottlingCache.cs b/src/Cuemon.AspNetCore/Http/Throttling/IThrottlingCache.cs index 923bca83b..e7403279f 100644 --- a/src/Cuemon.AspNetCore/Http/Throttling/IThrottlingCache.cs +++ b/src/Cuemon.AspNetCore/Http/Throttling/IThrottlingCache.cs @@ -4,7 +4,6 @@ namespace Cuemon.AspNetCore.Http.Throttling { /// /// Specifies the contract for the storage of a throttling cache. - /// Implements the . /// /// public interface IThrottlingCache : IDictionary diff --git a/src/Cuemon.Extensions.AspNetCore/Http/Throttling/MemoryThrottlingCache.cs b/src/Cuemon.AspNetCore/Http/Throttling/MemoryThrottlingCache.cs similarity index 66% rename from src/Cuemon.Extensions.AspNetCore/Http/Throttling/MemoryThrottlingCache.cs rename to src/Cuemon.AspNetCore/Http/Throttling/MemoryThrottlingCache.cs index 2d2bd6036..3701f381b 100644 --- a/src/Cuemon.Extensions.AspNetCore/Http/Throttling/MemoryThrottlingCache.cs +++ b/src/Cuemon.AspNetCore/Http/Throttling/MemoryThrottlingCache.cs @@ -1,12 +1,9 @@ using System.Collections.Concurrent; -using Cuemon.AspNetCore.Http.Throttling; -namespace Cuemon.Extensions.AspNetCore.Http.Throttling +namespace Cuemon.AspNetCore.Http.Throttling { /// /// Provides a simple in-memory representation of the . This class cannot be inherited. - /// Implements the . - /// Implements the . /// /// /// diff --git a/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelMiddleware.cs b/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelMiddleware.cs index 159dce8c9..88faf2d17 100644 --- a/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelMiddleware.cs +++ b/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelMiddleware.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Cuemon.AspNetCore.Http.Headers; -using Cuemon.AspNetCore.Infrastructure; using Cuemon.IO; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; @@ -35,25 +34,17 @@ public ThrottlingSentinelMiddleware(RequestDelegate next, Action. /// /// The context of the current request. - /// The dependency injected of . + /// The dependency injected of . /// A task that represents the execution of this middleware. - public override async Task InvokeAsync(HttpContext context, IThrottlingCache tc) + public override async Task InvokeAsync(HttpContext context, IThrottlingCache di) { - var exception = false; - try + await Decorator.Enclose(context).InvokeThrottlerSentinelAsync(di, Options, async (message, response) => { - await AspNetCoreInfrastructure.InvokeThrottlerSentinelAsync(context, tc, Options, async (message, response) => - { - response.StatusCode = (int) message.StatusCode; - Decorator.Enclose(response.Headers).TryAddOrUpdateHeaders(message.Headers); - await Decorator.Enclose(response.Body).WriteAsync(await message.Content.ReadAsByteArrayAsync()); - }); - } - catch (ThrottlingException) - { - exception = true; - } - if (!exception) { await Next(context).ConfigureAwait(false); } + response.StatusCode = (int)message.StatusCode; + Decorator.Enclose(response.Headers).AddOrUpdateHeaders(message.Headers); + await Decorator.Enclose(response.Body).WriteAsync(await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false)).ConfigureAwait(false); + }).ConfigureAwait(false); + await Next(context).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelOptions.cs b/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelOptions.cs index f132c457b..f3a17eec8 100644 --- a/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelOptions.cs +++ b/src/Cuemon.AspNetCore/Http/Throttling/ThrottlingSentinelOptions.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using Cuemon.AspNetCore.Http.Headers; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; @@ -35,12 +36,16 @@ public class ThrottlingSentinelOptions /// X-RateLimit-Reset /// /// + /// + /// + /// + /// /// /// true /// /// - /// - /// ThrottlingRetryAfterHeader.DeltaSeconds + /// + /// /// /// /// @@ -62,23 +67,24 @@ public class ThrottlingSentinelOptions /// public ThrottlingSentinelOptions() { - RateLimitHeaderName = "X-RateLimit-Limit"; - RateLimitRemainingHeaderName = "X-RateLimit-Remaining"; - RateLimitResetHeaderName = "X-RateLimit-Reset"; + RateLimitHeaderName = "RateLimit-Limit"; + RateLimitRemainingHeaderName = "RateLimit-Remaining"; + RateLimitResetHeaderName = "RateLimit-Reset"; + RateLimitResetScope = RetryConditionScope.DeltaSeconds; UseRetryAfterHeader = true; - RetryAfterHeader = ThrottlingRetryAfterHeader.DeltaSeconds; + RetryAfterScope = RetryConditionScope.DeltaSeconds; TooManyRequestsMessage = "Throttling rate limit quota violation. Quota limit exceeded."; ResponseBroker = (delta, reset) => { var message = new HttpResponseMessage((HttpStatusCode) StatusCodes.Status429TooManyRequests); if (UseRetryAfterHeader) { - switch (RetryAfterHeader) + switch (RetryAfterScope) { - case ThrottlingRetryAfterHeader.DeltaSeconds: + case RetryConditionScope.DeltaSeconds: message.Headers.Add(HeaderNames.RetryAfter, new RetryConditionHeaderValue(delta).ToString()); break; - case ThrottlingRetryAfterHeader.HttpDate: + case RetryConditionScope.HttpDate: message.Headers.Add(HeaderNames.RetryAfter, new RetryConditionHeaderValue(reset).ToString()); break; } @@ -130,6 +136,12 @@ public ThrottlingSentinelOptions() /// The name of the rate limit reset HTTP header. public string RateLimitResetHeaderName { get; set; } + /// + /// Gets or sets the preferred rate limit reset HTTP header value that conforms with RFC 7231. + /// + /// The preferred rate limit reset HTTP header value that conforms with RFC 7231. + public RetryConditionScope RateLimitResetScope { get; set; } + /// /// Gets or sets a value indicating whether to include a Retry-After HTTP header specifying how long to wait before making a new request. /// @@ -137,9 +149,9 @@ public ThrottlingSentinelOptions() public bool UseRetryAfterHeader { get; set; } /// - /// Gets or sets the preferred Retry-After HTTP header value that conforms with RFC 2616. + /// Gets or sets the preferred Retry-After HTTP header value that conforms with RFC 7231. /// - /// The preferred Retry-After HTTP header value that conforms with RFC 2616. - public ThrottlingRetryAfterHeader RetryAfterHeader { get; set; } + /// The preferred Retry-After HTTP header value that conforms with RFC 7231. + public RetryConditionScope RetryAfterScope { get; set; } } } \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Infrastructure/AspNetCoreInfrastructure.cs b/src/Cuemon.AspNetCore/Infrastructure/AspNetCoreInfrastructure.cs deleted file mode 100644 index b309bb34a..000000000 --- a/src/Cuemon.AspNetCore/Infrastructure/AspNetCoreInfrastructure.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Cuemon.AspNetCore.Http.Headers; -using Cuemon.AspNetCore.Http.Throttling; -using Cuemon.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Microsoft.Net.Http.Headers; - -namespace Cuemon.AspNetCore.Infrastructure -{ - internal static class AspNetCoreInfrastructure - { - private static readonly SemaphoreSlim ThrottleLocker = new SemaphoreSlim(1); - - public static async Task InvokeUserAgentSentinelAsync(HttpContext context, UserAgentSentinelOptions options, Action transformer) - { - var userAgent = context.Request.Headers[HeaderNames.UserAgent].FirstOrDefault(); - if (options.RequireUserAgentHeader) - { - var message = options.ResponseBroker?.Invoke(userAgent); - if (message != null) - { - context.Response.OnStarting(() => - { - transformer?.Invoke(message, context.Response); - return Task.CompletedTask; - }); - throw new UserAgentException((int)message.StatusCode, await message.Content.ReadAsStringAsync().ConfigureAwait(false)); - } - } - } - - public static async Task InvokeThrottlerSentinelAsync(HttpContext context, IThrottlingCache tc, ThrottlingSentinelOptions options, Action transformer) - { - var utcNow = DateTime.UtcNow; - var throttlingContext = options.ContextResolver?.Invoke(context); - if (!string.IsNullOrWhiteSpace(throttlingContext)) - { - try - { - await ThrottleLocker.WaitAsync().ConfigureAwait(false); - - if (!tc.TryGetValue(throttlingContext, out var tr)) - { - tr = new ThrottleRequest(options.Quota); - Decorator.Enclose(tc).TryAdd(throttlingContext, tr); - } - else - { - tr.Refresh(); - tr.IncrementTotal(); - } - - var window = new TimeRange(utcNow, tr.Expires); - var delta = window.Duration; - var reset = utcNow.Add(delta); - Decorator.Enclose(context.Response.Headers).TryAddOrUpdate(options.RateLimitHeaderName, tr.Quota.RateLimit.ToString(CultureInfo.InvariantCulture)); - Decorator.Enclose(context.Response.Headers).TryAddOrUpdate(options.RateLimitRemainingHeaderName, Math.Max(tr.Quota.RateLimit - tr.Total, 0).ToString(CultureInfo.InvariantCulture)); - Decorator.Enclose(context.Response.Headers).TryAddOrUpdate(options.RateLimitResetHeaderName, Decorator.Enclose(reset).ToUnixEpochTime().ToString(CultureInfo.InvariantCulture)); - if (tr.Total > tr.Quota.RateLimit && tr.Expires > utcNow) - { - var message = options.ResponseBroker?.Invoke(delta, reset); - if (message != null) - { - context.Response.OnStarting(() => - { - transformer?.Invoke(message, context.Response); - return Task.CompletedTask; - }); - throw new ThrottlingException((int)message.StatusCode, await message.Content.ReadAsStringAsync().ConfigureAwait(false), tr.Quota.RateLimit, delta, reset); - } - } - - tc[throttlingContext] = tr; - } - finally - { - ThrottleLocker.Release(); - } - } - } - } -} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Properties/AssemblyInfo.cs b/src/Cuemon.AspNetCore/Properties/AssemblyInfo.cs index af1492a49..661fc9125 100644 --- a/src/Cuemon.AspNetCore/Properties/AssemblyInfo.cs +++ b/src/Cuemon.AspNetCore/Properties/AssemblyInfo.cs @@ -1,6 +1,4 @@ -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; [assembly: ComVisible(false)] -[assembly: Guid("31f1ce52-3e05-495f-8359-dd708905d7d6")] -[assembly: InternalsVisibleTo("Cuemon.AspNetCore.Mvc, PublicKey=00240000048000009400000006020000002400005253413100040000010001002F66D8473F676F4E7B47400527D33951A774422DFFC3DF6D7F87C82E5694E9F3AA626D36BEBEA428AD5B800EFCF6CE87B73268F5A0125A7D38739D344703A1C48785AC1A45B1C27EDFDF2EB30BA2B3E3CEA92E5981C30F3A95685A680B7EBEE66F422D176CD1623019D5A05770B9BA498144B1134593BEA6F674F334CF2B90B0")] \ No newline at end of file +[assembly: Guid("31f1ce52-3e05-495f-8359-dd708905d7d6")] \ No newline at end of file diff --git a/src/Cuemon.AspNetCore/Properties/PackageReleaseNotes.txt b/src/Cuemon.AspNetCore/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..e74706340 --- /dev/null +++ b/src/Cuemon.AspNetCore/Properties/PackageReleaseNotes.txt @@ -0,0 +1,23 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Upgrade Steps +- Any former extension methods of the Cuemon.AspNetCore namespace was merged into the Cuemon.Extensions.AspNetCore namespace +  +# Breaking Changes +- MOVED HttpExceptionDescriptor class from the Cuemon.AspNetCore.Http namespace to Cuemon.AspNetCore.Diagnostics namespace +- REMOVED ThrottlingRetryAfterHeader enum from the Cuemon.AspNetCore.Http.Throttling namespace +- CHANGED ThrottlingSentinelOptions class in the Cuemon.AspNetCore.Http.Throttling namespace to be more compliant with industry standards +- RENAMED ApplicationBuilderFactory class in the Cuemon.AspNetCore.Builder namespace to MiddlewareBuilderFactory +  +# New Features +- ADDED IServerTiming interface in the Cuemon.AspNetCore.Diagnostics namespace that represents the Server Timing as per W3C Working Draft 28 July 2020 (https://www.w3.org/TR/2020/WD-server-timing-20200728/) +- ADDED ServerTiming class in the Cuemon.AspNetCore.Diagnostics namespace that provides a default implementation of the IServerTiming interface +- ADDED ServerTimingMetric class in the Cuemon.AspNetCore.Diagnostics namespace that represents a HTTP Server-Timing header field entry to communicate one metric and description for the given request-response cycle +- ADDED RetryConditionScope enum in the Cuemon.AspNetCore.Http.Headers namespace that specifies a set of values defining what value to use with a given HTTP header in regards to a retry condition +- ADDED CacheBusting class in the Cuemon.AspNetCore.Configuration namespace that represents a way to provide cache-busting capabilities +- ADDED CacheBustingOptions class in the Cuemon.AspNetCore.Configuration namespace that specifies options related to CacheBusting +- ADDED DynamicCacheBusting class in the Cuemon.AspNetCore.Configuration namespace that provides cache-busting capabilities on a duration based interval +- ADDED DynamicCacheBustingOptions class in the Cuemon.AspNetCore.Configuration namespace that specifies options related to DynamicCacheBusting +- ADDED ICacheBusting interface in the Cuemon.AspNetCore.Configuration namespace that is an interface to provide cache-busting capabilities +  \ No newline at end of file diff --git a/src/Cuemon.Core/ActionFactory.cs b/src/Cuemon.Core/ActionFactory.cs index 3bdc44bfa..c194a33dd 100644 --- a/src/Cuemon.Core/ActionFactory.cs +++ b/src/Cuemon.Core/ActionFactory.cs @@ -509,7 +509,7 @@ public void ExecuteMethod() /// /// A new that is a copy of this instance. /// When thread safety is required this is the method to invoke. - public ActionFactory Clone() + public override TemplateFactory Clone() { return new ActionFactory(Method, GenericArguments.Clone() as TTuple); } diff --git a/src/Cuemon.Core/Collections/Generic/PagedSettings.cs b/src/Cuemon.Core/Collections/Generic/PagedSettings.cs index 30aad9b53..1d43cb114 100644 --- a/src/Cuemon.Core/Collections/Generic/PagedSettings.cs +++ b/src/Cuemon.Core/Collections/Generic/PagedSettings.cs @@ -1,21 +1,17 @@ -namespace Cuemon.Collections.Generic +using System; + +namespace Cuemon.Collections.Generic { /// - /// Specifies a set of features to support on the object. This class cannot be inherited. + /// Specifies a set of features to support on the object. /// - public sealed class PagedSettings + public class PagedSettings : IEquatable { - private static int DefaultPageSizeValue = 25; - /// /// Gets or sets the default page size of the class. Default is 25. /// /// The default page size of the class. - public static int DefaultPageSize - { - get { return DefaultPageSizeValue; } - set { DefaultPageSizeValue = value; } - } + public static int DefaultPageSize { get; set; } = 25; #region Constructors /// @@ -41,6 +37,31 @@ public override int GetHashCode() return Generate.HashCode32(PageSize, PageNumber, (int)SortOrderDirection, Data.GetHashCode()) ^ Generate.HashCode32(string.Concat(SearchCriteria, SortOrderBy)); } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// true if the current object is equal to the other parameter; otherwise, false. + public virtual bool Equals(PagedSettings other) + { + if (ReferenceEquals(null, other)) { return false; } + if (ReferenceEquals(this, other)) { return true; } + return PageSize == other.PageSize && PageNumber.Equals(other.PageNumber) && SortOrderDirection.Equals(other.SortOrderDirection) && Data.GetHashCode().Equals(other.Data.GetHashCode()) && SearchCriteria.Equals(other.SearchCriteria) && SortOrderBy.Equals(other.SortOrderBy); + } + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The object to compare with the current object. + /// true if the specified is equal to this instance; otherwise, false. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { return false; } + if (ReferenceEquals(this, obj)) { return true; } + if (obj.GetType() != this.GetType()) { return false; } + return Equals((PagedSettings) obj); + } + #endregion #region Properties diff --git a/src/Cuemon.Core/Cuemon.Core.csproj b/src/Cuemon.Core/Cuemon.Core.csproj index 6947dc04a..8990527b2 100644 --- a/src/Cuemon.Core/Cuemon.Core.csproj +++ b/src/Cuemon.Core/Cuemon.Core.csproj @@ -1,37 +1,16 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 000bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon Cuemon.Core Cuemon - The Cuemon.Core assembly is the patriarch of the Cuemon family and provides fundamental-, utility- and base-classes that define commonly-used value and reference data types, events and event handlers, interfaces, attribute, and feature rich delegates to greatly support functional programming. A great addition to the well established System namespace to make your daily operations easier to work with. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon namespace contains fundamental factories, classes and base classes that define invaluable reference and value types that greatly extends the System namespace. Abundant support for delegates and functional programming. + action-factory bit-unit byte-unit calculator configure configure-revert configure-exchange configurable condition options-pattern data-reader decorator delimited-string disposable finalize-disposable safe-invoke safe-invoke-async func-factory patterns reference-project clean-architecture clean-code task-action-factory task-func-factory template template-factory time-range time-unit validator guard text-encoding parser-factory security aes-cryptor cyclic-redundancy-check fowler-noll-vo-hash hash-factory hash-result hmac-message-digest hmac-secure-hash-algorithm keyed-crypto-hash keyed-crypto-algorithm message-digest non-crypto-algorithm secure-hash-algorithm unkeyed-crypto-hash - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT - - - - - - \ No newline at end of file diff --git a/src/Cuemon.Core/Data/Integrity/ChecksumBuilder.cs b/src/Cuemon.Core/Data/Integrity/ChecksumBuilder.cs deleted file mode 100644 index 0de4f9fc3..000000000 --- a/src/Cuemon.Core/Data/Integrity/ChecksumBuilder.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using Cuemon.Integrity; -using Cuemon.Security.Cryptography; - -namespace Cuemon.Data.Integrity -{ - /// - /// Provides a way to fluently represent checksum values of arbitrary data. - /// - public class ChecksumBuilder : IDataIntegrity, IEquatable - { - /// - /// Initializes a new instance of the class. - /// - public ChecksumBuilder() : this((byte[])null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(double checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(short checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(string checksum, Action setup = null) : this(Generate.HashCode64(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(int checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(long checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(float checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(ushort checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(uint checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public ChecksumBuilder(ulong checksum, Action setup = null) : this(Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// An array of bytes containing a checksum of the data this instance represents. - /// The which may be configured. - public ChecksumBuilder(byte[] checksum, Action setup = null) - { - var options = Patterns.Configure(setup); - Algorithm = options.Algorithm; - Bytes = checksum == null ? new List() : new List(checksum); - } - - /// - /// Gets the hash algorithm to use for the checksum computation. - /// - /// The hash algorithm to use for the checksum computation. - public CryptoAlgorithm Algorithm { get; protected set; } - - /// - /// Gets a byte array that is the result of the associated . - /// - /// The byte array that is the result of the associated . - protected List Bytes { get; set; } - - /// - /// Gets a containing a computed hash value of the data this instance represents. - /// - /// A containing a computed hash value of the data this instance represents. - public HashResult Checksum => ComputedHash ?? (ComputedHash = HashFactory.CreateCrypto(Algorithm).ComputeHash(Bytes.ToArray())); - - /// - /// Gets or sets the computed checksum of . - /// - /// The computed checksum of . - protected HashResult ComputedHash { get; set; } - - /// - /// Provides a way to append additional checksum to the representation of this instance. - /// - /// A containing a checksum of the additional data this instance must represent. - public void AppendChecksum(byte[] checksum) - { - ComputedHash = null; - Bytes.AddRange(checksum); - } - - /// - /// Returns a hash code for this instance. - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - public override int GetHashCode() - { - return Generate.HashCode32(Checksum.ToHexadecimalString()); - } - - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with the current . - /// true if the specified is equal to this instance; otherwise, false. - public override bool Equals(object obj) - { - if (!(obj is ChecksumBuilder builder)) { return false; } - return Equals(builder); - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// true if the current object is equal to the other parameter; otherwise, false. - public virtual bool Equals(ChecksumBuilder other) - { - if (other == null) { return false; } - return Checksum.GetHashCode() == other.Checksum.GetHashCode(); - } - - /// - /// Converts the the of this instance to its equivalent hexadecimal representation. - /// - /// A hexadecimal representation of this instance. - public override string ToString() - { - return Checksum.ToHexadecimalString(); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/Data/Integrity/ChecksumBuilderOptions.cs b/src/Cuemon.Core/Data/Integrity/ChecksumBuilderOptions.cs deleted file mode 100644 index eb787a2de..000000000 --- a/src/Cuemon.Core/Data/Integrity/ChecksumBuilderOptions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Cuemon.Data.Integrity; -using Cuemon.Security.Cryptography; - -namespace Cuemon.Integrity -{ - /// - /// Configuration options for . - /// - public class ChecksumBuilderOptions - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The following table shows the initial property values for an instance of . - /// - /// - /// Property - /// Initial Value - /// - /// - /// - /// - /// - /// - /// - public ChecksumBuilderOptions() - { - Algorithm = CryptoAlgorithm.Md5; - } - - /// - /// Gets or sets the hash algorithm to use for the checksum computation. - /// - /// The hash algorithm to use for the checksum computation. - public CryptoAlgorithm Algorithm { get; set; } - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/DelimitedString.cs b/src/Cuemon.Core/DelimitedString.cs index fc3875444..658854431 100644 --- a/src/Cuemon.Core/DelimitedString.cs +++ b/src/Cuemon.Core/DelimitedString.cs @@ -2,14 +2,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace Cuemon { /// - /// Provides a set of static methods to break a delimited string into substrings. + /// Provides a set of static methods to convert a sequence into a delimited string and break a delimited string into substrings. /// public static class DelimitedString { @@ -46,7 +45,7 @@ public static string Create(IEnumerable source, ActionThe which may be configured. /// A that contain the substrings of delimited by a and optionally surrounded within . /// - /// This method was inspired by two articles on StackOverflow @ http://stackoverflow.com/questions/2807536/split-string-in-c-sharp and https://stackoverflow.com/questions/3776458/split-a-comma-separated-string-with-both-quoted-and-unquoted-strings. + /// This method was inspired by two articles on StackOverflow @ http://stackoverflow.com/questions/2807536/split-string-in-c-sharp, https://stackoverflow.com/questions/3776458/split-a-comma-separated-string-with-both-quoted-and-unquoted-strings and https://stackoverflow.com/questions/6542996/how-to-split-csv-whose-columns-may-contain. /// The default implementation conforms with the RFC-4180 standard. /// /// @@ -62,13 +61,13 @@ public static string[] Split(string value, Action setup var key = string.Concat(delimiter, "<-dq->", qualifier); if (!CompiledSplitExpressions.TryGetValue(key, out var compiledSplit)) { - compiledSplit = new Regex(string.Format(CultureInfo.InvariantCulture, "(?:^|{0})({1}(?:[^{1}]+|{1}{1})*{1}|[^{0}]*)", Regex.Escape(delimiter), Regex.Escape(qualifier)), RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeSpan.FromSeconds(2)); + compiledSplit = new Regex(string.Format(CultureInfo.InvariantCulture, "{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))", delimiter, qualifier), RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeSpan.FromSeconds(2)); CompiledSplitExpressions.TryAdd(key, compiledSplit); } try { - return compiledSplit.Matches(value).Cast().Where(m => m.Length > 0).Select(m => m.Value.TrimStart(delimiter.ToCharArray())).ToArray(); + return compiledSplit.Split(value); } catch (RegexMatchTimeoutException) { diff --git a/src/Cuemon.Core/Disposable.cs b/src/Cuemon.Core/Disposable.cs index aa9ae86dd..49c800546 100644 --- a/src/Cuemon.Core/Disposable.cs +++ b/src/Cuemon.Core/Disposable.cs @@ -1,6 +1,4 @@ using System; -using System.Threading; -using System.Threading.Tasks; namespace Cuemon { @@ -11,340 +9,6 @@ namespace Cuemon /// public abstract class Disposable : IDisposable { - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The delegate that will handle any exceptions might thrown by . - /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static TResult SafeInvoke(Func initializer, Func tester, Action catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = FuncFactory.Create(tester, default); - var f2 = ActionFactory.Create(catcher, default); - return SafeInvokeCore(f1, initializer, f2); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The parameter of the function delegate and delegate . - /// The delegate that will handle any exceptions might thrown by . - /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static TResult SafeInvoke(Func initializer, Func tester, T arg, Action catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = FuncFactory.Create(tester, default, arg); - var f2 = ActionFactory.Create(catcher, default, arg); - return SafeInvokeCore(f1, initializer, f2); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The delegate that will handle any exceptions might thrown by . - /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, Action catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = FuncFactory.Create(tester, default, arg1, arg2); - var f2 = ActionFactory.Create(catcher, default, arg1, arg2); - return SafeInvokeCore(f1, initializer, f2); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the third parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The third parameter of the function delegate and delegate . - /// The delegate that will handle any exceptions might thrown by . - /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, T3 arg3, Action catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = FuncFactory.Create(tester, default, arg1, arg2, arg3); - var f2 = ActionFactory.Create(catcher, default, arg1, arg2, arg3); - return SafeInvokeCore(f1, initializer, f2); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the third parameter of the function delegate and delegate . - /// The type of the fourth parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The third parameter of the function delegate and delegate . - /// The fourth parameter of the function delegate and delegate . - /// The delegate that will handle any exceptions might thrown by . - /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = FuncFactory.Create(tester, default, arg1, arg2, arg3, arg4); - var f2 = ActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4); - return SafeInvokeCore(f1, initializer, f2); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the third parameter of the function delegate and delegate . - /// The type of the fourth parameter of the function delegate and delegate . - /// The type of the fifth parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The third parameter of the function delegate and delegate . - /// The fourth parameter of the function delegate and delegate . - /// The fifth parameter of the function delegate and delegate . - /// The delegate that will handle any exceptions might thrown by . - /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = FuncFactory.Create(tester, default, arg1, arg2, arg3, arg4, arg5); - var f2 = ActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4, arg5); - return SafeInvokeCore(f1, initializer, f2); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The token to monitor for cancellation requests. The default value is . - /// The function delegate that will handle any exceptions might thrown by . - /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static Task SafeInvokeAsync(Func initializer, Func> tester, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = TaskFuncFactory.Create(tester, default); - var f2 = TaskActionFactory.Create(catcher, default); - return SafeInvokeAsyncCore(f1, initializer, f2, ct); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The parameter of the function delegate and delegate . - /// The token to monitor for cancellation requests. The default value is . - /// The function delegate that will handle any exceptions might thrown by . - /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static Task SafeInvokeAsync(Func initializer, Func> tester, T arg, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = TaskFuncFactory.Create(tester, default, arg); - var f2 = TaskActionFactory.Create(catcher, default, arg); - return SafeInvokeAsyncCore(f1, initializer, f2, ct); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The token to monitor for cancellation requests. The default value is . - /// The function delegate that will handle any exceptions might thrown by . - /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2); - var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2); - return SafeInvokeAsyncCore(f1, initializer, f2, ct); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the third parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The third parameter of the function delegate and delegate . - /// The token to monitor for cancellation requests. The default value is . - /// The function delegate that will handle any exceptions might thrown by . - /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, T3 arg3, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2, arg3); - var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2, arg3); - return SafeInvokeAsyncCore(f1, initializer, f2, ct); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the third parameter of the function delegate and delegate . - /// The type of the fourth parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The third parameter of the function delegate and delegate . - /// The fourth parameter of the function delegate and delegate . - /// The token to monitor for cancellation requests. The default value is . - /// The function delegate that will handle any exceptions might thrown by . - /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2, arg3, arg4); - var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4); - return SafeInvokeAsyncCore(f1, initializer, f2, ct); - } - - /// - /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). - /// - /// The type of the first parameter of the function delegate and delegate . - /// The type of the second parameter of the function delegate and delegate . - /// The type of the third parameter of the function delegate and delegate . - /// The type of the fourth parameter of the function delegate and delegate . - /// The type of the fifth parameter of the function delegate and delegate . - /// The type of the return value of the function delegate . - /// The function delegate that initializes an object implementing the interface. - /// The function delegate that is used to ensure that operations performed on abides CA2000. - /// The first parameter of the function delegate and delegate . - /// The second parameter of the function delegate and delegate . - /// The third parameter of the function delegate and delegate . - /// The fourth parameter of the function delegate and delegate . - /// The fifth parameter of the function delegate and delegate . - /// The token to monitor for cancellation requests. The default value is . - /// The function delegate that will handle any exceptions might thrown by . - /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. - public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable - { - Validator.ThrowIfNull(initializer, nameof(initializer)); - Validator.ThrowIfNull(tester, nameof(tester)); - var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2, arg3, arg4, arg5); - var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4, arg5); - return SafeInvokeAsyncCore(f1, initializer, f2, ct); - } - - private static TResult SafeInvokeCore(FuncFactory testerFactory, Func initializer, ActionFactory catcherFactory) - where TResult : class, IDisposable - where TTester : Template - where TCatcher : Template - { - TResult result = null; - try - { - testerFactory.GenericArguments.Arg1 = initializer(); - testerFactory.GenericArguments.Arg1 = testerFactory.ExecuteMethod(); - result = testerFactory.GenericArguments.Arg1; - testerFactory.GenericArguments.Arg1 = null; - } - catch (Exception e) - { - if (!catcherFactory.HasDelegate) - { - throw; - } - else - { - catcherFactory.GenericArguments.Arg1 = e; - catcherFactory.ExecuteMethod(); - } - } - finally - { - testerFactory.GenericArguments.Arg1?.Dispose(); - } - return result; - } - - private static async Task SafeInvokeAsyncCore(TaskFuncFactory testerFactory, Func initializer, TaskActionFactory catcherFactory, CancellationToken ct) - where TResult : class, IDisposable - where TTester : Template - where TCatcher : Template - { - TResult result = null; - try - { - testerFactory.GenericArguments.Arg1 = initializer(); - testerFactory.GenericArguments.Arg1 = await testerFactory.ExecuteMethodAsync(ct).ConfigureAwait(false); - result = testerFactory.GenericArguments.Arg1; - testerFactory.GenericArguments.Arg1 = null; - } - catch (Exception e) - { - if (!catcherFactory.HasDelegate) - { - throw; - } - else - { - catcherFactory.GenericArguments.Arg1 = e; - await catcherFactory.ExecuteMethodAsync(ct).ConfigureAwait(false); - } - } - finally - { - testerFactory.GenericArguments.Arg1?.Dispose(); - } - return result; - } - /// /// Gets a value indicating whether this object is disposed. /// diff --git a/src/Cuemon.Core/DisposableOptions.cs b/src/Cuemon.Core/DisposableOptions.cs index f8020e315..4dfba8c99 100644 --- a/src/Cuemon.Core/DisposableOptions.cs +++ b/src/Cuemon.Core/DisposableOptions.cs @@ -1,12 +1,11 @@ using System; -using Cuemon.Threading; namespace Cuemon { /// /// Configuration options for . /// - public class DisposableOptions : AsyncOptions + public class DisposableOptions { /// /// Initializes a new instance of the class. diff --git a/src/Cuemon.Core/ExceptionInsights.cs b/src/Cuemon.Core/ExceptionInsights.cs index 759f4bf53..32f0e6b3e 100644 --- a/src/Cuemon.Core/ExceptionInsights.cs +++ b/src/Cuemon.Core/ExceptionInsights.cs @@ -56,7 +56,7 @@ public static T Embed(T exception, MethodBase thrower, object[] runtimeParame { var rp = DelimitedString.Create(MethodDescriptor.MergeParameters(descriptor, runtimeParameters), o => { - o.StringConverter = pair => FormattableString.Invariant($"{pair.Key}={pair.Value}"); + o.StringConverter = pair => FormattableString.Invariant($"{pair.Key}={pair.Value ?? "null"}"); o.Delimiter = FormattableString.Invariant($"{Alphanumeric.NewLine}"); }); builder.Append(Convert.ToBase64String(Convertible.GetBytes(FormattableString.Invariant($"{rp}")))); @@ -72,6 +72,13 @@ public static T Embed(T exception, MethodBase thrower, object[] runtimeParame builder.Append("."); builder.Append(empty); } + EmbedSystemSnapshot(builder, snapshot, empty); + if (exception.Data[Key] == null) { exception.Data.Add(Key, builder.ToString()); } + return exception; + } + + private static void EmbedSystemSnapshot(StringBuilder builder, SystemSnapshot snapshot, string empty) + { builder.Append("."); if (snapshot.HasFlag(SystemSnapshot.CaptureThreadInfo)) { @@ -102,8 +109,6 @@ public static T Embed(T exception, MethodBase thrower, object[] runtimeParame { builder.Append(empty); } - if (exception.Data[Key] == null) { exception.Data.Add(Key, builder.ToString()); } - return exception; } } } \ No newline at end of file diff --git a/src/Cuemon.Core/Extensions/ByteArrayDecoratorExtensions.cs b/src/Cuemon.Core/Extensions/ByteArrayDecoratorExtensions.cs index af38a2f93..894b279b5 100644 --- a/src/Cuemon.Core/Extensions/ByteArrayDecoratorExtensions.cs +++ b/src/Cuemon.Core/Extensions/ByteArrayDecoratorExtensions.cs @@ -37,7 +37,7 @@ public static string ToEncodedString(this IDecorator decorator, Action decorator) { Validator.ThrowIfNull(decorator, nameof(decorator)); - return Disposable.SafeInvoke(() => new MemoryStream(decorator.Inner.Length), ms => + return Patterns.SafeInvoke(() => new MemoryStream(decorator.Inner.Length), ms => { ms.Write(decorator.Inner, 0, decorator.Inner.Length); ms.Position = 0; @@ -57,7 +57,7 @@ public static Stream ToStream(this IDecorator decorator) public static Task ToStreamAsync(this IDecorator decorator, CancellationToken ct = default) { Validator.ThrowIfNull(decorator, nameof(decorator)); - return Disposable.SafeInvokeAsync(() => new MemoryStream(decorator.Inner.Length), async (ms, cti) => + return Patterns.SafeInvokeAsync(() => new MemoryStream(decorator.Inner.Length), async (ms, cti) => { await ms.WriteAsync(decorator.Inner, 0, decorator.Inner.Length, cti).ConfigureAwait(false); ms.Position = 0; diff --git a/src/Cuemon.Core/Extensions/Collections/Generic/DictionaryDecoratorExtensions.cs b/src/Cuemon.Core/Extensions/Collections/Generic/DictionaryDecoratorExtensions.cs index 3c29af769..6ed5b2eb6 100644 --- a/src/Cuemon.Core/Extensions/Collections/Generic/DictionaryDecoratorExtensions.cs +++ b/src/Cuemon.Core/Extensions/Collections/Generic/DictionaryDecoratorExtensions.cs @@ -135,11 +135,11 @@ public static bool TryAdd(this IDecorator cannot be null -or- /// cannot be null. /// - public static bool TryAddOrUpdate(this IDecorator> decorator, TKey key, TValue value) + public static void AddOrUpdate(this IDecorator> decorator, TKey key, TValue value) { Validator.ThrowIfNull(decorator, nameof(decorator)); Validator.ThrowIfNull(key, nameof(key)); - return decorator.Inner.ContainsKey(key) ? Patterns.TryInvoke(() => { decorator.Inner[key] = value; }) : TryAdd(decorator, key, value); + Condition.FlipFlop(decorator.Inner.ContainsKey(key), () => Patterns.TryInvoke(() => { decorator.Inner[key] = value; }), () => TryAdd(decorator, key, value)); } } } \ No newline at end of file diff --git a/src/Cuemon.Core/Extensions/IO/StreamDecoratorExtensions.cs b/src/Cuemon.Core/Extensions/IO/StreamDecoratorExtensions.cs index 5586a15c4..1e23c432f 100644 --- a/src/Cuemon.Core/Extensions/IO/StreamDecoratorExtensions.cs +++ b/src/Cuemon.Core/Extensions/IO/StreamDecoratorExtensions.cs @@ -1,162 +1,16 @@ using System; -using System.ComponentModel; using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Cuemon.Text; namespace Cuemon.IO { /// /// Extension methods for the class tailored to adhere the decorator pattern. + /// This API supports the product infrastructure and is not intended to be used directly from your code. /// /// /// public static class StreamDecoratorExtensions { - /// - /// Converts the enclosed of the specified to a . - /// - /// The to extend. - /// The which may be configured. - /// A containing the result of the enclosed of the specified . - /// will be initialized with and . - /// - /// cannot be null. - /// - /// - /// was initialized with an invalid . - /// - public static string ToEncodedString(this IDecorator decorator, Action setup = null) - { - Validator.ThrowIfNull(decorator, nameof(decorator)); - var options = Patterns.Configure(setup); - if (options.Encoding.Equals(EncodingOptions.DefaultEncoding)) { options.Encoding = ByteOrderMark.DetectEncodingOrDefault(decorator.Inner, options.Encoding); } - if (options.Preamble < PreambleSequence.Keep || options.Preamble > PreambleSequence.Remove) { throw new InvalidEnumArgumentException(nameof(setup), (int)options.Preamble, typeof(PreambleSequence)); } - - var bytes = Decorator.Enclose(decorator.Inner).ToByteArray(o => - { - o.BufferSize = options.BufferSize; - o.LeaveOpen = options.LeaveOpen; - }); - return Convertible.ToString(bytes, o => - { - o.Encoding = options.Encoding; - o.Preamble = options.Preamble; - }); - } - - /// - /// Converts the enclosed of the specified to a . - /// - /// The to extend. - /// The which may be configured. - /// A task that represents the asynchronous operation. The task result contains a containing the result of the enclosed of the specified . - /// will be initialized with and . - /// - /// cannot be null. - /// - /// - /// was initialized with an invalid . - /// - public static Task ToEncodedStringAsync(this IDecorator decorator, Action setup = null) - { - Validator.ThrowIfNull(decorator, nameof(decorator)); - var options = Patterns.Configure(setup); - if (options.Encoding.Equals(EncodingOptions.DefaultEncoding)) { options.Encoding = ByteOrderMark.DetectEncodingOrDefault(decorator.Inner, options.Encoding); } - if (options.Preamble < PreambleSequence.Keep || options.Preamble > PreambleSequence.Remove) { throw new InvalidEnumArgumentException(nameof(setup), (int)options.Preamble, typeof(PreambleSequence)); } - return ToEncodedStringAsyncCore(decorator, options); - } - - private static async Task ToEncodedStringAsyncCore(this IDecorator decorator, StreamReaderOptions options) - { - var bytes = await Decorator.Enclose(decorator.Inner).ToByteArrayAsync(o => - { - o.BufferSize = options.BufferSize; - o.LeaveOpen = options.LeaveOpen; - o.CancellationToken = options.CancellationToken; - }).ConfigureAwait(false); - return Convertible.ToString(bytes, o => - { - o.Encoding = options.Encoding; - o.Preamble = options.Preamble; - }); - } - - /// - /// Converts the enclosed of the specified to its equivalent representation. - /// - /// The to extend. - /// The which may be configured. - /// A that is equivalent to the enclosed of the specified . - /// - /// cannot be null. - /// - /// - /// The enclosed of cannot be read from. - /// - public static byte[] ToByteArray(this IDecorator decorator, Action setup = null) - { - Validator.ThrowIfNull(decorator, nameof(decorator)); - Validator.ThrowIfFalse(decorator.Inner.CanRead, nameof(decorator.Inner), "Stream cannot be read from."); - var options = Patterns.Configure(setup); - try - { - if (decorator.Inner is MemoryStream s) { return s.ToArray(); } - using (var memoryStream = new MemoryStream(new byte[decorator.Inner.Length])) - { - var oldPosition = decorator.Inner.Position; - if (decorator.Inner.CanSeek) { decorator.Inner.Position = 0; } - decorator.Inner.CopyTo(memoryStream, options.BufferSize); - if (decorator.Inner.CanSeek) { decorator.Inner.Position = oldPosition; } - return memoryStream.ToArray(); - } - } - finally - { - if (!options.LeaveOpen) { decorator.Inner.Dispose(); } - } - } - - /// - /// Converts the enclosed of the specified to its equivalent representation. - /// - /// The to extend. - /// The which may be configured. - /// A task that represents the asynchronous operation. The task result contains a that is equivalent to the enclosed of the specified . - /// - /// cannot be null. - /// - /// - /// The enclosed of cannot be read from. - /// - public static Task ToByteArrayAsync(this IDecorator decorator, Action setup = null) - { - Validator.ThrowIfNull(decorator, nameof(decorator)); - Validator.ThrowIfFalse(decorator.Inner.CanRead, nameof(decorator.Inner), "Stream cannot be read from."); - return ToByteArrayAsyncCore(decorator, Patterns.Configure(setup)); - } - - private static async Task ToByteArrayAsyncCore(this IDecorator decorator, StreamCopyOptions options) - { - try - { - if (decorator.Inner is MemoryStream s) { return s.ToArray(); } - using (var memoryStream = new MemoryStream(new byte[decorator.Inner.Length])) - { - var oldPosition = decorator.Inner.Position; - if (decorator.Inner.CanSeek) { decorator.Inner.Position = 0; } - await decorator.Inner.CopyToAsync(memoryStream, options.BufferSize, options.CancellationToken).ConfigureAwait(false); - if (decorator.Inner.CanSeek) { decorator.Inner.Position = oldPosition; } - return memoryStream.ToArray(); - } - } - finally - { - if (!options.LeaveOpen) { decorator.Inner.Dispose(); } - } - } - /// /// Reads the bytes from the enclosed of the specified and writes them to the . /// @@ -186,32 +40,49 @@ public static void CopyStream(this IDecorator decorator, Stream destinat } /// - /// Asynchronously reads the bytes from the enclosed of the specified and writes them to the . + /// Converts the enclosed of the specified to its equivalent representation. Not intended to be used directly from your code. /// /// The to extend. - /// The to which the contents of the current stream will be copied. /// The size of the buffer. This value must be greater than zero. The default size is 81920. - /// The token to monitor for cancellation requests. The default value is . - /// if true, the enclosed of the specified will temporarily have its position changed to 0; otherwise the position is left untouched. + /// if true, the object is being left open; otherwise it is being closed and disposed. /// /// cannot be null. /// - public static async Task CopyStreamAsync(this IDecorator decorator, Stream destination, int bufferSize = 81920, CancellationToken ct = default, bool changePosition = true) + public static byte[] InvokeToByteArray(this IDecorator decorator, int bufferSize = 81920, bool leaveOpen = false) { Validator.ThrowIfNull(decorator, nameof(decorator)); - var source = decorator.Inner; - long lastPosition = 0; - if (changePosition && source.CanSeek) + Validator.ThrowIfFalse(decorator.Inner.CanRead, nameof(decorator.Inner), "Stream cannot be read from."); + try { - lastPosition = source.Position; - if (source.CanSeek) { source.Position = 0; } - } + if (decorator.Inner is MemoryStream s) + { + return s.ToArray(); + } + + using (var memoryStream = new MemoryStream(new byte[decorator.Inner.Length])) + { + var oldPosition = decorator.Inner.Position; + if (decorator.Inner.CanSeek) + { + decorator.Inner.Position = 0; + } - await source.CopyToAsync(destination, bufferSize, ct).ConfigureAwait(false); - await destination.FlushAsync(ct).ConfigureAwait(false); + decorator.Inner.CopyTo(memoryStream, bufferSize); + if (decorator.Inner.CanSeek) + { + decorator.Inner.Position = oldPosition; + } - if (changePosition && source.CanSeek) { source.Position = lastPosition; } - if (changePosition && destination.CanSeek) { destination.Position = 0; } + return memoryStream.ToArray(); + } + } + finally + { + if (!leaveOpen) + { + decorator.Inner.Dispose(); + } + } } } } \ No newline at end of file diff --git a/src/Cuemon.Core/Extensions/Reflection/AssemblyDecoratorExtensions.cs b/src/Cuemon.Core/Extensions/Reflection/AssemblyDecoratorExtensions.cs index 4f69f4335..8017960e4 100644 --- a/src/Cuemon.Core/Extensions/Reflection/AssemblyDecoratorExtensions.cs +++ b/src/Cuemon.Core/Extensions/Reflection/AssemblyDecoratorExtensions.cs @@ -62,47 +62,47 @@ public static IEnumerable GetTypes(this IDecorator decorator, st } /// - /// Returns a that represents the of the underlying of the . + /// Returns a that represents the of the underlying of the . /// /// The to extend. - /// A that represents the underlying of the . + /// A that represents the underlying of the . /// /// cannot be null. /// - public static Version GetAssemblyVersion(this IDecorator decorator) + public static VersionResult GetAssemblyVersion(this IDecorator decorator) { Validator.ThrowIfNull(decorator, nameof(decorator)); - return decorator.Inner.GetName().Version; + return new VersionResult(decorator.Inner.GetName().Version); } /// - /// Returns a that represents the of the underlying of the . + /// Returns a that represents the of the underlying of the . /// /// The to extend. - /// A that represents the file version of the underlying of the ; null if no could be retrieved. + /// A that represents the file version of the underlying of the ; null if no could be retrieved. /// /// cannot be null. /// - public static Version GetFileVersion(this IDecorator decorator) + public static VersionResult GetFileVersion(this IDecorator decorator) { Validator.ThrowIfNull(decorator, nameof(decorator)); var version = decorator.Inner.GetCustomAttribute(); - return version == null ? null : new Version(version.Version); + return new VersionResult(version.Version); } /// - /// Returns a that represents the of the underlying of the . + /// Returns a that represents the of the underlying of the . /// /// The to extend. - /// A that represents the product version of the underlying of the ; null if no could be retrieved. + /// A that represents the product version of the underlying of the ; null if no could be retrieved. /// /// cannot be null. /// - public static Version GetProductVersion(this IDecorator decorator) + public static VersionResult GetProductVersion(this IDecorator decorator) { Validator.ThrowIfNull(decorator, nameof(decorator)); var version = decorator.Inner.GetCustomAttribute(); - return version == null ? null : new Version(version.InformationalVersion); + return new VersionResult(version.InformationalVersion); } /// diff --git a/src/Cuemon.Core/Extensions/StringDecoratorExtensions.cs b/src/Cuemon.Core/Extensions/StringDecoratorExtensions.cs index 8d23a921f..6ab101613 100644 --- a/src/Cuemon.Core/Extensions/StringDecoratorExtensions.cs +++ b/src/Cuemon.Core/Extensions/StringDecoratorExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using Cuemon.Text; @@ -139,7 +140,7 @@ public static string ToAsciiEncodedString(this IDecorator decorator, Act public static Stream ToStream(this IDecorator decorator, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); - return Disposable.SafeInvoke(() => new MemoryStream(), ms => + return Patterns.SafeInvoke(() => new MemoryStream(), ms => { var bytes = Convertible.GetBytes(decorator.Inner, setup); ms.Write(bytes, 0, bytes.Length); @@ -152,6 +153,7 @@ public static Stream ToStream(this IDecorator decorator, Action of the specified to a . /// /// The to extend. + /// The token to monitor for cancellation requests. The default value is . /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a containing the result of the enclosed of the specified . /// will be initialized with and . @@ -161,17 +163,16 @@ public static Stream ToStream(this IDecorator decorator, Action /// was initialized with an invalid . /// - public static Task ToStreamAsync(this IDecorator decorator, Action setup = null) + public static Task ToStreamAsync(this IDecorator decorator, CancellationToken ct = default, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); - var options = Patterns.Configure(setup); - return Disposable.SafeInvokeAsync(() => new MemoryStream(), async (ms, ct) => + return Patterns.SafeInvokeAsync(() => new MemoryStream(), async (ms, token) => { var bytes = Convertible.GetBytes(decorator.Inner, setup); - await ms.WriteAsync(bytes, 0, bytes.Length, ct).ConfigureAwait(false); + await ms.WriteAsync(bytes, 0, bytes.Length, token).ConfigureAwait(false); ms.Position = 0; return ms; - }, options.CancellationToken); + }, ct); } /// diff --git a/src/Cuemon.Core/FuncFactory.cs b/src/Cuemon.Core/FuncFactory.cs index 14df4e07a..afb0d6b59 100644 --- a/src/Cuemon.Core/FuncFactory.cs +++ b/src/Cuemon.Core/FuncFactory.cs @@ -530,7 +530,7 @@ public TResult ExecuteMethod() /// /// A new that is a copy of this instance. /// When thread safety is required this is the method to invoke. - public FuncFactory Clone() + public override TemplateFactory Clone() { return new FuncFactory(Method, GenericArguments.Clone() as TTuple); } diff --git a/src/Cuemon.Core/Generate.cs b/src/Cuemon.Core/Generate.cs index 8cb92d757..96b5d6ecd 100644 --- a/src/Cuemon.Core/Generate.cs +++ b/src/Cuemon.Core/Generate.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using Cuemon.Collections.Generic; using Cuemon.Reflection; -using Cuemon.Security.Cryptography; +using Cuemon.Security; namespace Cuemon { @@ -24,10 +24,12 @@ public static class Generate private static readonly ThreadLocal LocalRandomizer = new ThreadLocal(() => { var rnd = new byte[4]; - var rng = RandomNumberGenerator.Create(); - rng.GetBytes(rnd); - var seed = BitConverter.ToInt32(rnd, 0); - return new Random(seed); + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetNonZeroBytes(rnd); + var seed = BitConverter.ToInt32(rnd, 0); + return new Random(seed); + } }); /// @@ -156,10 +158,10 @@ public static string RandomString(int length, params string[] values) { Validator.ThrowIfSequenceNullOrEmpty(values, nameof(values)); var result = new ConcurrentBag(); - Parallel.For(0, length, i => + Parallel.For(0, length, i => { - var index = RandomNumber(values.Length); - var indexLength = values[index].Length; + var index = RandomNumber(values.Length); + var indexLength = values[index].Length; result.Add(values[index][RandomNumber(indexLength)]); }); return Decorator.Enclose(result).ToStringEquivalent(); diff --git a/src/Cuemon.Core/GlobalSuppressions.cs b/src/Cuemon.Core/GlobalSuppressions.cs index 7554af4d8..60a79099a 100644 --- a/src/Cuemon.Core/GlobalSuppressions.cs +++ b/src/Cuemon.Core/GlobalSuppressions.cs @@ -46,10 +46,217 @@ [assembly: SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Validator.ThrowIfUri(System.String,System.UriKind,System.String,System.String)")] [assembly: SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Validator.ThrowIfWhiteSpace(System.String,System.String,System.String)")] [assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "This is a base class implementation of the IDisposable interface tailored to avoid wrong implementations.", Scope = "type", Target = "~T:Cuemon.Disposable")] -[assembly: SuppressMessage("Major Code Smell", "S2589:Boolean expressions should not be gratuitous", Justification = "Bug in Sonar?", Scope = "member", Target = "~M:Cuemon.ExceptionInsights.ToExceptionDescriptor(System.Exception,System.String,System.String,System.Uri)~Cuemon.Diagnostics.ExceptionDescriptor")] [assembly: SuppressMessage("Minor Code Smell", "S1128:Unused \"using\" should be removed", Justification = "It is actually used when resolving extension method from System.Collections.Generic; SC just can't figure this out.")] -[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "By design; property serves it purpose.", Scope = "member", Target = "~P:Cuemon.Data.ConcurrentDsvDataReader.NullRead")] -[assembly: SuppressMessage("Critical Code Smell", "S927:parameter names should match base declaration and other partial definitions", Justification = "By design to help clarify context.", Scope = "member", Target = "~M:Cuemon.Data.ConcurrentDsvDataReader.ReadNext(System.String[])~System.String[]")] -[assembly: SuppressMessage("Critical Code Smell", "S927:parameter names should match base declaration and other partial definitions", Justification = "By design to help clarify context.", Scope = "member", Target = "~M:Cuemon.Data.DsvDataReader.ReadNext(System.String[])~System.String[]")] -[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "By design; property serves it purpose.", Scope = "member", Target = "~P:Cuemon.Data.DsvDataReader.NullRead")] -[assembly: SuppressMessage("Minor Code Smell", "S1199:Nested code blocks should not be used", Justification = "By design.", Scope = "member", Target = "~M:Cuemon.IO.StreamFactory.CreateStreamCore``1(Cuemon.ActionFactory{``0},System.Action{Cuemon.IO.StreamWriterOptions})~System.IO.Stream")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateEighteen``18(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateEighteen``18(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "", Scope = "member", Target = "~M:Cuemon.Template.CreateEleven``11(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateFifteen``15(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateFifteen``15(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateFourteen``14(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateFourteen``14(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateNineteen``19(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateNineteen``19(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateSeventeen``17(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateSeventeen``17(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateSixteen``16(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateSixteen``16(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateThirteen``13(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateThirteen``13(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateTwelve``12(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateTwelve``12(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateTwenty``20(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18,``19)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18,``19}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateTwenty``20(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18,``19)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17,``18,``19}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template`8.#ctor(`0,`1,`2,`3,`4,`5,`6,`7)")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`10")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`10")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`11")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`11")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`12")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`12")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`13")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`13")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`14")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`14")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`15")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`15")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`16")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`16")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`17")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`17")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`18")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`18")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`19")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`19")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`20")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`20")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`4")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`5")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`6")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`6")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`7")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`7")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`8")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`8")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "type", Target = "~T:Cuemon.Template`9")] +[assembly: SuppressMessage("Major Code Smell", "S110:Inheritance tree of classes should not be too deep", Justification = "By design; provides invaluable information about arguments given to any factory implementation.", Scope = "type", Target = "~T:Cuemon.Template`9")] +[assembly: SuppressMessage("Critical Code Smell", "S3776:Cognitive Complexity of methods should not be too high", Justification = "By design.", Scope = "member", Target = "~F:Cuemon.Convertible.EndianSensitiveByteArrayConverters")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateEight``8(``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateEight``8(``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateEleven``11(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateFive``5(``0,``1,``2,``3,``4)~Cuemon.Template{``0,``1,``2,``3,``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateFour``4(``0,``1,``2,``3)~Cuemon.Template{``0,``1,``2,``3}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateNine``9(``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateNine``9(``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateSeven``7(``0,``1,``2,``3,``4,``5,``6)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateSix``6(``0,``1,``2,``3,``4,``5)~Cuemon.Template{``0,``1,``2,``3,``4,``5}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateTen``10(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic.", Scope = "member", Target = "~M:Cuemon.Template.CreateTen``10(``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``10(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``11(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``12(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``13(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``14(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``15(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``16(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``7(System.Action{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4,``5,``6)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``8(System.Action{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``9(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``10(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``11(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``12(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``13(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``14(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``15(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``16(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``4(System.Action{``0,``1,``2,``3},``0,``1,``2,``3)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``5(System.Action{``0,``1,``2,``3,``4},``0,``1,``2,``3,``4)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``6(System.Action{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,``5)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``7(System.Action{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4,``5,``6)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``8(System.Action{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Action delegates.", Scope = "member", Target = "~M:Cuemon.ActionFactory.Create``9(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.ActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8},``9}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8},``9}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``10}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``10}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``12(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``11}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``12(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``11}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``13(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``12}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``13(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``12}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``14(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``13}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``14(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``13}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``15(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``14}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``15(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``14}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``16(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``15}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``16(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``15}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``17(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``16}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``17(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``16}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``4(System.Func{``0,``1,``2,``3},``0,``1,``2)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2},``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``5(System.Func{``0,``1,``2,``3,``4},``0,``1,``2,``3)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3},``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``6(System.Func{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4},``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``7(System.Func{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4,``5)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5},``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6},``7}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6},``7}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7},``8}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Func delegates.", Scope = "member", Target = "~M:Cuemon.FuncFactory.Create``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.FuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7},``8}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``12(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``12(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``13(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``13(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``14(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``14(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``15(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``15(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``4(System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``5(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``6(System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``7(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``7(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskActionFactory.Create``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.TaskActionFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task{``9}},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8},``9}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task{``10}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``10}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``12(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,System.Threading.CancellationToken,System.Threading.Tasks.Task{``11}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``11}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``13(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,System.Threading.CancellationToken,System.Threading.Tasks.Task{``12}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``12}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``14(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,System.Threading.CancellationToken,System.Threading.Tasks.Task{``13}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``13}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``15(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,System.Threading.CancellationToken,System.Threading.Tasks.Task{``14}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``14}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``16(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,System.Threading.CancellationToken,System.Threading.Tasks.Task{``15}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``15}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``8(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task{``7}},``0,``1,``2,``3,``4,``5,``6)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6},``7}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task{``8}},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7},``8}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task{``9}},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8},``9}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task{``10}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``10}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``12(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,System.Threading.CancellationToken,System.Threading.Tasks.Task{``11}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``11}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``13(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,System.Threading.CancellationToken,System.Threading.Tasks.Task{``12}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``12}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``14(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,System.Threading.CancellationToken,System.Threading.Tasks.Task{``13}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``13}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``15(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,System.Threading.CancellationToken,System.Threading.Tasks.Task{``14}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``14}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``16(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,System.Threading.CancellationToken,System.Threading.Tasks.Task{``15}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``15}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``4(System.Func{``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``0,``1,``2)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2},``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``5(System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3},``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``6(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4},``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``7(System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``0,``1,``2,``3,``4,``5)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5},``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``8(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task{``7}},``0,``1,``2,``3,``4,``5,``6)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6},``7}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for Task based Func delegates.", Scope = "member", Target = "~M:Cuemon.TaskFuncFactory.Create``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task{``8}},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.TaskFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7},``8}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`10")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`11")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`12")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`13")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`14")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`15")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`16")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`17")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`18")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "A Func designed for the Tester pattern. Microsoft allows 17 arguments in there Func impl; so do i here.", Scope = "type", Target = "~T:Cuemon.TesterFunc`9")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``10(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7},``8,``9}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``10(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7},``8,``9}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``11(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8},``9,``10}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``11(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8},``9,``10}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``12(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``10,``11}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``12(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``10,``11}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``13(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``11,``12}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``13(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``11,``12}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``14(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``12,``13}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``14(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11},``12,``13}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``15(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``13,``14}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``15(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12},``13,``14}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``16(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``14,``15}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``16(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13},``14,``15}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``17(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``15,``16}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``17(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14},``15,``16}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``18(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``16,``17}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``18(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15,``16,``17},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10,``11,``12,``13,``14,``15},``16,``17}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``4(Cuemon.TesterFunc{``0,``1,``2,``3},``0,``1)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1},``2,``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``5(Cuemon.TesterFunc{``0,``1,``2,``3,``4},``0,``1,``2)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2},``3,``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``6(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3},``4,``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``7(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4},``5,``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``8(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5},``6,``7}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``9(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6},``7,``8}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "member", Target = "~M:Cuemon.TesterFuncFactory.Create``9(Cuemon.TesterFunc{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6)~Cuemon.TesterFuncFactory{Cuemon.Template{``0,``1,``2,``3,``4,``5,``6},``7,``8}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; think about it as a Tuple/Variadic - but for TesterFunc delegates.", Scope = "type", Target = "~T:Cuemon.TesterFuncFactory`3")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvoke``6(System.Func{``5},System.Func{``5,``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{System.Exception,``0,``1,``2,``3,``4})~``5")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvokeAsync``5(System.Func{``4},System.Func{``4,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Threading.CancellationToken,System.Func{System.Exception,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task})~System.Threading.Tasks.Task{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvokeAsync``6(System.Func{``5},System.Func{``5,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Func{System.Exception,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task})~System.Threading.Tasks.Task{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvoke``4(System.Func{``3},System.Func{``3,``0,``1,``2,``3},``0,``1,``2,System.Action{System.Exception,``0,``1,``2})~``3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvoke``5(System.Func{``4},System.Func{``4,``0,``1,``2,``3,``4},``0,``1,``2,``3,System.Action{System.Exception,``0,``1,``2,``3})~``4")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvoke``6(System.Func{``5},System.Func{``5,``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{System.Exception,``0,``1,``2,``3,``4})~``5")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvokeAsync``4(System.Func{``3},System.Func{``3,``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``0,``1,``2,System.Threading.CancellationToken,System.Func{System.Exception,``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task})~System.Threading.Tasks.Task{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvokeAsync``5(System.Func{``4},System.Func{``4,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Threading.CancellationToken,System.Func{System.Exception,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task})~System.Threading.Tasks.Task{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions)..", Scope = "member", Target = "~M:Cuemon.Patterns.SafeInvokeAsync``6(System.Func{``5},System.Func{``5,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Func{System.Exception,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task})~System.Threading.Tasks.Task{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Condition.FlipFlop``4(System.Boolean,System.Action{``0,``1,``2,``3},System.Action{``0,``1,``2,``3},``0,``1,``2,``3)")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Condition.FlipFlop``5(System.Boolean,System.Action{``0,``1,``2,``3,``4},System.Action{``0,``1,``2,``3,``4},``0,``1,``2,``3,``4)")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Condition.FlipFlop``5(System.Boolean,System.Action{``0,``1,``2,``3,``4},System.Action{``0,``1,``2,``3,``4},``0,``1,``2,``3,``4)")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Condition.TernaryIf``4(System.Boolean,System.Func{``0,``1,``2,``3},System.Func{``0,``1,``2,``3},``0,``1,``2)~``3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Condition.TernaryIf``5(System.Boolean,System.Func{``0,``1,``2,``3,``4},System.Func{``0,``1,``2,``3,``4},``0,``1,``2,``3)~``4")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Condition.TernaryIf``6(System.Boolean,System.Func{``0,``1,``2,``3,``4,``5},System.Func{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4)~``5")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Condition.TernaryIf``6(System.Boolean,System.Func{``0,``1,``2,``3,``4,``5},System.Func{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4)~``5")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Reflection.ActivatorFactory.CreateInstance``4(``0,``1,``2,System.Action{Cuemon.Reflection.ActivatorOptions})~``3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Reflection.ActivatorFactory.CreateInstance``5(``0,``1,``2,``3,System.Action{Cuemon.Reflection.ActivatorOptions})~``4")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Reflection.ActivatorFactory.CreateInstance``6(``0,``1,``2,``3,``4,System.Action{Cuemon.Reflection.ActivatorOptions})~``5")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Runtime.Dependency.Create``4(``0,``1,``2)~``3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Runtime.Dependency.Create``5(``0,``1,``2,``3)~``4")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Runtime.Dependency.Create``6(``0,``1,``2,``3,``4)~``5")] diff --git a/src/Cuemon.Core/Globalization/IMessageLocalizer.cs b/src/Cuemon.Core/Globalization/IMessageLocalizer.cs deleted file mode 100644 index 727e75a9f..000000000 --- a/src/Cuemon.Core/Globalization/IMessageLocalizer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Cuemon.Globalization -{ - /// - /// Provides a generic way to support localized messages on attribute decorated methods. - /// - public interface IMessageLocalizer - { - /// - /// Gets or sets and explicit message string. - /// - /// This property is intended to be used for non-localizable messages. Use and for localizable messages. - string Message { get; set; } - - /// - /// Gets or sets the resource name (property name) to use as the key for lookups on the resource type. - /// - /// Use this property to set the name of the property within that will provide a localized message. - string MessageResourceName { get; set; } - - /// - /// Gets or sets the resource type to use for message lookups. - /// - /// Use this property only in conjunction with . They are used together to retrieve localized messages at runtime. - Type MessageResourceType { get; set; } - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/Globalization/RegionInfoExtensions.cs b/src/Cuemon.Core/Globalization/RegionInfoExtensions.cs deleted file mode 100644 index 2384ba98e..000000000 --- a/src/Cuemon.Core/Globalization/RegionInfoExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace Cuemon.Globalization -{ - /// - /// This is an extension implementation of the class. - /// - public static class RegionInfoExtensions - { - /// - /// Resolves a sequence of related objects for the specified . - /// - /// The region to resolve a sequence of objects from. - /// An sequence of objects. - public static IEnumerable GetCultures(this RegionInfo region) - { - Validator.ThrowIfNull(region, nameof(region)); - return World.SpecificCultures.Value.Where(c => c.Name.EndsWith(region.TwoLetterISORegionName)); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/Globalization/ResourceAttribute.cs b/src/Cuemon.Core/Globalization/ResourceAttribute.cs new file mode 100644 index 000000000..deb787904 --- /dev/null +++ b/src/Cuemon.Core/Globalization/ResourceAttribute.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; + +namespace Cuemon.Globalization +{ + /// + /// Provides a generic way to support localization on attribute decorated methods. + /// + /// + public abstract class ResourceAttribute : Attribute + { + private readonly ConcurrentDictionary _propertyInfos = new ConcurrentDictionary(); + + /// + /// Initializes a new instance of the class. + /// + protected ResourceAttribute() + { + } + + /// + /// Gets or sets the type that contains the resources for looking up localized strings. + /// + /// The type that contains the resources for looking up localized strings. + public Type ResourceType { get; set; } + + /// + /// Returns the value of the specified string resource. + /// + /// The name of the resource to retrieve. + /// The value of the resource localized for the callers current UI culture, or null if cannot be found on the . + /// + /// You must specify a to perform the actual lookup of localized strings. + /// + /// + /// The specified does not contain a resource with the specified . + /// + protected string GetString(string name) + { + Validator.ThrowIfNullOrWhitespace(name, nameof(name)); + if (ResourceType == null) { throw new InvalidOperationException("You must specify a type to perform the actual lookup of localized strings."); } + var cacheKey = $"{ResourceType.ToString().ToUpperInvariant()}.{name.ToUpperInvariant()}"; + if (!_propertyInfos.TryGetValue(cacheKey, out var property)) + { + property = ResourceType.GetProperty(name, BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + var getMethod = property?.GetGetMethod(true); + if (getMethod != null && (getMethod.IsAssembly || getMethod.IsPublic)) + { + _propertyInfos.TryAdd(cacheKey, property); + } + } + return property == null ? throw new ArgumentException($"The specified type, '{ResourceType.FullName}', does not contain a resource with the specified '{name}'.") : property.GetValue(null, null) as string; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Globalization/World.cs b/src/Cuemon.Core/Globalization/World.cs index d4db3ec3b..c27c7e155 100644 --- a/src/Cuemon.Core/Globalization/World.cs +++ b/src/Cuemon.Core/Globalization/World.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; -using System.Reflection; -using Cuemon.Reflection; namespace Cuemon.Globalization { @@ -13,40 +10,13 @@ namespace Cuemon.Globalization /// public static class World { - private const int CultureTypesSpecificCultures = 2; - private static readonly MethodInfo CultureInfoGetCultures = typeof(CultureInfo).GetMethod("GetCultures", new MemberReflection(excludeInheritancePath: true)); - internal static readonly Lazy> SpecificCultures = new Lazy>(() => { var cultures = new SortedList(); var specificCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures); - if (specificCultures != null) - { - foreach (var c in specificCultures) - { - cultures.Add(c.DisplayName, c); - } - return cultures.Values; - } - - using (var lfdSpecificCultures = Decorator.Enclose(typeof(World).Assembly).GetManifestResources("CultureInfo.SpecificCultures.dsv", ManifestResourceMatch.ContainsName).Values.Single()) + foreach (var c in specificCultures) { - using (var reader = new StreamReader(lfdSpecificCultures)) - { - string specificCulture; - while ((specificCulture = reader.ReadLine()) != null) - { - try - { - var c = new CultureInfo(specificCulture); - cultures.Add(c.DisplayName, c); - } - catch (CultureNotFoundException) - { - // ignored on systems not supporting the specificCulture - } - } - } + cultures.Add(c.DisplayName, c); } return cultures.Values; }); @@ -67,5 +37,16 @@ public static class World /// /// The .NET specific regions of the world. public static IEnumerable Regions { get; } = SpecificRegions.Value; + + /// + /// Resolves a sequence of related objects for the specified . + /// + /// The region to resolve a sequence of objects from. + /// An sequence of objects. + public static IEnumerable GetCultures(RegionInfo region) + { + Validator.ThrowIfNull(region, nameof(region)); + return SpecificCultures.Value.Where(c => c.Name.EndsWith(region.TwoLetterISORegionName)); + } } } \ No newline at end of file diff --git a/src/Cuemon.Core/IO/StreamOptions.cs b/src/Cuemon.Core/IO/StreamOptions.cs deleted file mode 100644 index 633d2c1b4..000000000 --- a/src/Cuemon.Core/IO/StreamOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; - -namespace Cuemon.IO -{ - /// - /// Configuration options for . - /// - public class StreamOptions : DisposableOptions - { - - /// - /// Initializes a new instance of the class. - /// - public StreamOptions() - { - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/Patterns.cs b/src/Cuemon.Core/Patterns.cs index 18f97cc22..56511492d 100644 --- a/src/Cuemon.Core/Patterns.cs +++ b/src/Cuemon.Core/Patterns.cs @@ -2,11 +2,13 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; namespace Cuemon { /// - /// Provides a generic way to support different types of design patterns with small utility methods. + /// Provides a generic way to support different types of design patterns and practices with small utility methods. /// public sealed class Patterns { @@ -34,13 +36,13 @@ public static bool TryInvoke(Action method) } catch (Exception ex) { - if (ex is OutOfMemoryException || - ex is StackOverflowException || - ex is SEHException || - ex is AccessViolationException || - #pragma warning disable CS0618 // Type or member is obsolete + if (ex is OutOfMemoryException || + ex is StackOverflowException || + ex is SEHException || + ex is AccessViolationException || +#pragma warning disable CS0618 // Type or member is obsolete ex is ExecutionEngineException) // fatal exceptions; re-throw for .NET "legacy" (.NET Core will handle these by a high-level catch-all handler) - #pragma warning restore CS0618 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete { throw; } @@ -66,13 +68,13 @@ public static bool TryInvoke(Func method, out TResult result) } catch (Exception ex) { - if (ex is OutOfMemoryException || - ex is StackOverflowException || - ex is SEHException || + if (ex is OutOfMemoryException || + ex is StackOverflowException || + ex is SEHException || ex is AccessViolationException || - #pragma warning disable CS0618 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete ex is ExecutionEngineException) // fatal exceptions; re-throw for .NET "legacy" (.NET Core will handle these by a high-level catch-all handler) - #pragma warning restore CS0618 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete { throw; } @@ -166,9 +168,343 @@ public static Action ConfigureRevert(TOptions options) return o => { var to = typeof(TOptions); - var tops= to.GetRuntimeProperties().Where(pi => pi.CanRead && pi.CanWrite).ToList(); + var tops = to.GetRuntimeProperties().Where(pi => pi.CanRead && pi.CanWrite).ToList(); foreach (var p in tops) { p.SetValue(o, p.GetValue(options)); } }; } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The delegate that will handle any exceptions might thrown by . + /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static TResult SafeInvoke(Func initializer, Func tester, Action catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = FuncFactory.Create(tester, default); + var f2 = ActionFactory.Create(catcher, default); + return SafeInvokeCore(f1, initializer, f2); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The parameter of the function delegate and delegate . + /// The delegate that will handle any exceptions might thrown by . + /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static TResult SafeInvoke(Func initializer, Func tester, T arg, Action catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = FuncFactory.Create(tester, default, arg); + var f2 = ActionFactory.Create(catcher, default, arg); + return SafeInvokeCore(f1, initializer, f2); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The delegate that will handle any exceptions might thrown by . + /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, Action catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = FuncFactory.Create(tester, default, arg1, arg2); + var f2 = ActionFactory.Create(catcher, default, arg1, arg2); + return SafeInvokeCore(f1, initializer, f2); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the third parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The third parameter of the function delegate and delegate . + /// The delegate that will handle any exceptions might thrown by . + /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, T3 arg3, Action catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = FuncFactory.Create(tester, default, arg1, arg2, arg3); + var f2 = ActionFactory.Create(catcher, default, arg1, arg2, arg3); + return SafeInvokeCore(f1, initializer, f2); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the third parameter of the function delegate and delegate . + /// The type of the fourth parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The third parameter of the function delegate and delegate . + /// The fourth parameter of the function delegate and delegate . + /// The delegate that will handle any exceptions might thrown by . + /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = FuncFactory.Create(tester, default, arg1, arg2, arg3, arg4); + var f2 = ActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4); + return SafeInvokeCore(f1, initializer, f2); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the third parameter of the function delegate and delegate . + /// The type of the fourth parameter of the function delegate and delegate . + /// The type of the fifth parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The third parameter of the function delegate and delegate . + /// The fourth parameter of the function delegate and delegate . + /// The fifth parameter of the function delegate and delegate . + /// The delegate that will handle any exceptions might thrown by . + /// The return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static TResult SafeInvoke(Func initializer, Func tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = FuncFactory.Create(tester, default, arg1, arg2, arg3, arg4, arg5); + var f2 = ActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4, arg5); + return SafeInvokeCore(f1, initializer, f2); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The token to monitor for cancellation requests. The default value is . + /// The function delegate that will handle any exceptions might thrown by . + /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static Task SafeInvokeAsync(Func initializer, Func> tester, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = TaskFuncFactory.Create(tester, default); + var f2 = TaskActionFactory.Create(catcher, default); + return SafeInvokeAsyncCore(f1, initializer, f2, ct); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The parameter of the function delegate and delegate . + /// The token to monitor for cancellation requests. The default value is . + /// The function delegate that will handle any exceptions might thrown by . + /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static Task SafeInvokeAsync(Func initializer, Func> tester, T arg, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = TaskFuncFactory.Create(tester, default, arg); + var f2 = TaskActionFactory.Create(catcher, default, arg); + return SafeInvokeAsyncCore(f1, initializer, f2, ct); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The token to monitor for cancellation requests. The default value is . + /// The function delegate that will handle any exceptions might thrown by . + /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2); + var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2); + return SafeInvokeAsyncCore(f1, initializer, f2, ct); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the third parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The third parameter of the function delegate and delegate . + /// The token to monitor for cancellation requests. The default value is . + /// The function delegate that will handle any exceptions might thrown by . + /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, T3 arg3, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2, arg3); + var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2, arg3); + return SafeInvokeAsyncCore(f1, initializer, f2, ct); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the third parameter of the function delegate and delegate . + /// The type of the fourth parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The third parameter of the function delegate and delegate . + /// The fourth parameter of the function delegate and delegate . + /// The token to monitor for cancellation requests. The default value is . + /// The function delegate that will handle any exceptions might thrown by . + /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2, arg3, arg4); + var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4); + return SafeInvokeAsyncCore(f1, initializer, f2, ct); + } + + /// + /// Provides a generic way to abide the rule description of CA2000 (Dispose objects before losing scope). + /// + /// The type of the first parameter of the function delegate and delegate . + /// The type of the second parameter of the function delegate and delegate . + /// The type of the third parameter of the function delegate and delegate . + /// The type of the fourth parameter of the function delegate and delegate . + /// The type of the fifth parameter of the function delegate and delegate . + /// The type of the return value of the function delegate . + /// The function delegate that initializes an object implementing the interface. + /// The function delegate that is used to ensure that operations performed on abides CA2000. + /// The first parameter of the function delegate and delegate . + /// The second parameter of the function delegate and delegate . + /// The third parameter of the function delegate and delegate . + /// The fourth parameter of the function delegate and delegate . + /// The fifth parameter of the function delegate and delegate . + /// The token to monitor for cancellation requests. The default value is . + /// The function delegate that will handle any exceptions might thrown by . + /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate if the operations succeeded; otherwise null if the operation failed. + public static Task SafeInvokeAsync(Func initializer, Func> tester, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, CancellationToken ct = default, Func catcher = null) where TResult : class, IDisposable + { + Validator.ThrowIfNull(initializer, nameof(initializer)); + Validator.ThrowIfNull(tester, nameof(tester)); + var f1 = TaskFuncFactory.Create(tester, default, arg1, arg2, arg3, arg4, arg5); + var f2 = TaskActionFactory.Create(catcher, default, arg1, arg2, arg3, arg4, arg5); + return SafeInvokeAsyncCore(f1, initializer, f2, ct); + } + + private static TResult SafeInvokeCore(FuncFactory testerFactory, Func initializer, ActionFactory catcherFactory) + where TResult : class, IDisposable + where TTester : Template + where TCatcher : Template + { + TResult result = null; + try + { + testerFactory.GenericArguments.Arg1 = initializer(); + testerFactory.GenericArguments.Arg1 = testerFactory.ExecuteMethod(); + result = testerFactory.GenericArguments.Arg1; + testerFactory.GenericArguments.Arg1 = null; + } + catch (Exception e) + { + if (!catcherFactory.HasDelegate) + { + throw; + } + else + { + catcherFactory.GenericArguments.Arg1 = e; + catcherFactory.ExecuteMethod(); + } + } + finally + { + testerFactory.GenericArguments.Arg1?.Dispose(); + } + return result; + } + + private static async Task SafeInvokeAsyncCore(TaskFuncFactory testerFactory, Func initializer, TaskActionFactory catcherFactory, CancellationToken ct) + where TResult : class, IDisposable + where TTester : Template + where TCatcher : Template + { + TResult result = null; + try + { + testerFactory.GenericArguments.Arg1 = initializer(); + testerFactory.GenericArguments.Arg1 = await testerFactory.ExecuteMethodAsync(ct).ConfigureAwait(false); + result = testerFactory.GenericArguments.Arg1; + testerFactory.GenericArguments.Arg1 = null; + } + catch (Exception e) + { + if (!catcherFactory.HasDelegate) + { + throw; + } + else + { + catcherFactory.GenericArguments.Arg1 = e; + await catcherFactory.ExecuteMethodAsync(ct).ConfigureAwait(false); + } + } + finally + { + testerFactory.GenericArguments.Arg1?.Dispose(); + } + return result; + } } } \ No newline at end of file diff --git a/src/Cuemon.Core/Properties/PackageReleaseNotes.txt b/src/Cuemon.Core/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..dbaee21a1 --- /dev/null +++ b/src/Cuemon.Core/Properties/PackageReleaseNotes.txt @@ -0,0 +1,53 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- To use the earlier built-in support for transient fault handling, please refer to the Cuemon.Resilience namespace, as it has been merged and refactored out of this assembly +- To use the earlier built-in support for time-measuring and describing exceptions, please refer to the Cuemon.Diagnostics namespace, as it has been merged and refactored out of this assembly +- To use the earlier built-in support for a computed checksum operation, please refer to the Cuemon.Data.Integrity namespace, as it has been merged and refactored out of this and the Cuemon.Integrity assembly +- Any former extension methods of the Cuemon namespace (and related) was either removed completely or merged into there respective Cuemon.Extensions.* namespace equivalent +  +# Breaking Changes +- REMOVED StringFormatter class from the Cuemon namespace +- REMOVED StandardizedDateTimeFormatPattern enum from the Cuemon namespace +- MOVED AsyncOptions class in the Cuemon.Threading namespace to its own assembly (by the same name and namespace) +- REMOVED JsonWebToken class from the Cuemon.Security.Web namespace +- REMOVED JsonWebTokenHashAlgorithm class from the Cuemon.Security.Web namespace +- REMOVED JsonWebTokenHashAlgorithmConverter class from the Cuemon.Security.Web namespace +- REMOVED JsonWebTokenHeader class from the Cuemon.Security.Web namespace +- REMOVED JsonWebTokenPayload class from the Cuemon.Security.Web namespace +- REMOVED Obfuscator class from the Cuemon.Security namespace +- REMOVED ObfuscatorMapping class from the Cuemon.Security namespace +- REMOVED SecurityToken class from the Cuemon.Security namespace +- REMOVED SecurityTokenSettings class from the Cuemon.Security namespace (replaced with SignedUriOptions in the Cuemon.Extensions.Net.Security namespace) +- REMOVED SecurityUtility class from the Cuemon.Security namespace +- REMOVED AssemblyExtensions class from the Cuemon.Reflection namespace +- MOVED MemberInfoExtensions class from the Cuemon.Reflection namespace to Cuemon.Extensions.Reflection namespace +- REMOVED MethodBaseConverterExtensions class from the Cuemon.Reflection namespace +- MOVED LatencyException class in the Cuemon namespace to its own assembly and namespace (Cuemon.Resilience) +- MOVED TransientOperation class in the Cuemon namespace to its own assembly and namespace (Cuemon.Resilience) +- MOVED TransientFaultEvidence class in the Cuemon namespace to its own assembly and namespace (Cuemon.Resilience) +- MOVED TransientFaultException class in the Cuemon namespace to its own assembly and namespace (Cuemon.Resilience) +- MOVED TransientOperation class in the Cuemon namespace to its own assembly and namespace (Cuemon.Resilience) +- MOVED TransientOperationOptions class in the Cuemon namespace to its own assembly and namespace (Cuemon.Resilience) +- REMOVED IMessageLocalizer interface from the Cuemon.Globalization namespace +  +# New Features +- ADDED ResourceAttribute class in the Cuemon.Globalization namespace that provides a generic way to support localization on attribute decorated methods +- +  +# Bug Fixes +- +- +  +# Improvements +- +- +  +# Quality Actions +- +- +  +# Other Changes +- +- \ No newline at end of file diff --git a/src/Cuemon.Core/Reflection/MemberReflection.cs b/src/Cuemon.Core/Reflection/MemberReflection.cs index 8419820a6..50cbaf754 100644 --- a/src/Cuemon.Core/Reflection/MemberReflection.cs +++ b/src/Cuemon.Core/Reflection/MemberReflection.cs @@ -29,12 +29,14 @@ public static implicit operator BindingFlags(MemberReflection mr) /// if set to true non-public members are excluded from the binding constraint. /// if set to true static members are excluded from the binding constraint. /// if set to true derived members of a type's inheritance path are excluded from the binding constraint. - public MemberReflection(bool excludePrivate = false, bool excludeStatic = false, bool excludeInheritancePath = false) : + /// if set to true public members are excluded from the binding constraint. + public MemberReflection(bool excludePrivate = false, bool excludeStatic = false, bool excludeInheritancePath = false, bool excludePublic = false) : this(o => { o.ExcludeInheritancePath = excludeInheritancePath; o.ExcludePrivate = excludePrivate; o.ExcludeStatic = excludeStatic; + o.ExcludePublic = excludePublic; }) { } @@ -50,6 +52,7 @@ public MemberReflection(Action setup) if (options.ExcludePrivate) { flags &= ~BindingFlags.NonPublic; } if (options.ExcludeStatic) { flags &= ~BindingFlags.Static; } if (options.ExcludeInheritancePath) { flags |= BindingFlags.DeclaredOnly; } + if (options.ExcludePublic) { flags &= ~BindingFlags.Public; } Flags = flags; } diff --git a/src/Cuemon.Core/Reflection/MemberReflectionOptions.cs b/src/Cuemon.Core/Reflection/MemberReflectionOptions.cs index d77a777aa..ece279157 100644 --- a/src/Cuemon.Core/Reflection/MemberReflectionOptions.cs +++ b/src/Cuemon.Core/Reflection/MemberReflectionOptions.cs @@ -27,6 +27,10 @@ public class MemberReflectionOptions /// /// false /// + /// + /// + /// false + /// /// /// public MemberReflectionOptions() @@ -34,6 +38,7 @@ public MemberReflectionOptions() ExcludePrivate = false; ExcludeStatic = false; ExcludeInheritancePath = false; + ExcludePublic = false; } /// @@ -53,5 +58,11 @@ public MemberReflectionOptions() /// /// true if derived members of a type's inheritance path are excluded from the binding constraint; otherwise, false. public bool ExcludeInheritancePath { get; set; } + + /// + /// Gets or sets a value indicating whether public members are excluded from the binding constraint. + /// + /// true if public members are excluded from the binding constraint; otherwise, false. + public bool ExcludePublic { get; set; } } } \ No newline at end of file diff --git a/src/Cuemon.Core/Reflection/VersionResult.cs b/src/Cuemon.Core/Reflection/VersionResult.cs new file mode 100644 index 000000000..f5c3fd108 --- /dev/null +++ b/src/Cuemon.Core/Reflection/VersionResult.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; + +namespace Cuemon.Reflection +{ + /// + /// Represents different representations of a version scheme in a consistent way. + /// + public class VersionResult + { + private readonly Version _version; + + /// + /// Initializes a new instance of the class. + /// + /// The that represents a potential alphanumeric version. + public VersionResult(string alphanumericVersion) + { + AlphanumericVersion = alphanumericVersion; + } + + /// + /// Initializes a new instance of the class. + /// + /// The that represents a numerical version {major.minor.build.revision}. + public VersionResult(Version version) + { + AlphanumericVersion = version?.ToString(); + _version = version; + } + + /// + /// Gets the alphanumeric version assigned to this instance. + /// + /// The alphanumeric version assigned to this instance. + public string AlphanumericVersion { get; } + + /// + /// Gets a value indicating whether this instance has alphanumeric version assigned. + /// + /// true if this instance has alphanumeric version assigned; otherwise, false. + public bool HasAlphanumericVersion => !string.IsNullOrEmpty(AlphanumericVersion); + + /// + /// Determines whether this instance represents a semantic version. + /// + /// true if this instance represents a semantic version; otherwise, false. + public bool IsSemanticVersion() + { + if (HasAlphanumericVersion) + { + var isSemantic = true; + var versions = AlphanumericVersion.Split('.'); + foreach (var version in versions) + { + isSemantic &= int.TryParse(version, out _); + } + return !isSemantic; + } + return false; + } + + /// + /// Converts this instance to an equivalent object. + /// + /// An equivalent object of this instance. + /// + /// Only a non-semantic version can be converted to a object. + /// + public Version ToVersion() + { + if (HasAlphanumericVersion && _version != null) { return _version; } + if (HasAlphanumericVersion) + { + var versionComponents = new List(); + var versions = AlphanumericVersion.Split('.'); + foreach (var version in versions) + { + if (int.TryParse(version, out var v)) { versionComponents.Add(v); } + } + return new Version(DelimitedString.Create(versionComponents, o => o.Delimiter = ".")); + } + throw new InvalidOperationException("Only a non-semantic version can be converted to a Version object."); + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return AlphanumericVersion; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck.cs b/src/Cuemon.Core/Security/CyclicRedundancyCheck.cs similarity index 92% rename from src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck.cs rename to src/Cuemon.Core/Security/CyclicRedundancyCheck.cs index f8a267a12..fa952a170 100644 --- a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck.cs +++ b/src/Cuemon.Core/Security/CyclicRedundancyCheck.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Represents the base class from which all implementations of the CRC (Cyclic Redundancy Check) checksum algorithm must derive. @@ -10,9 +10,6 @@ namespace Cuemon.Security.Cryptography public abstract class CyclicRedundancyCheck : Hash { private readonly Lazy> _lazyLookup; - private readonly ulong _initialValue; - private readonly ulong _finalXor; - /// /// Initializes a new instance of the class. @@ -24,8 +21,8 @@ public abstract class CyclicRedundancyCheck : Hash protected CyclicRedundancyCheck(ulong polynomial, ulong initialValue, ulong finalXor, Action setup) : base(setup) { _lazyLookup = new Lazy>(() => PolynomialTableInitializerCore(polynomial)); - _initialValue = initialValue; - _finalXor = finalXor; + InitialValue = initialValue; + FinalXor = finalXor; } private List PolynomialTableInitializerCore(ulong polynomial) @@ -70,12 +67,12 @@ private List PolynomialTableInitializerCore(ulong polynomial) /// Gets the CRC initial value of the register. /// /// The CRC initial value of the register. - public ulong InitialValue => _initialValue; + public ulong InitialValue { get; } /// /// Gets the CRC final value that is XORed to the final register value. /// /// The CRC final value that is XORed to the final register value. - public ulong FinalXor => _finalXor; + public ulong FinalXor { get; } } } \ No newline at end of file diff --git a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck32.cs b/src/Cuemon.Core/Security/CyclicRedundancyCheck32.cs similarity index 98% rename from src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck32.cs rename to src/Cuemon.Core/Security/CyclicRedundancyCheck32.cs index 29378b6ca..f0e31694d 100644 --- a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck32.cs +++ b/src/Cuemon.Core/Security/CyclicRedundancyCheck32.cs @@ -1,6 +1,6 @@ using System; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides a CRC-32 implementation of the CRC (Cyclic Redundancy Check) checksum algorithm for 32-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck64.cs b/src/Cuemon.Core/Security/CyclicRedundancyCheck64.cs similarity index 98% rename from src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck64.cs rename to src/Cuemon.Core/Security/CyclicRedundancyCheck64.cs index 66825b874..c71b4e327 100644 --- a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheck64.cs +++ b/src/Cuemon.Core/Security/CyclicRedundancyCheck64.cs @@ -1,6 +1,6 @@ using System; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides a CRC-64 implementation of the CRC (Cyclic Redundancy Check) checksum algorithm for 64-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheckAlgorithm.cs b/src/Cuemon.Core/Security/CyclicRedundancyCheckAlgorithm.cs similarity index 97% rename from src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheckAlgorithm.cs rename to src/Cuemon.Core/Security/CyclicRedundancyCheckAlgorithm.cs index 3f4b210f1..950f8d747 100644 --- a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheckAlgorithm.cs +++ b/src/Cuemon.Core/Security/CyclicRedundancyCheckAlgorithm.cs @@ -1,4 +1,4 @@ -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Different models of the CRC algorithm family. diff --git a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheckOptions.cs b/src/Cuemon.Core/Security/CyclicRedundancyCheckOptions.cs similarity index 98% rename from src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheckOptions.cs rename to src/Cuemon.Core/Security/CyclicRedundancyCheckOptions.cs index 288a5b4f2..9b04c4837 100644 --- a/src/Cuemon.Core/Security/Cryptography/CyclicRedundancyCheckOptions.cs +++ b/src/Cuemon.Core/Security/CyclicRedundancyCheckOptions.cs @@ -1,4 +1,4 @@ -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Configuration options for . diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo1024.cs b/src/Cuemon.Core/Security/FowlerNollVo1024.cs similarity index 97% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVo1024.cs rename to src/Cuemon.Core/Security/FowlerNollVo1024.cs index 8a6546a29..031d979ce 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo1024.cs +++ b/src/Cuemon.Core/Security/FowlerNollVo1024.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides an implementation of the FVN (Fowler–Noll–Vo) non-cryptographic hashing algorithm for 1024-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo128.cs b/src/Cuemon.Core/Security/FowlerNollVo128.cs similarity index 95% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVo128.cs rename to src/Cuemon.Core/Security/FowlerNollVo128.cs index 2ceb65ef7..986957c52 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo128.cs +++ b/src/Cuemon.Core/Security/FowlerNollVo128.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides an implementation of the FVN (Fowler–Noll–Vo) non-cryptographic hashing algorithm for 128-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo256.cs b/src/Cuemon.Core/Security/FowlerNollVo256.cs similarity index 95% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVo256.cs rename to src/Cuemon.Core/Security/FowlerNollVo256.cs index 62a9f6407..a4bcc0be8 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo256.cs +++ b/src/Cuemon.Core/Security/FowlerNollVo256.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides an implementation of the FVN (Fowler–Noll–Vo) non-cryptographic hashing algorithm for 256-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo32.cs b/src/Cuemon.Core/Security/FowlerNollVo32.cs similarity index 95% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVo32.cs rename to src/Cuemon.Core/Security/FowlerNollVo32.cs index 69e448af8..2e75d80d9 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo32.cs +++ b/src/Cuemon.Core/Security/FowlerNollVo32.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides an implementation of the FVN (Fowler–Noll–Vo) non-cryptographic hashing algorithm for 32-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo512.cs b/src/Cuemon.Core/Security/FowlerNollVo512.cs similarity index 96% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVo512.cs rename to src/Cuemon.Core/Security/FowlerNollVo512.cs index 57e73a75e..d6be422d3 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo512.cs +++ b/src/Cuemon.Core/Security/FowlerNollVo512.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides an implementation of the FVN (Fowler–Noll–Vo) non-cryptographic hashing algorithm for 512-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo64.cs b/src/Cuemon.Core/Security/FowlerNollVo64.cs similarity index 95% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVo64.cs rename to src/Cuemon.Core/Security/FowlerNollVo64.cs index cbc648267..badb8624c 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVo64.cs +++ b/src/Cuemon.Core/Security/FowlerNollVo64.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides an implementation of the FVN (Fowler–Noll–Vo) non-cryptographic hashing algorithm for 64-bit hash values. This class cannot be inherited. diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVoAlgorithm.cs b/src/Cuemon.Core/Security/FowlerNollVoAlgorithm.cs similarity index 89% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVoAlgorithm.cs rename to src/Cuemon.Core/Security/FowlerNollVoAlgorithm.cs index aaab74f95..a3dec618e 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVoAlgorithm.cs +++ b/src/Cuemon.Core/Security/FowlerNollVoAlgorithm.cs @@ -1,4 +1,4 @@ -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Defines the algorithms of the Fowler-Noll-Vo hash function. diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVoHash.cs b/src/Cuemon.Core/Security/FowlerNollVoHash.cs similarity index 90% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVoHash.cs rename to src/Cuemon.Core/Security/FowlerNollVoHash.cs index 5dbe8ecf2..bb4a720bd 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVoHash.cs +++ b/src/Cuemon.Core/Security/FowlerNollVoHash.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Represents the base class from which all implementations of the Fowler–Noll–Vo non-cryptographic hashing algorithm must derive. @@ -17,9 +17,9 @@ public abstract class FowlerNollVoHash : Hash /// The which need to be configured. protected FowlerNollVoHash(short bits, BigInteger prime, BigInteger offsetBasis, Action setup) : base(setup) { + Bits = bits; Prime = prime; OffsetBasis = offsetBasis; - Bits = BigInteger.Pow(2, bits); } /// @@ -38,7 +38,7 @@ protected FowlerNollVoHash(short bits, BigInteger prime, BigInteger offsetBasis, /// Gets the size of the implementation in bits. /// /// The size of the implementation in bits. - public BigInteger Bits { get; } + public short Bits { get; } /// /// Computes the hash value for the specified . @@ -55,7 +55,6 @@ public override HashResult ComputeHash(byte[] input) { hash ^= b; hash *= Prime; - hash %= Bits; } break; default: @@ -63,12 +62,11 @@ public override HashResult ComputeHash(byte[] input) { hash *= Prime; hash ^= b; - hash %= Bits; } break; } var result = hash.ToByteArray(); - if (Condition.IsOdd(result.Length)) { Array.Resize(ref result, result.Length - 1); } + Array.Resize(ref result, Bits / ByteUnit.BitsPerByte); result = Convertible.ReverseEndianness(result, o => o.ByteOrder = Options.ByteOrder); return new HashResult(result); } diff --git a/src/Cuemon.Core/Security/Cryptography/FowlerNollVoOptions.cs b/src/Cuemon.Core/Security/FowlerNollVoOptions.cs similarity index 97% rename from src/Cuemon.Core/Security/Cryptography/FowlerNollVoOptions.cs rename to src/Cuemon.Core/Security/FowlerNollVoOptions.cs index ade6c0ac2..fd9da5490 100644 --- a/src/Cuemon.Core/Security/Cryptography/FowlerNollVoOptions.cs +++ b/src/Cuemon.Core/Security/FowlerNollVoOptions.cs @@ -1,4 +1,4 @@ -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Configuration options for . diff --git a/src/Cuemon.Core/Security/Cryptography/Hash.cs b/src/Cuemon.Core/Security/Hash.cs similarity index 98% rename from src/Cuemon.Core/Security/Cryptography/Hash.cs rename to src/Cuemon.Core/Security/Hash.cs index 9f7e511f3..c64b10d17 100644 --- a/src/Cuemon.Core/Security/Cryptography/Hash.cs +++ b/src/Cuemon.Core/Security/Hash.cs @@ -6,7 +6,7 @@ using Cuemon.IO; using Cuemon.Text; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Represents the base class from which all implementations of hash algorithms and checksums should derive. @@ -264,7 +264,7 @@ public virtual HashResult ComputeHash(IEnumerable input) /// A containing the computed hash code of the specified . public virtual HashResult ComputeHash(Stream input) { - return ComputeHash(Disposable.SafeInvoke(() => new MemoryStream(), destination => + return ComputeHash(Patterns.SafeInvoke(() => new MemoryStream(), destination => { Decorator.Enclose(input).CopyStream(destination); return destination; diff --git a/src/Cuemon.Core/Security/Cryptography/HashFactory.cs b/src/Cuemon.Core/Security/HashFactory.cs similarity index 60% rename from src/Cuemon.Core/Security/Cryptography/HashFactory.cs rename to src/Cuemon.Core/Security/HashFactory.cs index ef8bc2ec0..e109e8b67 100644 --- a/src/Cuemon.Core/Security/Cryptography/HashFactory.cs +++ b/src/Cuemon.Core/Security/HashFactory.cs @@ -1,187 +1,12 @@ using System; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Provides access to factory methods for creating and configuring instances. /// public static class HashFactory { - /// - /// Creates an instance of a HMAC cryptographic implementation that derives from with the specified . Default is . - /// - /// The secret key for the encryption. - /// The that defines the HMAC cryptographic implementation. Default is . - /// The which may be configured. - /// A implementation of the by parameter specified . - /// - /// cannot be null. - /// - public static Hash CreateHmacCrypto(byte[] secret, KeyedCryptoAlgorithm algorithm = default, Action setup = null) - { - switch (algorithm) - { - case KeyedCryptoAlgorithm.HmacMd5: - return CreateHmacCryptoMd5(secret, setup); - case KeyedCryptoAlgorithm.HmacSha1: - return CreateHmacCryptoSha1(secret, setup); - case KeyedCryptoAlgorithm.HmacSha384: - return CreateHmacCryptoSha384(secret, setup); - case KeyedCryptoAlgorithm.HmacSha512: - return CreateHmacCryptoSha512(secret, setup); - default: - return CreateHmacCryptoSha256(secret, setup); - } - } - - /// - /// Creates an instance of . - /// - /// The secret key for encryption. The key can be any length. However, the recommended size is 128 bytes. If the key is more than 128 bytes long, it is hashed (using SHA-384) to derive a 128-byte key. If it is less than 128 bytes long, it is padded to 128 bytes. - /// The which may be configured. - /// A implementation of . - /// - /// cannot be null. - /// - public static Hash CreateHmacCryptoSha512(byte[] secret, Action setup = null) - { - Validator.ThrowIfNull(secret, nameof(secret)); - return new HmacSecureHashAlgorithm512(secret, setup); - } - - /// - /// Creates an instance of . - /// - /// The secret key for encryption. The key can be any length. However, the recommended size is 128 bytes. If the key is more than 128 bytes long, it is hashed (using SHA-384) to derive a 128-byte key. If it is less than 128 bytes long, it is padded to 128 bytes. - /// The which may be configured. - /// A implementation of . - /// - /// cannot be null. - /// - public static Hash CreateHmacCryptoSha384(byte[] secret, Action setup = null) - { - Validator.ThrowIfNull(secret, nameof(secret)); - return new HmacSecureHashAlgorithm384(secret, setup); - } - - /// - /// Creates an instance of . - /// - /// The secret key for encryption. The key can be any length. However, the recommended size is 64 bytes. If the key is more than 64 bytes long, it is hashed (using SHA-256) to derive a 64-byte key. If it is less than 64 bytes long, it is padded to 64 bytes. - /// The which may be configured. - /// A implementation of . - /// - /// cannot be null. - /// - public static Hash CreateHmacCryptoSha256(byte[] secret, Action setup = null) - { - Validator.ThrowIfNull(secret, nameof(secret)); - return new HmacSecureHashAlgorithm256(secret, setup); - } - - /// - /// Creates an instance of . - /// - /// The secret key for encryption. The key can be any length, but if it is more than 64 bytes long it will be hashed (using SHA-1) to derive a 64-byte key. Therefore, the recommended size of the secret key is 64 bytes. - /// The which may be configured. - /// A implementation of . - /// - /// cannot be null. - /// - public static Hash CreateHmacCryptoSha1(byte[] secret, Action setup = null) - { - Validator.ThrowIfNull(secret, nameof(secret)); - return new HmacSecureHashAlgorithm1(secret, setup); - } - - /// - /// Creates an instance of . - /// - /// The secret key for encryption. The key can be any length, but if it is more than 64 bytes long it will be hashed (using SHA-1) to derive a 64-byte key. Therefore, the recommended size of the secret key is 64 bytes. - /// The which may be configured. - /// A implementation of . - /// - /// cannot be null. - /// - public static Hash CreateHmacCryptoMd5(byte[] secret, Action setup = null) - { - Validator.ThrowIfNull(secret, nameof(secret)); - return new HmacMessageDigest5(secret, setup); - } - - /// - /// Creates an instance of a cryptographic implementation that derives from with the specified . Default is . - /// - /// The that defines the cryptographic implementation. Default is . - /// The which may be configured. - /// A implementation of the by parameter specified . - public static Hash CreateCrypto(CryptoAlgorithm algorithm = default, Action setup = null) - { - switch (algorithm) - { - case CryptoAlgorithm.Md5: - return CreateCryptoMd5(setup); - case CryptoAlgorithm.Sha1: - return CreateCryptoSha1(setup); - case CryptoAlgorithm.Sha384: - return CreateCryptoSha384(setup); - case CryptoAlgorithm.Sha512: - return CreateCryptoSha512(setup); - default: - return CreateCryptoSha256(setup); - } - } - - /// - /// Creates an instance of . - /// - /// The which may be configured. - /// A implementation of . - public static Hash CreateCryptoSha512(Action setup = null) - { - return new SecureHashAlgorithm512(setup); - } - - /// - /// Creates an instance of . - /// - /// The which may be configured. - /// A implementation of . - public static Hash CreateCryptoSha384(Action setup = null) - { - return new SecureHashAlgorithm384(setup); - } - - /// - /// Creates an instance of . - /// - /// The which may be configured. - /// A implementation of . - public static Hash CreateCryptoSha256(Action setup = null) - { - return new SecureHashAlgorithm256(setup); - } - - /// - /// Creates an instance of . - /// - /// The which may be configured. - /// A implementation of . - public static Hash CreateCryptoSha1(Action setup = null) - { - return new SecureHashAlgorithm1(setup); - } - - /// - /// Creates an instance of . - /// - /// The which may be configured. - /// A implementation of . - public static Hash CreateCryptoMd5(Action setup = null) - { - return new MessageDigest5(setup); - } - /// /// Creates an instance of a non-cryptographic implementation that derives from with the specified . Default is using . /// diff --git a/src/Cuemon.Core/Security/Cryptography/HashResult.cs b/src/Cuemon.Core/Security/HashResult.cs similarity index 98% rename from src/Cuemon.Core/Security/Cryptography/HashResult.cs rename to src/Cuemon.Core/Security/HashResult.cs index 91227277b..f31caf403 100644 --- a/src/Cuemon.Core/Security/Cryptography/HashResult.cs +++ b/src/Cuemon.Core/Security/HashResult.cs @@ -1,6 +1,6 @@ using System; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Represents the result of a computed checksum operation. @@ -9,7 +9,6 @@ public class HashResult : IEquatable { private readonly byte[] _input; - /// /// Initializes a new instance of the class. /// @@ -31,6 +30,7 @@ public HashResult(byte[] input) /// The copy of the original value that reflects a computed operation. public byte[] GetBytes() { + if (_input.Length == 0) { return new byte[0]; } var copy = new byte[_input.Length]; Array.Copy(_input, copy, copy.Length); return copy; diff --git a/src/Cuemon.Core/Security/Cryptography/IHash.cs b/src/Cuemon.Core/Security/IHash.cs similarity index 88% rename from src/Cuemon.Core/Security/Cryptography/IHash.cs rename to src/Cuemon.Core/Security/IHash.cs index 7eb25246c..fc2016a63 100644 --- a/src/Cuemon.Core/Security/Cryptography/IHash.cs +++ b/src/Cuemon.Core/Security/IHash.cs @@ -1,9 +1,9 @@ using System.IO; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// - /// Defines the bare minimum of cryptographic transformations. + /// Defines the bare minimum of both non-cryptographic and cryptographic transformations. /// public interface IHash { diff --git a/src/Cuemon.Core/Security/Cryptography/NonCryptoAlgorithm.cs b/src/Cuemon.Core/Security/NonCryptoAlgorithm.cs similarity index 95% rename from src/Cuemon.Core/Security/Cryptography/NonCryptoAlgorithm.cs rename to src/Cuemon.Core/Security/NonCryptoAlgorithm.cs index 165fc4134..617d7a250 100644 --- a/src/Cuemon.Core/Security/Cryptography/NonCryptoAlgorithm.cs +++ b/src/Cuemon.Core/Security/NonCryptoAlgorithm.cs @@ -1,4 +1,4 @@ -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { /// /// Specifies the different implementations of a non-cryptographic hashing algorithm. diff --git a/src/Cuemon.Core/StandardizedDateTimeFormatPattern.cs b/src/Cuemon.Core/StandardizedDateTimeFormatPattern.cs deleted file mode 100644 index 5557b9f5b..000000000 --- a/src/Cuemon.Core/StandardizedDateTimeFormatPattern.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Cuemon -{ - /// - /// Defines some standardized patterns to use when formatting date- and time values. - /// - public enum StandardizedDateTimeFormatPattern - { - /// - /// Displays a date using the ISO8601 basic date format, eg.: YYYYMMDD. - /// - Iso8601CompleteDateBasic, - /// - /// Displays a date using the ISO8601 extended date format (human readable), eg.: YYYY-MM-DD. - /// - Iso8601CompleteDateExtended, - /// - /// Displays a date using the ISO8601 basic date format in conjunction with the ISO8601 time format, eg.: YYYYMMDDThhmmssTZD. - /// - Iso8601CompleteDateTimeBasic, - /// - /// Displays a date using the ISO8601 extended date format (human readable) in conjunction with the ISO8601 extended time format (human readable), eg.: YYYY-MM-DDThh:mm:ssTZD. - /// - Iso8601CompleteDateTimeExtended - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/StringFormatter.cs b/src/Cuemon.Core/StringFormatter.cs deleted file mode 100644 index f5ca13ef5..000000000 --- a/src/Cuemon.Core/StringFormatter.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Globalization; - -namespace Cuemon -{ - /// - /// This utility class is designed to make related formating operations easier to work with. - /// - public static class StringFormatter - { - /// - /// Returns a string expression representing a standardized date- and time value. - /// - /// The value to be formatted. - /// The standardized patterns to apply on . - /// Returns a string expression representing a date- and time value. - public static string FromDateTime(DateTime value, StandardizedDateTimeFormatPattern pattern) - { - return FromDateTime(value, pattern, 0); - } - - /// - /// Returns a string expression representing a standardized date- and time value. - /// - /// The value to be formatted. - /// The standardized patterns to apply on . - /// The amount of fractional decimal places to apply to the string expression. - /// Returns a string expression representing a date- and time value. - public static string FromDateTime(DateTime value, StandardizedDateTimeFormatPattern pattern, byte fractionalDecimalPlaces) - { - switch (pattern) - { - case StandardizedDateTimeFormatPattern.Iso8601CompleteDateBasic: - return value.ToString("yyyyMMdd", CultureInfo.InvariantCulture); - case StandardizedDateTimeFormatPattern.Iso8601CompleteDateExtended: - return value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); - case StandardizedDateTimeFormatPattern.Iso8601CompleteDateTimeBasic: - return value.ToString(string.Format(CultureInfo.InvariantCulture, "yyyyMMddTHHmmss{0}{1}", fractionalDecimalPlaces == 0 ? "" : string.Format(CultureInfo.InvariantCulture, ".{0}", Generate.FixedString('f', fractionalDecimalPlaces)), value.Kind == DateTimeKind.Utc ? "Z" : "zz"), CultureInfo.InvariantCulture); - case StandardizedDateTimeFormatPattern.Iso8601CompleteDateTimeExtended: - return value.ToString(string.Format(CultureInfo.InvariantCulture, "yyyy-MM-ddTHH:mm:ss{0}{1}", fractionalDecimalPlaces == 0 ? "" : string.Format(CultureInfo.InvariantCulture, ".{0}", Generate.FixedString('f', fractionalDecimalPlaces)), value.Kind == DateTimeKind.Utc ? "Z" : "zz"), CultureInfo.InvariantCulture); - default: - throw new ArgumentOutOfRangeException(nameof(pattern)); - } - } - - /// - /// Returns a string expression representing a standardized date- and time value. - /// - /// The value to be formatted. - /// The standardized patterns to apply on . - /// Returns a string expression representing a date- and time value. - public static string FromDateTime(DateTime value, DateTimeFormatPattern pattern) - { - return FromDateTime(value, pattern, CultureInfo.InvariantCulture); - } - - /// - /// Returns a string expression representing a standardized date- and time value. - /// - /// The value to be formatted. - /// The standardized patterns to apply on . - /// An that supplies culture-specific formatting information. - /// Returns a string expression representing a date- and time value. - public static string FromDateTime(DateTime value, DateTimeFormatPattern pattern, IFormatProvider provider) - { - var formatInfo = DateTimeFormatInfo.GetInstance(provider); - switch (pattern) - { - case DateTimeFormatPattern.LongDate: - return value.ToString(formatInfo.LongDatePattern, formatInfo); - case DateTimeFormatPattern.LongDateTime: - return value.ToString(string.Format(formatInfo, "{0} {1}", formatInfo.LongDatePattern, formatInfo.LongTimePattern), formatInfo); - case DateTimeFormatPattern.LongTime: - return value.ToString(formatInfo.LongTimePattern, formatInfo); - case DateTimeFormatPattern.ShortDate: - return value.ToString(formatInfo.ShortDatePattern, formatInfo); - case DateTimeFormatPattern.ShortDateTime: - return value.ToString(string.Format(formatInfo, "{0} {1}", formatInfo.ShortDatePattern, formatInfo.ShortTimePattern), formatInfo); - case DateTimeFormatPattern.ShortTime: - return value.ToString(formatInfo.ShortTimePattern, formatInfo); - default: - throw new ArgumentOutOfRangeException(nameof(pattern)); - } - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/TaskActionFactory.cs b/src/Cuemon.Core/TaskActionFactory.cs index 302135a45..2de8ef77e 100644 --- a/src/Cuemon.Core/TaskActionFactory.cs +++ b/src/Cuemon.Core/TaskActionFactory.cs @@ -448,9 +448,16 @@ internal TaskActionFactory(Func method, TTuple /// /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous operation. + /// + /// No delegate was specified on the factory. + /// + /// + /// The was canceled. + /// public Task ExecuteMethodAsync(CancellationToken ct) { ThrowIfNoValidDelegate(Condition.IsNull(Method)); + ct.ThrowIfCancellationRequested(); return Method.Invoke(GenericArguments, ct); } @@ -459,7 +466,7 @@ public Task ExecuteMethodAsync(CancellationToken ct) /// /// A new that is a copy of this instance. /// When thread safety is required this is the method to invoke. - public TaskActionFactory Clone() + public override TemplateFactory Clone() { return new TaskActionFactory(Method, GenericArguments.Clone() as TTuple); } diff --git a/src/Cuemon.Core/TaskFuncFactory.cs b/src/Cuemon.Core/TaskFuncFactory.cs index 70ac1166f..f55959350 100644 --- a/src/Cuemon.Core/TaskFuncFactory.cs +++ b/src/Cuemon.Core/TaskFuncFactory.cs @@ -465,9 +465,16 @@ internal TaskFuncFactory(Func> method, /// /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous operation. The task result contains the return value of the function delegate associated with this instance. + /// + /// No delegate was specified on the factory. + /// + /// + /// The was canceled. + /// public Task ExecuteMethodAsync(CancellationToken ct) { ThrowIfNoValidDelegate(Condition.IsNull(Method)); + ct.ThrowIfCancellationRequested(); return Method.Invoke(GenericArguments, ct); } @@ -476,7 +483,7 @@ public Task ExecuteMethodAsync(CancellationToken ct) /// /// A new that is a copy of this instance. /// When thread safety is required this is the method to invoke. - public TaskFuncFactory Clone() + public override TemplateFactory Clone() { return new TaskFuncFactory(Method, GenericArguments.Clone() as TTuple); } diff --git a/src/Cuemon.Core/Template.cs b/src/Cuemon.Core/Template.cs index cb3eff0c7..b47177f0e 100644 --- a/src/Cuemon.Core/Template.cs +++ b/src/Cuemon.Core/Template.cs @@ -1,5 +1,4 @@ -using System; -using Cuemon.Collections.Generic; +using Cuemon.Collections.Generic; namespace Cuemon { diff --git a/src/Cuemon.Core/TemplateFactory.cs b/src/Cuemon.Core/TemplateFactory.cs index 043a4fc99..94504d941 100644 --- a/src/Cuemon.Core/TemplateFactory.cs +++ b/src/Cuemon.Core/TemplateFactory.cs @@ -58,9 +58,19 @@ public override string ToString() /// Validates and throws an if this instance has no valid delegate. /// /// The value of a condition that can be either true or false. + /// + /// No delegate was specified on the factory. + /// protected void ThrowIfNoValidDelegate(bool delegateIsNull) { if (!HasDelegate) { throw new InvalidOperationException(delegateIsNull ? "There is no delegate specified on the factory." : FormattableString.Invariant($"There is a delegate specified on the factory, '{Decorator.Enclose(GetType()).ToFriendlyName(o => o.FullName = true)}', but it leads to a null referenced delegate wrapper.")); } } + + /// + /// Creates a shallow copy of the current object. + /// + /// A new implementation that is a copy of this instance. + /// When thread safety is required this is the method to invoke. + public abstract TemplateFactory Clone(); } } \ No newline at end of file diff --git a/src/Cuemon.Core/TesterFuncFactory.cs b/src/Cuemon.Core/TesterFuncFactory.cs index 29e6d8170..68cb80c84 100644 --- a/src/Cuemon.Core/TesterFuncFactory.cs +++ b/src/Cuemon.Core/TesterFuncFactory.cs @@ -563,7 +563,7 @@ public virtual TSuccess ExecuteMethod(out TResult result) /// /// A new that is a copy of this instance. /// When thread safety is required this is the method to invoke. - public TesterFuncFactory Clone() + public override TemplateFactory Clone() { return new TesterFuncFactory(Method, GenericArguments.Clone() as TTuple); } diff --git a/src/Cuemon.Core/Text/ByteOrderMark.cs b/src/Cuemon.Core/Text/ByteOrderMark.cs index b8e07f406..07724219e 100644 --- a/src/Cuemon.Core/Text/ByteOrderMark.cs +++ b/src/Cuemon.Core/Text/ByteOrderMark.cs @@ -156,9 +156,9 @@ public static Stream Remove(Stream value, Encoding encoding, Action o.LeaveOpen = option.LeaveOpen); + var bytes = Decorator.Enclose(value).InvokeToByteArray(leaveOpen: option.LeaveOpen); bytes = Remove(bytes, encoding); - return Disposable.SafeInvoke(() => new MemoryStream(bytes.Length), ms => + return Patterns.SafeInvoke(() => new MemoryStream(bytes.Length), ms => { ms.Write(bytes, 0, bytes.Length); ms.Position = 0; diff --git a/src/Cuemon.Core/Text/EncodingOptions.cs b/src/Cuemon.Core/Text/EncodingOptions.cs index 7cc55b22e..af3d9ff36 100644 --- a/src/Cuemon.Core/Text/EncodingOptions.cs +++ b/src/Cuemon.Core/Text/EncodingOptions.cs @@ -1,13 +1,12 @@ using System; using System.Text; -using Cuemon.Threading; namespace Cuemon.Text { /// /// Configuration options related to . /// - public class EncodingOptions : AsyncOptions, IEncodingOptions + public class EncodingOptions : IEncodingOptions { private Encoding _encoding; diff --git a/src/Cuemon.Core/Text/IConfigurableParser.cs b/src/Cuemon.Core/Text/IConfigurableParser.cs index 912ffa723..f0060087d 100644 --- a/src/Cuemon.Core/Text/IConfigurableParser.cs +++ b/src/Cuemon.Core/Text/IConfigurableParser.cs @@ -4,7 +4,6 @@ namespace Cuemon.Text { /// /// Defines methods that converts a to an of a particular type. - /// Implements the /// /// The type of the delegate setup. /// diff --git a/src/Cuemon.Core/Text/Stem.cs b/src/Cuemon.Core/Text/Stem.cs index 3a8b128e6..89b1f68da 100644 --- a/src/Cuemon.Core/Text/Stem.cs +++ b/src/Cuemon.Core/Text/Stem.cs @@ -3,7 +3,7 @@ namespace Cuemon.Text { /// - /// Provides a way to support assigning an stem to a value. + /// Provides a way to support assigning a stem to a value. /// public sealed class Stem { diff --git a/src/Cuemon.Core/Validator.cs b/src/Cuemon.Core/Validator.cs index 391f8b92e..c4a785992 100644 --- a/src/Cuemon.Core/Validator.cs +++ b/src/Cuemon.Core/Validator.cs @@ -15,10 +15,10 @@ public sealed class Validator private static readonly Validator ExtendedValidator = new Validator(); /// - /// Gets the singleton instance of the Validator functionality allowing for extensions methods like: Validator.Throw.IfNotValidJsonDocument(). + /// Gets the singleton instance of the Validator functionality allowing for extensions methods like: Validator.ThrowIf.InvalidJsonDocument(). /// /// The singleton instance of the Validator functionality. - public static Validator Throw { get; } = ExtendedValidator; + public static Validator ThrowIf { get; } = ExtendedValidator; /// /// Validates and throws an (or a derived counterpart) from the specified delegate . @@ -235,6 +235,48 @@ public static void ThrowIfFalse(bool value, string paramName, string message = " } } + /// + /// Validates and throws an if the specified returns true. + /// + /// The function delegate that determines if an is thrown. + /// The name of the parameter that caused the exception. + /// A message that describes the error. + /// + /// returned true. + /// + public static void ThrowIfTrue(Func predicate, string paramName, string message) + { + try + { + ThrowWhen(c => c.IsTrue(predicate).Create(() => new ArgumentException(message, paramName)).TryThrow()); + } + catch (ArgumentException ex) + { + throw ExceptionInsights.Embed(ex, MethodBase.GetCurrentMethod(), Arguments.ToArray(predicate, paramName, message)); + } + } + + /// + /// Validates and throws an if the specified returns false. + /// + /// The function delegate that determines if an is thrown. + /// The name of the parameter that caused the exception. + /// A message that describes the error. + /// + /// returned false. + /// + public static void ThrowIfFalse(Func predicate, string paramName, string message) + { + try + { + ThrowWhen(c => c.IsFalse(predicate).Create(() => new ArgumentException(message, paramName)).TryThrow()); + } + catch (ArgumentException ex) + { + throw ExceptionInsights.Embed(ex, MethodBase.GetCurrentMethod(), Arguments.ToArray(predicate, paramName, message)); + } + } + /// /// Validates and throws an if the specified has no elements. /// @@ -268,14 +310,14 @@ public static void ThrowIfSequenceEmpty(IEnumerable value, string paramNam /// /// contains no elements. /// - public static void ThrowIfSequenceNullOrEmpty(IEnumerable value, string paramName, string message = "Value contains no elements.") + public static void ThrowIfSequenceNullOrEmpty(IEnumerable value, string paramName, string message = "Value is either null or contains no elements.") { try { - ThrowIfNull(value, paramName); - ThrowIfSequenceEmpty(value, paramName); + ThrowIfNull(value, paramName, message); + ThrowIfSequenceEmpty(value, paramName, message); } - catch (ArgumentException ex) + catch (Exception ex) when (ex is ArgumentException) { throw ExceptionInsights.Embed(ex, MethodBase.GetCurrentMethod(), Arguments.ToArray(value, paramName, message)); } diff --git a/src/Cuemon.Core/Wrapper.cs b/src/Cuemon.Core/Wrapper.cs index 4098c0f6e..0f33248f6 100644 --- a/src/Cuemon.Core/Wrapper.cs +++ b/src/Cuemon.Core/Wrapper.cs @@ -193,7 +193,17 @@ public TResult InstanceAs() /// public TResult InstanceAs(IFormatProvider provider) { - return (TResult)Decorator.Enclose(Instance).ChangeType(InstanceType, o => o.FormatProvider = provider); + var presult = Decorator.Enclose(Instance).ChangeType(InstanceType, o => o.FormatProvider = provider); + return Decorator.Enclose(presult).ChangeTypeOrDefault(); + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return Wrapper.ParseInstance(this); } #endregion } diff --git a/src/Cuemon.Data.Integrity/CacheValidator.cs b/src/Cuemon.Data.Integrity/CacheValidator.cs index 454008687..c3beb53bf 100644 --- a/src/Cuemon.Data.Integrity/CacheValidator.cs +++ b/src/Cuemon.Data.Integrity/CacheValidator.cs @@ -4,20 +4,37 @@ using System.Linq; using System.Reflection; using Cuemon.Collections.Generic; +using Cuemon.Security; namespace Cuemon.Data.Integrity { /// /// Provides a way to represent cacheable data-centric content that can be validated by cache-aware applications. /// - public class CacheValidator : ChecksumBuilder, IEntityDataTimestamp + public class CacheValidator : ChecksumBuilder, IEntityInfo { private const long NullOrZeroLengthChecksum = 23719; - private static readonly CacheValidator DefaultCacheValidatorValue = new CacheValidator(DateTime.MinValue, DateTime.MinValue); + private static readonly CacheValidator DefaultCacheValidatorValue = new CacheValidator(new EntityInfo(DateTime.MinValue, DateTime.MinValue), () => Security.HashFactory.CreateFnv128()); private static CacheValidator _referencePointCacheValidator; private static Assembly _assemblyValue; private static readonly Lazy LazyAssembly = new Lazy(() => Assembly.GetEntryAssembly() ?? typeof(ChecksumBuilder).GetTypeInfo().Assembly); + /// + /// Gets the most significant object from the most significant (largest) value of either or in the specified . + /// + /// A sequence of objects to parse for the most significant (largest) value of either or . + /// The most significant object from the specified . + public static CacheValidator GetMostSignificant(params CacheValidator[] sequence) + { + Validator.ThrowIfNull(sequence, nameof(sequence)); + var mostSignificant = Default; + foreach (var candidate in sequence) + { + if (candidate.GetMostSignificant().Ticks > mostSignificant.GetMostSignificant().Ticks) { mostSignificant = candidate; } + } + return mostSignificant; + } + /// /// Gets or sets the that will serve as the ideal candidate for a reference point. Default is with a fallback to Cuemon.Core.dll. /// @@ -36,180 +53,64 @@ public static Assembly AssemblyReference } } - private CacheValidator() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// The which need to be configured. - public CacheValidator(DateTime created, Action setup = null) - : this(created, created, setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, Action setup = null) - : this(created, modified, (byte[])null, setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, double checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, short checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) - { - } - - - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, string checksum, Action setup = null) - : this(created, modified, Generate.HashCode64(checksum), setup) - { - } - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, int checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, long checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, float checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) - { - } - - /// - /// Initializes a new instance of the class. + /// Gets a object that is initialized to a default representation that should be considered invalid for usage beyond this check. /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, ushort checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) - { - } + /// A object that is initialized to a default representation. + public static CacheValidator Default => DefaultCacheValidatorValue.Clone(); /// - /// Initializes a new instance of the class. + /// Gets a object that represents an reference point. /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, uint checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) + /// A object that represents an reference point. + public static CacheValidator ReferencePoint { + get + { + if (_referencePointCacheValidator == null) + { + _referencePointCacheValidator = CacheValidatorFactory.CreateValidator(AssemblyReference); + } + return _referencePointCacheValidator.Clone(); + } } - /// - /// Initializes a new instance of the class. - /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// A value containing a byte-for-byte checksum of the data this instance represents. - /// The which need to be configured. - public CacheValidator(DateTime created, DateTime? modified, ulong checksum, Action setup = null) - : this(created, modified, Convertible.GetBytes(checksum), setup) + private CacheValidator(Func hashFactory) : base(hashFactory) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// A value for when data this instance represents was first created. - /// A value for when data this instance represents was last modified. - /// An array of bytes containing a checksum of the data this instance represents. - /// The which may be configured. - public CacheValidator(DateTime created, DateTime? modified, byte[] checksum, Action setup = null) + /// An object that representing the meta-data of an entity. + /// The function delegate that is invoked to produce the . + /// A enumeration value that indicates how a checksum is manipulated. Default is . + /// method + public CacheValidator(EntityInfo entity, Func hashFactory, EntityDataIntegrityMethod method = EntityDataIntegrityMethod.Unaltered) : base(entity?.Checksum.GetBytes(), hashFactory) { - var options = Patterns.Configure(setup); - var isChecksumNullOrZeroLength = (checksum == null || checksum.Length == 0); + Validator.ThrowIfNull(entity, nameof(entity)); + + Created = entity.Created; + Modified = entity.Modified; + Validation = entity.Validation; + Method = method; - Created = created.ToUniversalTime(); - Modified = modified?.ToUniversalTime(); - - var strength = isChecksumNullOrZeroLength ? EntityDataIntegrityStrength.Unspecified : EntityDataIntegrityStrength.Strong; - switch (options.Method) + switch (method) { case EntityDataIntegrityMethod.Unaltered: break; case EntityDataIntegrityMethod.Timestamp: - checksum = Convertible.GetBytes(Created.Ticks ^ Modified?.Ticks ?? DateTime.MinValue.Ticks); - strength = EntityDataIntegrityStrength.Weak; + Bytes = new List(Convertible.GetBytes(Created.Ticks ^ Modified?.Ticks ?? DateTime.MinValue.Ticks)); break; case EntityDataIntegrityMethod.Combined: - var checksumValue = isChecksumNullOrZeroLength ? NullOrZeroLengthChecksum : Generate.HashCode64(checksum.Cast()); - checksum = Convertible.GetBytes(Created.Ticks ^ Modified?.Ticks ?? DateTime.MinValue.Ticks ^ checksumValue); - strength = isChecksumNullOrZeroLength ? EntityDataIntegrityStrength.Weak : EntityDataIntegrityStrength.Strong; + var checksumValue = entity.Checksum.HasValue ? Generate.HashCode64(Bytes.Cast()) : NullOrZeroLengthChecksum; + Bytes = new List(Convertible.GetBytes(Created.Ticks ^ Modified?.Ticks ?? DateTime.MinValue.Ticks ^ checksumValue)); break; default: - throw new InvalidEnumArgumentException(nameof(setup), (int)options.Method, typeof(EntityDataIntegrityMethod)); + throw new InvalidEnumArgumentException(nameof(method), (int)method, typeof(EntityDataIntegrityMethod)); } - Bytes = checksum == null ? new List() : new List(checksum); - Strength = strength; - Method = options.Method; - Options = options; - Algorithm = options.Algorithm; + } - - private CacheValidatorOptions Options { get; set; } /// /// Gets a value from when data this instance represents was first created, expressed as the Coordinated Universal Time (UTC). @@ -223,28 +124,6 @@ public CacheValidator(DateTime created, DateTime? modified, byte[] checksum, Act /// A value from when data this instance represents was last modified, expressed as the Coordinated Universal Time (UTC). public DateTime? Modified { get; private set; } - /// - /// Gets a object that is initialized to a default representation that should be considered invalid for usage beyond this check. - /// - /// A object that is initialized to a default representation. - public static CacheValidator Default => DefaultCacheValidatorValue.Clone(); - - /// - /// Gets a object that represents an reference point. - /// - /// A object that represents an reference point. - public static CacheValidator ReferencePoint - { - get - { - if (_referencePointCacheValidator == null) - { - _referencePointCacheValidator = CacheValidatorFactory.CreateValidator(AssemblyReference); - } - return _referencePointCacheValidator.Clone(); - } - } - /// /// Gets an enumeration value of indicating the usage method of this instance. /// @@ -252,10 +131,22 @@ public static CacheValidator ReferencePoint public EntityDataIntegrityMethod Method { get; private set; } /// - /// Gets an enumeration value of indicating the strength of this instance. + /// Gets an enumeration value of indicating the strength of this instance. /// - /// One of the enumeration values of that specifies the strength of this instance. - public EntityDataIntegrityStrength Strength { get; private set; } + /// One of the enumeration values of that specifies the strength of this instance. + public EntityDataIntegrityValidation Validation { get; private set; } + + /// + /// Combines the to the representation of this instance. + /// + /// A containing a checksum of the additional data this instance must represent. + /// A reference to this instance after the operation has completed. + public override ChecksumBuilder CombineWith(byte[] additionalChecksum) + { + var isChecksumNullOrZeroLength = (additionalChecksum == null || additionalChecksum.Length == 0); + if (isChecksumNullOrZeroLength) { Validation = EntityDataIntegrityValidation.Strong; } + return base.CombineWith(additionalChecksum); + } /// /// Creates a shallow copy of the current object. @@ -263,13 +154,12 @@ public static CacheValidator ReferencePoint /// A new that is a copy of this instance. public virtual CacheValidator Clone() { - return new CacheValidator() + return new CacheValidator(HashFactory) { - Options = Options, Method = Method, Modified = Modified, Created = Created, - Strength = Strength, + Validation = Validation, Bytes = Bytes.ToList(), ComputedHash = ComputedHash }; @@ -283,21 +173,5 @@ public DateTime GetMostSignificant() { return Arguments.ToEnumerableOf(Created, Modified ?? DateTime.MinValue).Max(); } - - /// - /// Gets the most significant object from the most significant (largest) value of either or in the specified . - /// - /// A sequence of objects to parse for the most significant (largest) value of either or . - /// The most significant object from the specified . - public static CacheValidator GetMostSignificant(params CacheValidator[] sequence) - { - Validator.ThrowIfNull(sequence, nameof(sequence)); - var mostSignificant = Default; - foreach (var candidate in sequence) - { - if (candidate.GetMostSignificant().Ticks > mostSignificant.GetMostSignificant().Ticks) { mostSignificant = candidate; } - } - return mostSignificant; - } } } \ No newline at end of file diff --git a/src/Cuemon.Data.Integrity/CacheValidatorFactory.cs b/src/Cuemon.Data.Integrity/CacheValidatorFactory.cs index 36fcf2969..ead129999 100644 --- a/src/Cuemon.Data.Integrity/CacheValidatorFactory.cs +++ b/src/Cuemon.Data.Integrity/CacheValidatorFactory.cs @@ -1,7 +1,7 @@ using System; using System.IO; -using System.Linq; using System.Reflection; +using Cuemon.Security; namespace Cuemon.Data.Integrity { @@ -14,14 +14,16 @@ public static class CacheValidatorFactory /// Creates and returns an instance of from the specified . /// /// The to convert. + /// The function delegate that is invoked to produce the . Default is . /// The which may be configured. /// A that represents the . /// /// cannot be null. /// - public static CacheValidator CreateValidator(FileInfo file, Action setup = null) + public static CacheValidator CreateValidator(FileInfo file, Func hashFactory = null, Action setup = null) { Validator.ThrowIfNull(file, nameof(file)); + if (hashFactory == null) { hashFactory = () => HashFactory.CreateFnv128(); } var options = Patterns.Configure(setup); return DataIntegrityFactory.CreateIntegrity(file, fio => { @@ -30,18 +32,10 @@ public static CacheValidator CreateValidator(FileInfo file, Action 0) { - return new CacheValidator(fi.CreationTimeUtc, fi.LastWriteTimeUtc, Generate.HashCode64(checksumBytes.Cast()), o => - { - o.Method = options.Method; - o.Algorithm = options.Algorithm; - }); + return new CacheValidator(new EntityInfo(fi.CreationTimeUtc, fi.LastWriteTimeUtc, checksumBytes, EntityDataIntegrityValidation.Strong), hashFactory); } var fileNameHashCode64 = Generate.HashCode64(file.FullName); - return new CacheValidator(fi.CreationTimeUtc, fi.LastWriteTimeUtc, fileNameHashCode64, o => - { - o.Method = options.Method; - o.Algorithm = options.Algorithm; - }); + return new CacheValidator(new EntityInfo(fi.CreationTimeUtc, fi.LastWriteTimeUtc, Convertible.GetBytes(fileNameHashCode64)), hashFactory, options.Method); }; }) as CacheValidator; } @@ -50,15 +44,22 @@ public static CacheValidator CreateValidator(FileInfo file, Action from the specified . /// /// The to convert. + /// The function delegate that is invoked to produce the . Default is . /// The which may be configured. /// A that represents the . - public static CacheValidator CreateValidator(Assembly assembly, Action setup = null) + /// + /// cannot be null. + /// + public static CacheValidator CreateValidator(Assembly assembly, Func hashFactory = null, Action setup = null) { + Validator.ThrowIfNull(assembly, nameof(assembly)); + if (hashFactory == null) { hashFactory = () => HashFactory.CreateFnv128(); } + var options = Patterns.Configure(setup); var assemblyHashCode64 = Generate.HashCode64(assembly.FullName); var assemblyLocation = assembly.Location; return assembly.IsDynamic - ? new CacheValidator(DateTime.MinValue, DateTime.MaxValue, assemblyHashCode64, Patterns.ConfigureExchange(setup)) - : CreateValidator(new FileInfo(assemblyLocation), setup); + ? new CacheValidator(new EntityInfo(DateTime.MinValue, DateTime.MaxValue, Convertible.GetBytes(assemblyHashCode64)), hashFactory, options.Method) + : CreateValidator(new FileInfo(assemblyLocation), hashFactory, setup); } } } \ No newline at end of file diff --git a/src/Cuemon.Data.Integrity/CacheValidatorOptions.cs b/src/Cuemon.Data.Integrity/CacheValidatorOptions.cs deleted file mode 100644 index 8821fd008..000000000 --- a/src/Cuemon.Data.Integrity/CacheValidatorOptions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Cuemon.Integrity; -using Cuemon.Security.Cryptography; - -namespace Cuemon.Data.Integrity -{ - /// - /// Configuration options for . - /// - public class CacheValidatorOptions : ChecksumBuilderOptions - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The following table shows the initial property values for an instance of . - /// - /// - /// Property - /// Initial Value - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public CacheValidatorOptions() - { - Algorithm = CryptoAlgorithm.Md5; - Method = EntityDataIntegrityMethod.Unaltered; - } - - /// - /// Gets an enumeration value of indicating how a checksum is generated. - /// - /// One of the enumeration values of that indicates how a checksum is generated. - public EntityDataIntegrityMethod Method { get; set; } - } -} \ No newline at end of file diff --git a/src/Cuemon.Data.Integrity/ChecksumBuilder.cs b/src/Cuemon.Data.Integrity/ChecksumBuilder.cs new file mode 100644 index 000000000..0a0d62e32 --- /dev/null +++ b/src/Cuemon.Data.Integrity/ChecksumBuilder.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using Cuemon.Security; + +namespace Cuemon.Data.Integrity +{ + /// + /// Provides a way to fluently represent checksum values of arbitrary data. + /// + public class ChecksumBuilder : IDataIntegrity, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The function delegate that is invoked to produce the . + public ChecksumBuilder(Func hashFactory) : this(null, hashFactory) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A containing a checksum of the data this instance represents. + /// The function delegate that is invoked to produce the . + public ChecksumBuilder(byte[] checksum, Func hashFactory) + { + Validator.ThrowIfNull(hashFactory, nameof(hashFactory)); + Bytes = checksum == null ? new List() : new List(checksum); + HashFactory = hashFactory; + } + + /// + /// Gets the value factory of this instance. + /// + /// The value factory of this instance. + protected Func HashFactory { get; } + + /// + /// Gets a byte array that is the result of the associated . + /// + /// The byte array that is the result of the associated . + protected List Bytes { get; set; } + + /// + /// Gets a containing a computed hash value of the data this instance represents. + /// + /// A containing a computed hash value of the data this instance represents. + public HashResult Checksum => ComputedHash ?? (ComputedHash = HashFactory.Invoke().ComputeHash(Bytes.ToArray())); + + /// + /// Gets or sets the computed checksum of . + /// + /// The computed checksum of . + protected HashResult ComputedHash { get; set; } + + /// + /// Combines the to the representation of this instance. + /// + /// A containing a checksum of the additional data this instance must represent. + /// A reference to this instance after the operation has completed. + public virtual ChecksumBuilder CombineWith(byte[] additionalChecksum) + { + ComputedHash = null; + Bytes.AddRange(additionalChecksum); + return this; + } + + /// + /// Returns a hash code for this instance. + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + public override int GetHashCode() + { + return Generate.HashCode32(Checksum.ToHexadecimalString()); + } + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with the current . + /// true if the specified is equal to this instance; otherwise, false. + public override bool Equals(object obj) + { + if (!(obj is ChecksumBuilder builder)) { return false; } + return Equals(builder); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// true if the current object is equal to the other parameter; otherwise, false. + public virtual bool Equals(ChecksumBuilder other) + { + if (other == null) { return false; } + return Checksum.GetHashCode() == other.Checksum.GetHashCode(); + } + + /// + /// Converts the the of this instance to its equivalent hexadecimal representation. + /// + /// A hexadecimal representation of this instance. + public override string ToString() + { + return Checksum.ToHexadecimalString(); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Data.Integrity/Cuemon.Data.Integrity.csproj b/src/Cuemon.Data.Integrity/Cuemon.Data.Integrity.csproj index 6f8c28e18..bf07a6fd6 100644 --- a/src/Cuemon.Data.Integrity/Cuemon.Data.Integrity.csproj +++ b/src/Cuemon.Data.Integrity/Cuemon.Data.Integrity.csproj @@ -1,26 +1,15 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 130bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Data.Integrity Cuemon.Data.Integrity - The Cuemon.Data.Integrity assembly provides access to abstractions related to the System.Xml namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Data.Integrity namespace contains types that provide ways for developers to determine and maintain integrity of data that is normally associated with an entity/resource. + cache-validator checksum-builder entity-info data-integrity diff --git a/src/Cuemon.Core/Data/Integrity/EntityDataIntegrityMethod.cs b/src/Cuemon.Data.Integrity/EntityDataIntegrityMethod.cs similarity index 100% rename from src/Cuemon.Core/Data/Integrity/EntityDataIntegrityMethod.cs rename to src/Cuemon.Data.Integrity/EntityDataIntegrityMethod.cs diff --git a/src/Cuemon.Core/Data/Integrity/EntityDataIntegrityStrength.cs b/src/Cuemon.Data.Integrity/EntityDataIntegrityValidation.cs similarity index 92% rename from src/Cuemon.Core/Data/Integrity/EntityDataIntegrityStrength.cs rename to src/Cuemon.Data.Integrity/EntityDataIntegrityValidation.cs index 605cc63b7..ae0afcfab 100644 --- a/src/Cuemon.Core/Data/Integrity/EntityDataIntegrityStrength.cs +++ b/src/Cuemon.Data.Integrity/EntityDataIntegrityValidation.cs @@ -3,7 +3,7 @@ /// /// Specifies the validation strength of a data checksum. /// - public enum EntityDataIntegrityStrength + public enum EntityDataIntegrityValidation { /// /// Indicates that no checksum strength was specified. diff --git a/src/Cuemon.Data.Integrity/EntityInfo.cs b/src/Cuemon.Data.Integrity/EntityInfo.cs new file mode 100644 index 000000000..6a2bf45b4 --- /dev/null +++ b/src/Cuemon.Data.Integrity/EntityInfo.cs @@ -0,0 +1,69 @@ +using System; +using Cuemon.Security; + +namespace Cuemon.Data.Integrity +{ + /// + /// Represents the metadata information normally associated with an entity/resource. + /// Implements the + /// + /// + public class EntityInfo : IEntityInfo + { + /// + /// Initializes a new instance of the class. + /// + /// A value for when data this instance represents was first created. + public EntityInfo(DateTime created) : this(created, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A value for when data this instance represents was first created. + /// A value for when data this instance represents was last modified. + public EntityInfo(DateTime created, DateTime? modified) : this(created, modified, null, EntityDataIntegrityValidation.Unspecified) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A value for when data this instance represents was first created. + /// A value for when data this instance represents was last modified. + /// A containing a checksum of the data this instance represents. + /// A enumeration value that indicates the validation strength of the specified . Default is . + public EntityInfo(DateTime created, DateTime? modified, byte[] checksum, EntityDataIntegrityValidation validation = EntityDataIntegrityValidation.Weak) + { + Created = created.ToUniversalTime(); + Modified = modified?.ToUniversalTime(); + Checksum = new HashResult(checksum); + Validation = validation; + } + + /// + /// Gets a value from when data this resource represents was first created, expressed as the Coordinated Universal Time (UTC). + /// + /// The timestamp from when data this resource represents was first created. + public DateTime Created { get; } + + /// + /// Gets a value from when data this resource represents was last modified, expressed as the Coordinated Universal Time (UTC). + /// + /// The timestamp from when data this resource represents was last modified. + public DateTime? Modified { get; } + + /// + /// Gets a that represents the integrity of this instance. + /// + /// The checksum that represents the integrity of this instance. + public HashResult Checksum { get; } + + /// + /// Gets the validation strength of the integrity of this resource. + /// + /// The validation strength of the integrity of this resource. + public EntityDataIntegrityValidation Validation { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Extensions/Data/Integrity/ChecksumBuilderDecoratorExtensions.cs b/src/Cuemon.Data.Integrity/Extensions/ChecksumBuilderDecoratorExtensions.cs similarity index 65% rename from src/Cuemon.Core/Extensions/Data/Integrity/ChecksumBuilderDecoratorExtensions.cs rename to src/Cuemon.Data.Integrity/Extensions/ChecksumBuilderDecoratorExtensions.cs index 10945d500..bdb877c73 100644 --- a/src/Cuemon.Core/Extensions/Data/Integrity/ChecksumBuilderDecoratorExtensions.cs +++ b/src/Cuemon.Data.Integrity/Extensions/ChecksumBuilderDecoratorExtensions.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Cuemon.Data.Integrity { @@ -16,14 +14,14 @@ public static class ChecksumBuilderDecoratorExtensions /// /// The type of the . /// The to extend. - /// A array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params double[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, double additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -31,14 +29,14 @@ public static T CombineWith(this IDecorator decorator, params double[] add /// /// The type of the . /// The to extend. - /// An array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params short[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, short additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -46,19 +44,14 @@ public static T CombineWith(this IDecorator decorator, params short[] addi /// /// The type of the . /// The to extend. - /// A array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params string[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, string additionalChecksum) where T : ChecksumBuilder { - var result = new List(); - for (int i = 0; i < additionalChecksum.Length; i++) - { - result.Add(Generate.HashCode64(additionalChecksum[i])); - } - return CombineWith(decorator, result.ToArray()); + return CombineWith(decorator, Generate.HashCode64(additionalChecksum)); } /// @@ -66,14 +59,14 @@ public static T CombineWith(this IDecorator decorator, params string[] add /// /// The type of the . /// The to extend. - /// An array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params int[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, int additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -81,14 +74,14 @@ public static T CombineWith(this IDecorator decorator, params int[] additi /// /// The type of the . /// The to extend. - /// An array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params long[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, long additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -96,14 +89,14 @@ public static T CombineWith(this IDecorator decorator, params long[] addit /// /// The type of the . /// The to extend. - /// A array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params float[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, float additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -111,14 +104,14 @@ public static T CombineWith(this IDecorator decorator, params float[] addi /// /// The type of the . /// The to extend. - /// An array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params ushort[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, ushort additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -126,14 +119,14 @@ public static T CombineWith(this IDecorator decorator, params ushort[] add /// /// The type of the . /// The to extend. - /// An array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params uint[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, uint additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -141,14 +134,14 @@ public static T CombineWith(this IDecorator decorator, params uint[] addit /// /// The type of the . /// The to extend. - /// An array that contains zero or more checksum of the additional data the enclosed of the must represent. + /// A value containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params ulong[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, ulong additionalChecksum) where T : ChecksumBuilder { - return CombineWith(decorator, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return CombineWith(decorator, Convertible.GetBytes(additionalChecksum)); } /// @@ -156,17 +149,17 @@ public static T CombineWith(this IDecorator decorator, params ulong[] addi /// /// The type of the . /// The to extend. - /// An array of bytes containing a checksum of the additional data the enclosed of the must represent. + /// A containing a checksum of the additional data the enclosed of the must represent. /// An updated instance of the enclosed of the . /// /// cannot be null. /// - public static T CombineWith(this IDecorator decorator, params byte[] additionalChecksum) where T : ChecksumBuilder + public static T CombineWith(this IDecorator decorator, byte[] additionalChecksum) where T : ChecksumBuilder { Validator.ThrowIfNull(decorator, nameof(decorator)); if (additionalChecksum == null) { return decorator.Inner; } if (additionalChecksum.Length == 0) { return decorator.Inner; } - decorator.Inner.AppendChecksum(additionalChecksum); + decorator.Inner.CombineWith(additionalChecksum); return decorator.Inner; } } diff --git a/src/Cuemon.Data.Integrity/FileChecksumOptions.cs b/src/Cuemon.Data.Integrity/FileChecksumOptions.cs index 7d8946b92..9f4b8eaf3 100644 --- a/src/Cuemon.Data.Integrity/FileChecksumOptions.cs +++ b/src/Cuemon.Data.Integrity/FileChecksumOptions.cs @@ -1,6 +1,5 @@ using System.IO; using Cuemon.IO; -using Cuemon.Security.Cryptography; namespace Cuemon.Data.Integrity { @@ -20,10 +19,6 @@ public class FileChecksumOptions : FileInfoOptions /// Initial Value /// /// - /// - /// - /// - /// /// /// /// @@ -31,15 +26,9 @@ public class FileChecksumOptions : FileInfoOptions /// public FileChecksumOptions() { - Algorithm = CryptoAlgorithm.Md5; Method = EntityDataIntegrityMethod.Unaltered; } - /// - /// Gets or sets the hash algorithm to use for the checksum computation. - /// - /// The hash algorithm to use for the checksum computation. - public CryptoAlgorithm Algorithm { get; set; } /// /// Gets an enumeration value of indicating how a checksum is generated. diff --git a/src/Cuemon.Data.Integrity/GlobalSuppressions.cs b/src/Cuemon.Data.Integrity/GlobalSuppressions.cs new file mode 100644 index 000000000..f62ceb404 --- /dev/null +++ b/src/Cuemon.Data.Integrity/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Bug", "S2259:Null pointers should not be dereferenced", Justification = "False-Positive", Scope = "member", Target = "~M:Cuemon.Data.Integrity.CacheValidator.#ctor(Cuemon.Data.Integrity.EntityInfo,System.Func{Cuemon.Security.Hash},Cuemon.Data.Integrity.EntityDataIntegrityMethod)")] diff --git a/src/Cuemon.Core/Data/Integrity/IDataIntegrity.cs b/src/Cuemon.Data.Integrity/IDataIntegrity.cs similarity index 92% rename from src/Cuemon.Core/Data/Integrity/IDataIntegrity.cs rename to src/Cuemon.Data.Integrity/IDataIntegrity.cs index ffa40808c..0df23fd2e 100644 --- a/src/Cuemon.Core/Data/Integrity/IDataIntegrity.cs +++ b/src/Cuemon.Data.Integrity/IDataIntegrity.cs @@ -1,4 +1,4 @@ -using Cuemon.Security.Cryptography; +using Cuemon.Security; namespace Cuemon.Data.Integrity { diff --git a/src/Cuemon.Core/Data/Integrity/IEntityDataIntegrity.cs b/src/Cuemon.Data.Integrity/IEntityDataIntegrity.cs similarity index 70% rename from src/Cuemon.Core/Data/Integrity/IEntityDataIntegrity.cs rename to src/Cuemon.Data.Integrity/IEntityDataIntegrity.cs index 3e2b70e1e..112da9a58 100644 --- a/src/Cuemon.Core/Data/Integrity/IEntityDataIntegrity.cs +++ b/src/Cuemon.Data.Integrity/IEntityDataIntegrity.cs @@ -1,14 +1,15 @@ namespace Cuemon.Data.Integrity { /// - /// An interface that represents the integrity od data that is normally associated with an entity/resource. + /// An interface that represents the integrity of data that is normally associated with an entity/resource. /// + /// public interface IEntityDataIntegrity : IDataIntegrity { /// /// Gets the validation strength of the integrity of this resource. /// /// The validation strength of the integrity of this resource. - EntityDataIntegrityStrength Validation { get; } + EntityDataIntegrityValidation Validation { get; } } } \ No newline at end of file diff --git a/src/Cuemon.Core/Data/Integrity/IEntityDataTimestamp.cs b/src/Cuemon.Data.Integrity/IEntityDataTimestamp.cs similarity index 100% rename from src/Cuemon.Core/Data/Integrity/IEntityDataTimestamp.cs rename to src/Cuemon.Data.Integrity/IEntityDataTimestamp.cs diff --git a/src/Cuemon.Core/Data/Integrity/IEntityData.cs b/src/Cuemon.Data.Integrity/IEntityInfo.cs similarity index 83% rename from src/Cuemon.Core/Data/Integrity/IEntityData.cs rename to src/Cuemon.Data.Integrity/IEntityInfo.cs index 088b37ed3..7c128a4e7 100644 --- a/src/Cuemon.Core/Data/Integrity/IEntityData.cs +++ b/src/Cuemon.Data.Integrity/IEntityInfo.cs @@ -5,7 +5,7 @@ /// /// /// - public interface IEntityData : IEntityDataTimestamp, IEntityDataIntegrity + public interface IEntityInfo : IEntityDataTimestamp, IEntityDataIntegrity { } } \ No newline at end of file diff --git a/src/Cuemon.Data.Integrity/Properties/PackageReleaseNotes.txt b/src/Cuemon.Data.Integrity/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..0f8e1e940 --- /dev/null +++ b/src/Cuemon.Data.Integrity/Properties/PackageReleaseNotes.txt @@ -0,0 +1,20 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.Integrity namespace was removed with this version +- Any former extension methods of the Cuemon.Integrity namespace was merged into the Cuemon.Extensions.Data.Integrity namespace +  +# Breaking Changes +- RENAMED ChecksumMethod enum in the Cuemon.Data.Integrity namespace to EntityDataIntegrityMethod (including rename of Default --> Unaltered) +- RENAMED ChecksumStrength enum in the Cuemon.Data.Integrity namespace to EntityDataIntegrityValidation (including rename of None --> Unspecified) +- RENAMED ICacheableIntegrity interface in the Cuemon.Data.Integrity namespace to IEntityDataIntegrity +- RENAMED ICacheableTimestamp interface in the Cuemon.Data.Integrity namespace to IEntityDataTimestamp +- RENAMED ICacheableEntity interface in the Cuemon.Data.Integrity namespace to IEntityInfo +  +# New Features +- ADDED CacheValidatorFactory class in the Cuemon.Data.Integrity namespace that provides access to factory methods for creating and configuring CacheValidator instances +- ADDED DataIntegrityFactory class in the Cuemon.Data.Integrity namespace that provides access to factory methods for creating and configuring implementations of the IDataIntegrity interface +- ADDED FileChecksumOptions class in the Cuemon.Data.Integrity namespace that specifies configuration options for FileInfo +- ADDED FileIntegrityOptions class in the Cuemon.Data.Integrity namespace that specifies configuration options for FileInfo +- ADDED IDataIntegrity interface in the Cuemon.Data.Integrity namespace determines the integrity of data \ No newline at end of file diff --git a/src/Cuemon.Data.SqlClient/Cuemon.Data.SqlClient.csproj b/src/Cuemon.Data.SqlClient/Cuemon.Data.SqlClient.csproj index 32dee29c1..873e901f3 100644 --- a/src/Cuemon.Data.SqlClient/Cuemon.Data.SqlClient.csproj +++ b/src/Cuemon.Data.SqlClient/Cuemon.Data.SqlClient.csproj @@ -1,26 +1,15 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 030bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Data.SqlClient Cuemon.Data.SqlClient - The Cuemon.Data.SqlClient assembly provides different Microsoft SQL Server implementations of different abstractions found in the Cuemon.Data namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Data.SqlClient namespace contains types that provide ways for developers to work with Microsoft SQL Server integrations. The namespace is an addition to the System.Data.SqlClient namespace. + sql sql-in-operator sql-query-builder sql-data-manager transient-fault-handling @@ -29,7 +18,7 @@ - + \ No newline at end of file diff --git a/src/Cuemon.Data.SqlClient/Properties/PackageReleaseNotes.txt b/src/Cuemon.Data.SqlClient/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..c30ad7d2a --- /dev/null +++ b/src/Cuemon.Data.SqlClient/Properties/PackageReleaseNotes.txt @@ -0,0 +1,6 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Breaking Changes +- CHANGED SqlDataManager class in the Cuemon.Data.SqlClient namespace to be less dependant on base class and applied quality gate actions +- CHANGED SqlInOperator class in the Cuemon.Data.SqlClient namespace to adapt the changes made to the base class \ No newline at end of file diff --git a/src/Cuemon.Data.SqlClient/SqlDataManager.cs b/src/Cuemon.Data.SqlClient/SqlDataManager.cs index c2c8d2494..ee3517fb6 100644 --- a/src/Cuemon.Data.SqlClient/SqlDataManager.cs +++ b/src/Cuemon.Data.SqlClient/SqlDataManager.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Globalization; using System.Linq; +using System.Reflection; using Cuemon.Collections.Generic; using Cuemon.Resilience; @@ -103,14 +105,14 @@ public SqlDataManager(string connectionString) /// The data command to execute. /// The parameters to use in the command. /// - public override int ExecuteIdentityInt32(IDataCommand dataCommand, params DbParameter[] parameters) + public override int ExecuteIdentityInt32(IDataCommand dataCommand, params IDbDataParameter[] parameters) { if (dataCommand == null) throw new ArgumentNullException(nameof(dataCommand)); if (dataCommand.Type != CommandType.Text) { throw new ArgumentException("This method only supports CommandType.Text specifications.", nameof(dataCommand)); } return ExecuteScalarAsInt32(new DataCommand(FormattableString.Invariant($"{dataCommand.Text} SELECT CONVERT(INT, SCOPE_IDENTITY())")) - { - Timeout = dataCommand.Timeout - }, parameters); + { + Timeout = dataCommand.Timeout + }, parameters); } /// @@ -119,7 +121,7 @@ public override int ExecuteIdentityInt32(IDataCommand dataCommand, params DbPara /// The data command to execute. /// The parameters to use in the command. /// - public override long ExecuteIdentityInt64(IDataCommand dataCommand, params DbParameter[] parameters) + public override long ExecuteIdentityInt64(IDataCommand dataCommand, params IDbDataParameter[] parameters) { if (dataCommand == null) throw new ArgumentNullException(nameof(dataCommand)); if (dataCommand.Type != CommandType.Text) { throw new ArgumentException("This method only supports CommandType.Text specifications.", nameof(dataCommand)); } @@ -135,7 +137,7 @@ public override long ExecuteIdentityInt64(IDataCommand dataCommand, params DbPar /// The data command to execute. /// The parameters to use in the command. /// - public override decimal ExecuteIdentityDecimal(IDataCommand dataCommand, params DbParameter[] parameters) + public override decimal ExecuteIdentityDecimal(IDataCommand dataCommand, params IDbDataParameter[] parameters) { if (dataCommand == null) throw new ArgumentNullException(nameof(dataCommand)); if (dataCommand.Type != CommandType.Text) { throw new ArgumentException("This method only supports CommandType.Text specifications.", nameof(dataCommand)); } @@ -157,12 +159,12 @@ public override DataManager Clone() } /// - /// Core method for executing methods on the object resolved from the virtual method. + /// Core method for executing methods on the object resolved from the virtual method. /// /// The type to return. /// The data command to execute. /// The parameters to use in the command. - /// The function delegate that will invoke a method on the resolved from the virtual method. + /// The function delegate that will invoke a method on the resolved from the virtual method. /// A value of that is equal to the invoked method of the object. /// /// If is null, no SQL operation is wrapped inside a transient fault handling operation. @@ -172,10 +174,10 @@ public override DataManager Clone() /// In case of a transient failure the default implementation will use .
/// In any other case the originating exception is thrown. ///
- protected override T ExecuteCore(IDataCommand dataCommand, DbParameter[] parameters, Func commandInvoker) + protected override T ExecuteCore(IDataCommand dataCommand, IDbDataParameter[] parameters, Func commandInvoker) { - return TransientFaultHandlingOptionsCallback == null - ? base.ExecuteCore(dataCommand, parameters, commandInvoker) + return TransientFaultHandlingOptionsCallback == null + ? base.ExecuteCore(dataCommand, parameters, commandInvoker) : TransientOperation.WithFunc(() => base.ExecuteCore(dataCommand, parameters, commandInvoker), TransientFaultHandlingOptionsCallback); } @@ -184,49 +186,50 @@ protected override T ExecuteCore(IDataCommand dataCommand, DbParameter[] para ///
/// The data command to execute. /// The parameters to use in the command. - /// - protected override DbCommand GetCommandCore(IDataCommand dataCommand, params DbParameter[] parameters) + /// A a new instance. + /// + /// cannot be null -or- + /// cannot be null. + /// + protected override DbCommand GetCommandCore(IDataCommand dataCommand, params IDbDataParameter[] parameters) + { + Validator.ThrowIfNull(dataCommand, nameof(dataCommand)); + Validator.ThrowIfNull(parameters, nameof(parameters)); + return Patterns.SafeInvoke(() => new SqlCommand(dataCommand.Text, new SqlConnection(ConnectionString)), sc => + { + AddSqlParameters(sc, parameters); + return sc; + }, ex => throw ExceptionInsights.Embed(new InvalidOperationException("There is an error when creating a new SqlCommand.", ex), MethodBase.GetCurrentMethod(), Arguments.ToArray(dataCommand, parameters))); + } + + private static void AddSqlParameters(SqlCommand command, IEnumerable parameters) { - if (dataCommand == null) { throw new ArgumentNullException(nameof(dataCommand)); } - if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } - SqlCommand command; - SqlCommand tempCommand = null; - try + foreach (var parameter in parameters) { - tempCommand = new SqlCommand(dataCommand.Text, new SqlConnection(ConnectionString)); - foreach (var parameter in parameters) + if (parameter is SqlParameter sqlParameter && (sqlParameter.SqlDbType == SqlDbType.SmallDateTime || sqlParameter.SqlDbType == SqlDbType.DateTime)) { - if (parameter is SqlParameter sqlParameter) - { - // handle dates so they are compatible with SQL 200X and forward - if (sqlParameter.SqlDbType == SqlDbType.SmallDateTime || sqlParameter.SqlDbType == SqlDbType.DateTime) - { - if (parameter.Value != null && DateTime.TryParse(parameter.Value.ToString(), out var dateTime)) - { - if (dateTime == DateTime.MinValue) - { - parameter.Value = sqlParameter.SqlDbType == SqlDbType.DateTime ? DateTime.Parse("1753-01-01", CultureInfo.InvariantCulture) : DateTime.Parse("1900-01-01", CultureInfo.InvariantCulture); - } - - if (dateTime == DateTime.MaxValue && sqlParameter.SqlDbType == SqlDbType.SmallDateTime) - { - parameter.Value = DateTime.Parse("2079-06-01", CultureInfo.InvariantCulture); - } - } - } - } - - if (parameter.Value == null) { parameter.Value = DBNull.Value; } - tempCommand.Parameters.Add(parameter); + HandleSqlDateTime(sqlParameter); // handle dates so they are compatible with SQL 200X and forward } - command = tempCommand; - tempCommand = null; + + if (parameter.Value == null) { parameter.Value = DBNull.Value; } + command.Parameters.Add(parameter); } - finally + } + + private static void HandleSqlDateTime(SqlParameter parameter) + { + if (parameter.Value != null && DateTime.TryParse(parameter.Value.ToString(), out var dateTime)) { - if (tempCommand != null) { tempCommand.Dispose(); } + if (dateTime == DateTime.MinValue) + { + parameter.Value = parameter.SqlDbType == SqlDbType.DateTime ? DateTime.Parse("1753-01-01", CultureInfo.InvariantCulture) : DateTime.Parse("1900-01-01", CultureInfo.InvariantCulture); + } + + if (dateTime == DateTime.MaxValue && parameter.SqlDbType == SqlDbType.SmallDateTime) + { + parameter.Value = DateTime.Parse("2079-06-01", CultureInfo.InvariantCulture); + } } - return command; } private static SqlException ParseException(Exception exception) diff --git a/src/Cuemon.Data.SqlClient/SqlQueryBuilder.cs b/src/Cuemon.Data.SqlClient/SqlQueryBuilder.cs index 1c482999d..abeae6880 100644 --- a/src/Cuemon.Data.SqlClient/SqlQueryBuilder.cs +++ b/src/Cuemon.Data.SqlClient/SqlQueryBuilder.cs @@ -41,13 +41,14 @@ public SqlQueryBuilder(string tableName, IDictionary keyColumns, #region Methods /// - /// Create and returns the builded query from the specified . + /// Create and returns the query from the specified . /// /// Type of the query to create. /// The name of the table or view. Overrides the class wide tableName. - /// + /// The result of the builder as a T-SQL query. public override string GetQuery(QueryType queryType, string tableName) { + tableName = tableName ?? TableName; switch (queryType) { case QueryType.Exists: diff --git a/src/Cuemon.Data/BulkCopyDataReader.cs b/src/Cuemon.Data/BulkCopyDataReader.cs index 22d29283c..27738071b 100644 --- a/src/Cuemon.Data/BulkCopyDataReader.cs +++ b/src/Cuemon.Data/BulkCopyDataReader.cs @@ -10,7 +10,7 @@ namespace Cuemon.Data { /// - /// Provides a way of copying an existing object implementing the interface to a filtered forward-only stream of rows that is mapped for bulk upload. This class cannot be inherited. + /// Provides a way of copying an existing object implementing the class to a filtered forward-only stream of rows that is mapped for bulk upload. This class cannot be inherited. /// public sealed class BulkCopyDataReader : DbDataReader { @@ -66,29 +66,20 @@ private void SetFields(IOrderedDictionary fields) /// /// The name of the column to find. /// The column with the specified name as an . - public override object this[string name] - { - get { return Fields[name]; } - } + public override object this[string name] => Fields[name]; /// /// Gets the column located at the specified index. /// /// The zero-based index of the column to get. /// The column located at the specified index as an . - public override object this[int i] - { - get { return Fields[i]; } - } + public override object this[int i] => Fields[i]; /// /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. /// /// The records affected. - public override int RecordsAffected - { - get { return -1; } - } + public override int RecordsAffected => -1; private IOrderedDictionary Fields { get; set; } @@ -96,18 +87,13 @@ public override int RecordsAffected /// Gets a value that indicates whether this contains one or more rows. /// /// true if this instance has rows; otherwise, false. - public override bool HasRows { - get { return Reader.HasRows; } - } + public override bool HasRows => Reader.HasRows; /// /// Gets a value indicating whether the data reader is closed. /// /// true if this instance is closed; otherwise, false. - public override bool IsClosed - { - get { return IsDisposed; } - } + public override bool IsClosed => IsDisposed; private bool IsDisposed { get; set; } @@ -122,10 +108,8 @@ public override bool IsClosed /// Gets the number of columns in the current row. /// /// When not positioned in a valid recordset, 0; otherwise, the number of columns in the current record. - public override int FieldCount - { - get { return Mappings.Count; } - } + public override int FieldCount => Mappings.Count; + #endregion #region Methods @@ -449,10 +433,7 @@ protected override void Dispose(bool disposing) /// /// The depth of nesting for the current row. /// The outermost table has a depth of zero. - public override int Depth - { - get { return 0; } - } + public override int Depth => 0; /// /// Populates an array of objects with the column values of the current record. diff --git a/src/Cuemon.Core/Data/ConcurrentDsvDataReader.cs b/src/Cuemon.Data/ConcurrentDsvDataReader.cs similarity index 100% rename from src/Cuemon.Core/Data/ConcurrentDsvDataReader.cs rename to src/Cuemon.Data/ConcurrentDsvDataReader.cs diff --git a/src/Cuemon.Data/Cuemon.Data.csproj b/src/Cuemon.Data/Cuemon.Data.csproj index 7b61dce3d..2e3ccd9e4 100644 --- a/src/Cuemon.Data/Cuemon.Data.csproj +++ b/src/Cuemon.Data/Cuemon.Data.csproj @@ -1,26 +1,15 @@ - + - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 110bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Data Cuemon.Data - The Cuemon.Data assembly provides access to abstractions related to the System.Data namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Data namespace contains types that provide ways to connect, build and manipulate different data sources. The namespace is an addition to the System.Data namespace. + database db abstractions dto data-transfer row column bulk-copy dsv csv xml-data-reader dsv-data-reader concurrent-dsv-data-reader in-operator token-builder diff --git a/src/Cuemon.Data/DataManager.cs b/src/Cuemon.Data/DataManager.cs index 172e40648..4e9c5dce7 100644 --- a/src/Cuemon.Data/DataManager.cs +++ b/src/Cuemon.Data/DataManager.cs @@ -186,7 +186,7 @@ public static string ReaderToString(DbDataReader value) /// /// A value. /// - public int Execute(IDataCommand dataCommand, params DbParameter[] parameters) + public int Execute(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteCore(dataCommand, parameters, dbCommand => { @@ -209,7 +209,7 @@ public int Execute(IDataCommand dataCommand, params DbParameter[] parameters) /// /// A value. /// - public bool ExecuteExists(IDataCommand dataCommand, params DbParameter[] parameters) + public bool ExecuteExists(IDataCommand dataCommand, params IDbDataParameter[] parameters) { using (var reader = ExecuteReader(dataCommand, parameters)) { @@ -223,7 +223,7 @@ public bool ExecuteExists(IDataCommand dataCommand, params DbParameter[] paramet /// The data command to execute. /// The parameters to use in the command. /// - public abstract int ExecuteIdentityInt32(IDataCommand dataCommand, params DbParameter[] parameters); + public abstract int ExecuteIdentityInt32(IDataCommand dataCommand, params IDbDataParameter[] parameters); /// /// Executes the command statement and returns an identity value as long. @@ -231,7 +231,7 @@ public bool ExecuteExists(IDataCommand dataCommand, params DbParameter[] paramet /// The data command to execute. /// The parameters to use in the command. /// - public abstract long ExecuteIdentityInt64(IDataCommand dataCommand, params DbParameter[] parameters); + public abstract long ExecuteIdentityInt64(IDataCommand dataCommand, params IDbDataParameter[] parameters); /// /// Executes the command statement and returns an identity value as decimal. @@ -239,7 +239,7 @@ public bool ExecuteExists(IDataCommand dataCommand, params DbParameter[] paramet /// The data command to execute. /// The parameters to use in the command. /// - public abstract decimal ExecuteIdentityDecimal(IDataCommand dataCommand, params DbParameter[] parameters); + public abstract decimal ExecuteIdentityDecimal(IDataCommand dataCommand, params IDbDataParameter[] parameters); /// /// Executes the command statement and returns an object supporting the DbDataReader interface. @@ -249,7 +249,7 @@ public bool ExecuteExists(IDataCommand dataCommand, params DbParameter[] paramet /// /// An object supporting the interface. /// - public DbDataReader ExecuteReader(IDataCommand dataCommand, params DbParameter[] parameters) + public DbDataReader ExecuteReader(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteCore(dataCommand, parameters, dbCommand => dbCommand.ExecuteReader(CommandBehavior.CloseConnection)); } @@ -262,7 +262,7 @@ public DbDataReader ExecuteReader(IDataCommand dataCommand, params DbParameter[] /// /// An object. /// - public virtual string ExecuteXmlString(IDataCommand dataCommand, params DbParameter[] parameters) + public virtual string ExecuteXmlString(IDataCommand dataCommand, params IDbDataParameter[] parameters) { using (var reader = ExecuteReader(dataCommand, parameters)) { @@ -277,7 +277,7 @@ public virtual string ExecuteXmlString(IDataCommand dataCommand, params DbParame /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from . - public object ExecuteScalar(IDataCommand dataCommand, params DbParameter[] parameters) + public object ExecuteScalar(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteCore(dataCommand, parameters, dbCommand => { @@ -301,7 +301,7 @@ public object ExecuteScalar(IDataCommand dataCommand, params DbParameter[] param /// The parameters to use in the command. /// The first column of the first row in the result from as the specified . /// This method uses when casting the first column of the first row in the result from . - public object ExecuteScalarAsType(IDataCommand dataCommand, Type returnType, params DbParameter[] parameters) + public object ExecuteScalarAsType(IDataCommand dataCommand, Type returnType, params IDbDataParameter[] parameters) { return ExecuteScalarAsType(dataCommand, returnType, CultureInfo.InvariantCulture, parameters); } @@ -315,7 +315,7 @@ public object ExecuteScalarAsType(IDataCommand dataCommand, Type returnType, par /// An object that supplies culture-specific formatting information. /// The parameters to use in the command. /// The first column of the first row in the result from as the specified . - public object ExecuteScalarAsType(IDataCommand dataCommand, Type returnType, IFormatProvider provider, params DbParameter[] parameters) + public object ExecuteScalarAsType(IDataCommand dataCommand, Type returnType, IFormatProvider provider, params IDbDataParameter[] parameters) { return Decorator.Enclose(ExecuteScalar(dataCommand, parameters)).ChangeType(returnType, o => o.FormatProvider = provider); } @@ -329,7 +329,7 @@ public object ExecuteScalarAsType(IDataCommand dataCommand, Type returnType, IFo /// The parameters to use in the command. /// The first column of the first row in the result from as . /// This method uses when casting the first column of the first row in the result from . - public TResult ExecuteScalarAs(IDataCommand dataCommand, params DbParameter[] parameters) + public TResult ExecuteScalarAs(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return (TResult)ExecuteScalarAsType(dataCommand, typeof(TResult), parameters); } @@ -343,7 +343,7 @@ public TResult ExecuteScalarAs(IDataCommand dataCommand, params DbParam /// An object that supplies culture-specific formatting information. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public TResult ExecuteScalarAs(IDataCommand dataCommand, IFormatProvider provider, params DbParameter[] parameters) + public TResult ExecuteScalarAs(IDataCommand dataCommand, IFormatProvider provider, params IDbDataParameter[] parameters) { return (TResult)ExecuteScalarAsType(dataCommand, typeof(TResult), provider, parameters); } @@ -355,7 +355,7 @@ public TResult ExecuteScalarAs(IDataCommand dataCommand, IFormatProvide /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public bool ExecuteScalarAsBoolean(IDataCommand dataCommand, params DbParameter[] parameters) + public bool ExecuteScalarAsBoolean(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -368,7 +368,7 @@ public bool ExecuteScalarAsBoolean(IDataCommand dataCommand, params DbParameter[ /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public DateTime ExecuteScalarAsDateTime(IDataCommand dataCommand, params DbParameter[] parameters) + public DateTime ExecuteScalarAsDateTime(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -380,7 +380,7 @@ public DateTime ExecuteScalarAsDateTime(IDataCommand dataCommand, params DbParam /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public short ExecuteScalarAsInt16(IDataCommand dataCommand, params DbParameter[] parameters) + public short ExecuteScalarAsInt16(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -392,7 +392,7 @@ public short ExecuteScalarAsInt16(IDataCommand dataCommand, params DbParameter[] /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public int ExecuteScalarAsInt32(IDataCommand dataCommand, params DbParameter[] parameters) + public int ExecuteScalarAsInt32(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -404,7 +404,7 @@ public int ExecuteScalarAsInt32(IDataCommand dataCommand, params DbParameter[] p /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public long ExecuteScalarAsInt64(IDataCommand dataCommand, params DbParameter[] parameters) + public long ExecuteScalarAsInt64(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -416,7 +416,7 @@ public long ExecuteScalarAsInt64(IDataCommand dataCommand, params DbParameter[] /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public byte ExecuteScalarAsByte(IDataCommand dataCommand, params DbParameter[] parameters) + public byte ExecuteScalarAsByte(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -428,7 +428,7 @@ public byte ExecuteScalarAsByte(IDataCommand dataCommand, params DbParameter[] p /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public sbyte ExecuteScalarAsSByte(IDataCommand dataCommand, params DbParameter[] parameters) + public sbyte ExecuteScalarAsSByte(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -440,7 +440,7 @@ public sbyte ExecuteScalarAsSByte(IDataCommand dataCommand, params DbParameter[] /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public decimal ExecuteScalarAsDecimal(IDataCommand dataCommand, params DbParameter[] parameters) + public decimal ExecuteScalarAsDecimal(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -452,7 +452,7 @@ public decimal ExecuteScalarAsDecimal(IDataCommand dataCommand, params DbParamet /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public double ExecuteScalarAsDouble(IDataCommand dataCommand, params DbParameter[] parameters) + public double ExecuteScalarAsDouble(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -464,7 +464,7 @@ public double ExecuteScalarAsDouble(IDataCommand dataCommand, params DbParameter /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public ushort ExecuteScalarAsUInt16(IDataCommand dataCommand, params DbParameter[] parameters) + public ushort ExecuteScalarAsUInt16(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -476,7 +476,7 @@ public ushort ExecuteScalarAsUInt16(IDataCommand dataCommand, params DbParameter /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public uint ExecuteScalarAsUInt32(IDataCommand dataCommand, params DbParameter[] parameters) + public uint ExecuteScalarAsUInt32(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -488,7 +488,7 @@ public uint ExecuteScalarAsUInt32(IDataCommand dataCommand, params DbParameter[] /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public ulong ExecuteScalarAsUInt64(IDataCommand dataCommand, params DbParameter[] parameters) + public ulong ExecuteScalarAsUInt64(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -500,7 +500,7 @@ public ulong ExecuteScalarAsUInt64(IDataCommand dataCommand, params DbParameter[ /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public string ExecuteScalarAsString(IDataCommand dataCommand, params DbParameter[] parameters) + public string ExecuteScalarAsString(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -513,7 +513,7 @@ public string ExecuteScalarAsString(IDataCommand dataCommand, params DbParameter /// The data command to execute. /// The parameters to use in the command. /// The first column of the first row in the result from as . - public Guid ExecuteScalarAsGuid(IDataCommand dataCommand, params DbParameter[] parameters) + public Guid ExecuteScalarAsGuid(IDataCommand dataCommand, params IDbDataParameter[] parameters) { return ExecuteScalarAs(dataCommand, parameters); } @@ -526,12 +526,12 @@ public Guid ExecuteScalarAsGuid(IDataCommand dataCommand, params DbParameter[] p /// The parameters to use in the command. /// The function delegate that will invoke a method on the resolved from the virtual method. /// A value of that is equal to the invoked method of the object. - protected virtual T ExecuteCore(IDataCommand dataCommand, DbParameter[] parameters, Func commandInvoker) + protected virtual T ExecuteCore(IDataCommand dataCommand, IDbDataParameter[] parameters, Func commandInvoker) { return InvokeCommandCore(dataCommand, parameters, commandInvoker); } - private T InvokeCommandCore(IDataCommand dataCommand, DbParameter[] parameters, Func sqlInvoker) + private T InvokeCommandCore(IDataCommand dataCommand, IDbDataParameter[] parameters, Func sqlInvoker) { T result; DbCommand command = null; @@ -555,7 +555,7 @@ private T InvokeCommandCore(IDataCommand dataCommand, DbParameter[] parameter /// The data command to execute. /// The parameters to use in the command. /// System.Data.Common.DbCommand - protected virtual DbCommand ExecuteCommandCore(IDataCommand dataCommand, params DbParameter[] parameters) + protected virtual DbCommand ExecuteCommandCore(IDataCommand dataCommand, params IDbDataParameter[] parameters) { if (dataCommand == null) throw new ArgumentNullException(nameof(dataCommand)); DbCommand command = null; @@ -585,8 +585,8 @@ private void OpenConnection(DbCommand command) /// /// The data command to execute. /// The parameters to use in the command. - /// - protected abstract DbCommand GetCommandCore(IDataCommand dataCommand, params DbParameter[] parameters); + /// An instance of a implementation. + protected abstract DbCommand GetCommandCore(IDataCommand dataCommand, params IDbDataParameter[] parameters); #endregion } } \ No newline at end of file diff --git a/src/Cuemon.Core/Data/DataReader.cs b/src/Cuemon.Data/DataReader.cs similarity index 99% rename from src/Cuemon.Core/Data/DataReader.cs rename to src/Cuemon.Data/DataReader.cs index cb7c40293..48eb398cc 100644 --- a/src/Cuemon.Core/Data/DataReader.cs +++ b/src/Cuemon.Data/DataReader.cs @@ -8,8 +8,6 @@ namespace Cuemon.Data { /// /// Provides a generic way of reading a forward-only stream of rows from a based data source. This is an abstract class. - /// Implements the - /// Implements the /// /// The type of the value that this will read. /// diff --git a/src/Cuemon.Data/DataTransferRow.cs b/src/Cuemon.Data/DataTransferRow.cs index 6f7497b2f..c03a9c137 100644 --- a/src/Cuemon.Data/DataTransferRow.cs +++ b/src/Cuemon.Data/DataTransferRow.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Text; -using Cuemon.Reflection; namespace Cuemon.Data { diff --git a/src/Cuemon.Data/DataTransferRowCollection.cs b/src/Cuemon.Data/DataTransferRowCollection.cs index 591b6813a..d71243346 100644 --- a/src/Cuemon.Data/DataTransferRowCollection.cs +++ b/src/Cuemon.Data/DataTransferRowCollection.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Data; using System.Linq; -using Cuemon.Reflection; namespace Cuemon.Data { diff --git a/src/Cuemon.Core/Data/DsvDataReader.cs b/src/Cuemon.Data/DsvDataReader.cs similarity index 100% rename from src/Cuemon.Core/Data/DsvDataReader.cs rename to src/Cuemon.Data/DsvDataReader.cs diff --git a/src/Cuemon.Data/GlobalSuppressions.cs b/src/Cuemon.Data/GlobalSuppressions.cs index 1767d078c..ed16c5634 100644 --- a/src/Cuemon.Data/GlobalSuppressions.cs +++ b/src/Cuemon.Data/GlobalSuppressions.cs @@ -6,3 +6,10 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Major Bug", "S2259:Null pointers should not be dereferenced", Justification = "The variable, fieldCount, is initialized to -1 in case of record being null - hence, the for loop will never iterate.", Scope = "member", Target = "~M:Cuemon.Data.DataTransferColumnCollection.#ctor(System.Data.IDataRecord)")] +[assembly: SuppressMessage("Major Bug", "S1751:Loops with at most one iteration should be refactored", Justification = "This is by design and how a reader is implemented. While there are lines to be read, we built a token, and the token is being read. When read, it returns true and proceeds to next line.", Scope = "member", Target = "~M:Cuemon.Data.DsvDataReader.Read~System.Boolean")] +[assembly: SuppressMessage("Major Bug", "S1751:Loops with at most one iteration should be refactored", Justification = "This is by design and how a reader is implemented. While there are lines to be read, we built a token, and the token is being read. When read, it returns true and proceeds to next line.", Scope = "member", Target = "~M:Cuemon.Data.ConcurrentDsvDataReader.ReadAsync~System.Threading.Tasks.Task{System.Boolean}")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "By design; property serves it purpose.", Scope = "member", Target = "~P:Cuemon.Data.ConcurrentDsvDataReader.NullRead")] +[assembly: SuppressMessage("Critical Code Smell", "S927:parameter names should match base declaration and other partial definitions", Justification = "By design to help clarify context.", Scope = "member", Target = "~M:Cuemon.Data.ConcurrentDsvDataReader.ReadNext(System.String[])~System.String[]")] +[assembly: SuppressMessage("Critical Code Smell", "S927:parameter names should match base declaration and other partial definitions", Justification = "By design to help clarify context.", Scope = "member", Target = "~M:Cuemon.Data.DsvDataReader.ReadNext(System.String[])~System.String[]")] +[assembly: SuppressMessage("Major Code Smell", "S1168:Empty arrays and collections should be returned instead of null", Justification = "By design; property serves it purpose.", Scope = "member", Target = "~P:Cuemon.Data.DsvDataReader.NullRead")] +[assembly: SuppressMessage("Major Code Smell", "S907:\"goto\" statement should not be used", Justification = "Legacy implementation.", Scope = "member", Target = "~M:Cuemon.Data.Xml.XmlDataReader.ReadNext(System.Boolean)~System.Boolean")] \ No newline at end of file diff --git a/src/Cuemon.Data/InOperatorResult.cs b/src/Cuemon.Data/InOperatorResult.cs index 337b66879..316710712 100644 --- a/src/Cuemon.Data/InOperatorResult.cs +++ b/src/Cuemon.Data/InOperatorResult.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Linq; namespace Cuemon.Data { @@ -31,6 +32,15 @@ internal InOperatorResult(IEnumerable arguments, IEnumerableThe parameters for the IN operator. public IEnumerable Parameters { get; } + /// + /// Converts the parameters for the IN operator to an . + /// + /// An array of . + public IDbDataParameter[] ToParametersArray() + { + return Parameters.ToArray(); + } + /// /// Returns a that represents this instance. /// diff --git a/src/Cuemon.Data/Properties/PackageReleaseNotes.txt b/src/Cuemon.Data/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..6de46a2eb --- /dev/null +++ b/src/Cuemon.Data/Properties/PackageReleaseNotes.txt @@ -0,0 +1,25 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.Data.XmlClient assembly and namespace was removed with this version +- Types found in the Cuemon.Data.XmlClient namespace was merged into the Cuemon.Data.Xml namespace +- To use the earlier built-in support for Microsoft SQL Server, please refer to the Cuemon.Data.SqlClient namespace, as it has been merged and refactored out of this assembly +- Any former extension methods of the Cuemon.Data namespace was merged into the Cuemon.Extensions.Data namespace +  +# Breaking Changes +- REFACTORED XmlDataReader class in the Cuemon.Data.Xml namespace to match and inherit from DataReader +- REFACTORED DataManager class in the Cuemon.Data namespace to have a higher cohesion and lower coupling +- RENAMED StringDataReader class in the Cuemon.Data namespace to DataReader{T} (including major refactoring of the underlying code) +- RENAMED DataParameterEqualityComparer class in the Cuemon.Data namespace to DbParameterEqualityComparer +- REMOVED DataTransferSorter class from the Cuemon.Data namespace +- REMOVED CsvDataReader class from the Cuemon.Data.CsvClient namespace (including the namespace) +- MERGED Cuemon.Data.SqlClient namespace to its own assembly +- REFACTORED InOperator class in the Cuemon.Data namespace to be less complex and make usage of InOperatorResult +- REMOVED QueryUtility class from the Cuemon.Data namespace +  +# New Features +- ADDED ConcurrentDsvDataReader class in the Cuemon.Data namespace that provides a concurrent way of reading a forward-only stream of rows from a DSV (Delimiter Separated Values) based data source +- ADDED DsvDataReader class in the Cuemon.Data namespace that provides a way of reading a forward-only stream of rows from a DSV (Delimiter Separated Values) based data source +- ADDED InOperatorResult class in the Cuemon.Data namespace that provides the result of an InOperator{T} operation +- ADDED TokenBuilder class in the Cuemon.Data namespace that represents a mutable string of characters optimized for tokens \ No newline at end of file diff --git a/src/Cuemon.Data/QueryBuilder.cs b/src/Cuemon.Data/QueryBuilder.cs index 648d39217..467400cc7 100644 --- a/src/Cuemon.Data/QueryBuilder.cs +++ b/src/Cuemon.Data/QueryBuilder.cs @@ -155,10 +155,10 @@ public static string EncodeFragment(QueryFormat format, IEnumerable valu } /// - /// Create and returns the builded query from the specified . + /// Create and returns the query from the specified . /// /// Type of the query to create. - /// The builded T-SQL query. + /// The result of the builder as a T-SQL query. public string GetQuery(QueryType queryType) { return GetQuery(queryType, null); diff --git a/src/Cuemon.Core/Data/TokenBuilder.cs b/src/Cuemon.Data/TokenBuilder.cs similarity index 100% rename from src/Cuemon.Core/Data/TokenBuilder.cs rename to src/Cuemon.Data/TokenBuilder.cs diff --git a/src/Cuemon.Xml/XmlDataReader.cs b/src/Cuemon.Data/Xml/XmlDataReader.cs similarity index 99% rename from src/Cuemon.Xml/XmlDataReader.cs rename to src/Cuemon.Data/Xml/XmlDataReader.cs index ffaf5c632..6ba0d87c8 100644 --- a/src/Cuemon.Xml/XmlDataReader.cs +++ b/src/Cuemon.Data/Xml/XmlDataReader.cs @@ -2,10 +2,9 @@ using System.Collections.Specialized; using System.Globalization; using System.Xml; -using Cuemon.Data; using Cuemon.Text; -namespace Cuemon.Xml +namespace Cuemon.Data.Xml { /// /// Provides a way of reading a forward-only stream of rows from an XML based data source. This class cannot be inherited. diff --git a/src/Cuemon.Diagnostics/Cuemon.Diagnostics.csproj b/src/Cuemon.Diagnostics/Cuemon.Diagnostics.csproj index a69dfa3ab..3bcfbebf7 100644 --- a/src/Cuemon.Diagnostics/Cuemon.Diagnostics.csproj +++ b/src/Cuemon.Diagnostics/Cuemon.Diagnostics.csproj @@ -1,26 +1,15 @@ - + - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 1a0bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Diagnostics Cuemon.Diagnostics - The Cuemon.Diagnostics assembly provides access to features that extends the System.Diagnostics namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Diagnostics namespace contains types that provide ways for developers to describe exceptions including evidence to why an operation faulted. Also includes a flexible, generic and lambda friendly way to perform both synchronous and asynchronous time measuring operations. The namespace is an addition to the System.Diagnostics namespace. + time-measuring time-measure time-measure-profiler with-action with-func async-time-measuring with-action-async with-func-async profiler exception-descriptor diff --git a/src/Cuemon.Diagnostics/ExceptionDescriptor.cs b/src/Cuemon.Diagnostics/ExceptionDescriptor.cs index 1f6e674ac..ffde3ead7 100644 --- a/src/Cuemon.Diagnostics/ExceptionDescriptor.cs +++ b/src/Cuemon.Diagnostics/ExceptionDescriptor.cs @@ -44,7 +44,12 @@ public static ExceptionDescriptor Extract(Exception exception, string code = "Un builder.AppendLine(exception.ToString()); var memberSignature = Convertible.ToString(Convert.FromBase64String(insights[IndexOfThrower])); var runtimeParameters = Convertible.ToString(Convert.FromBase64String(insights[IndexOfRuntimeParameters])); - ed.AddEvidence("Thrower", new MemberEvidence(memberSignature, string.IsNullOrWhiteSpace(runtimeParameters) ? null : runtimeParameters.Split(Alphanumeric.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToDictionary(k => k.Substring(0, k.IndexOf('=')), v => v.Substring(v.IndexOf('=') + 1))), evidence => evidence); + ed.AddEvidence("Thrower", new MemberEvidence(memberSignature, string.IsNullOrWhiteSpace(runtimeParameters) ? null : runtimeParameters.Split(Alphanumeric.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToDictionary(k => k.Substring(0, k.IndexOf('=')), v => + { + var t = v.Substring(v.IndexOf('=') + 1); + if (t == "null") { return null; } + return t; + })), evidence => evidence); TryAddEvidence(ed, "Thread", insights[IndexOfThreadInfo]); TryAddEvidence(ed, "Process", insights[IndexOfProcessInfo]); TryAddEvidence(ed, "Environment", insights[IndexOfEnvironmentInfo]); @@ -176,5 +181,14 @@ public void PostInitializeWith(IEnumerable attribu if (!string.IsNullOrWhiteSpace(attribute.HelpLink)) { HelpLink = new Uri(attribute.HelpLink); } } } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return Failure.ToString(); + } } } \ No newline at end of file diff --git a/src/Cuemon.Diagnostics/ExceptionDescriptorAttribute.cs b/src/Cuemon.Diagnostics/ExceptionDescriptorAttribute.cs index c01fd6d0f..443981e09 100644 --- a/src/Cuemon.Diagnostics/ExceptionDescriptorAttribute.cs +++ b/src/Cuemon.Diagnostics/ExceptionDescriptorAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; using Cuemon.Globalization; namespace Cuemon.Diagnostics @@ -9,9 +8,10 @@ namespace Cuemon.Diagnostics /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class ExceptionDescriptorAttribute : Attribute, IMessageLocalizer + public class ExceptionDescriptorAttribute : ResourceAttribute { private string _helpLink; + private string _message; /// /// Initializes a new instance of the class. @@ -22,21 +22,6 @@ public ExceptionDescriptorAttribute(Type failureType) Validator.ThrowIfNull(failureType, nameof(failureType)); Validator.ThrowIfNotContainsType(failureType, nameof(failureType), "The specified type is not an Exception.", typeof(Exception)); FailureType = failureType; - Message = InitializeFromResource(); - } - - private string InitializeFromResource() - { - if (!string.IsNullOrWhiteSpace(MessageResourceName) && MessageResourceType != null) - { - var property = MessageResourceType.GetProperty(MessageResourceName, BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); - var getMethod = property?.GetGetMethod(true); - if (getMethod != null && (getMethod.IsAssembly || getMethod.IsPublic)) - { - return property.GetValue(null, null) as string; - } - } - return null; } /// @@ -49,20 +34,26 @@ private string InitializeFromResource() /// Gets or sets a default message that describes the current failure. /// /// The default message that explains the reason for the failure. - public string Message { get; set; } + public string Message + { + get + { + string localizedMessage = null; + if (ResourceType != null) + { + localizedMessage = GetString(MessageResourceName); + } + return localizedMessage ?? _message; + } + set => _message = value; + } /// - /// Gets or sets the resource name (property name) to use as the key for lookups on the resource type. + /// Gets or sets the resource name (property name) to use as the key for looking up a localized message string. /// - /// Use this property to set the name of the property within that will provide a localized message that describes the current failure. + /// The resource name (property name) to use as the key for looking up a localized message string that describes the current failure. public string MessageResourceName { get; set; } - /// - /// Gets or sets the resource type to use for message lookups. - /// - /// Use this property only in conjunction with . They are used together to retrieve localized messages at runtime. - public Type MessageResourceType { get; set; } - /// /// Gets or sets a link to the help page associated with this failure. /// diff --git a/src/Cuemon.Diagnostics/GlobalSuppressions.cs b/src/Cuemon.Diagnostics/GlobalSuppressions.cs new file mode 100644 index 000000000..c62df0804 --- /dev/null +++ b/src/Cuemon.Diagnostics/GlobalSuppressions.cs @@ -0,0 +1,59 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``4(System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``5(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``5(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``6(System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``6(System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``7(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``7(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithActionAsync``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task{``9}},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``9}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Threading.Tasks.Task{``9}},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``9}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task{``10}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``10}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Threading.Tasks.Task{``10}},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``10}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``4(System.Func{``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``0,``1,``2,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``5(System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``6(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``6(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``7(System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``7(System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``8(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task{``7}},``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``8(System.Func{``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task{``7}},``0,``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task{``8}},``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``8}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFuncAsync``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Threading.Tasks.Task{``8}},``0,``1,``2,``3,``4,``5,``6,``7,System.Threading.CancellationToken,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~System.Threading.Tasks.Task{Cuemon.Diagnostics.TimeMeasureProfiler{``8}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``10(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``10(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``4(System.Action{``0,``1,``2,``3},``0,``1,``2,``3,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``5(System.Action{``0,``1,``2,``3,``4},``0,``1,``2,``3,``4,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``6(System.Action{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,``5,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``6(System.Action{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,``5,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``7(System.Action{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4,``5,``6,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``7(System.Action{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4,``5,``6,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``8(System.Action{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6,``7,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``8(System.Action{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6,``7,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``9(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithAction``9(System.Action{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``9}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``10(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9},``0,``1,``2,``3,``4,``5,``6,``7,``8,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``9}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``10}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``11(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,``10},``0,``1,``2,``3,``4,``5,``6,``7,``8,``9,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``10}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``4(System.Func{``0,``1,``2,``3},``0,``1,``2,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``5(System.Func{``0,``1,``2,``3,``4},``0,``1,``2,``3,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``6(System.Func{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``7(System.Func{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4,``5,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``7(System.Func{``0,``1,``2,``3,``4,``5,``6},``0,``1,``2,``3,``4,``5,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``7}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``8(System.Func{``0,``1,``2,``3,``4,``5,``6,``7},``0,``1,``2,``3,``4,``5,``6,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``7}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``8}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 10 generic arguments.", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasure.WithFunc``9(System.Func{``0,``1,``2,``3,``4,``5,``6,``7,``8},``0,``1,``2,``3,``4,``5,``6,``7,System.Action{Cuemon.Diagnostics.TimeMeasureOptions})~Cuemon.Diagnostics.TimeMeasureProfiler{``8}")] diff --git a/src/Cuemon.Diagnostics/Properties/PackageReleaseNotes.txt b/src/Cuemon.Diagnostics/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..43c958c62 --- /dev/null +++ b/src/Cuemon.Diagnostics/Properties/PackageReleaseNotes.txt @@ -0,0 +1,13 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Breaking Changes +- REMOVED EventLogEntryType enum from the Cuemon.Diagnostics namespace as it is now (finally) part of .NET Platform Extensions and .NET Core +- CHANGED ExceptionDescriptorAttribute class in the Cuemon.Diagnostics namespace to have a more simple and streamlined design +- CHANGED TimeMeasure class in the Cuemon.Diagnostics namespace to fully support the Task-based Asynchronous Pattern (TAP) with cancellation +  +# New Features +- ADDED ExceptionDescriptorOptions class into Cuemon.Diagnostics namespace that specifies configuration options for serializer implementations +  +# Improvements +- EXTENDED ExceptionDescriptor class in the Cuemon.Diagnostics namespace with a new static method: Extract \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Authentication/ApplicationBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Authentication/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..2440e9715 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Authentication/ApplicationBuilderExtensions.cs @@ -0,0 +1,48 @@ +using System; +using Cuemon.AspNetCore.Authentication.Basic; +using Cuemon.AspNetCore.Authentication.Digest; +using Cuemon.AspNetCore.Authentication.Hmac; +using Cuemon.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; + +namespace Cuemon.Extensions.AspNetCore.Authentication +{ + /// + /// Extension methods for the interface. + /// + public static class ApplicationBuilderExtensions + { + /// + /// Adds a HTTP Basic Authentication scheme to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The HTTP middleware which may be configured. + /// A reference to after the operation has completed. + public static IApplicationBuilder UseBasicAuthentication(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + + /// + /// Adds a HTTP Digest Authentication scheme to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The HTTP middleware which may be configured. + /// A reference to after the operation has completed. + public static IApplicationBuilder UseDigestAccessAuthentication(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + + /// + /// Adds a HTTP HMAC Authentication scheme to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The HTTP middleware which may be configured. + /// A reference to after the operation has completed. + public static IApplicationBuilder UseHmacAuthentication(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Authentication/Cuemon.Extensions.AspNetCore.Authentication.csproj b/src/Cuemon.Extensions.AspNetCore.Authentication/Cuemon.Extensions.AspNetCore.Authentication.csproj new file mode 100644 index 000000000..ea3ab0dae --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Authentication/Cuemon.Extensions.AspNetCore.Authentication.csproj @@ -0,0 +1,19 @@ + + + + net5.0;netcoreapp3.0;netstandard2.0 + 220bdf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Extensions.AspNetCore.Authentication + Cuemon.Extensions.AspNetCore.Authentication + The Cuemon.Extensions.AspNetCore.Authentication namespace contains both types and extension methods that complements the Cuemon.AspNetCore namespace while being an addition to the Microsoft.AspNetCore namespace. Provides an in-memory implementation of a throttling cache for ASP.NET Core. + extension-methods extensions memory-throttling-cache use-hosting-environment-header use-correlation-identifier-header use-request-identifier-header use-user-agent-sentinel use-custom-throttling-sentinel + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Authentication/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.AspNetCore.Authentication/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..354bcf016 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Authentication/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("5e2ba4d0-1e7d-4ea1-9ca8-9133d7fa2319")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Authentication/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.AspNetCore.Authentication/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..9dd2cf1b0 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Authentication/Properties/PackageReleaseNotes.txt @@ -0,0 +1,9 @@ +Version: 6.0.0 +Availability: NET Standard 2.0 +  +# Breaking Changes +- +  +# New Features +- +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Authentication/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Authentication/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..cc23303e4 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Authentication/ServiceCollectionExtensions.cs @@ -0,0 +1,23 @@ +using Cuemon.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; + +namespace Cuemon.Extensions.AspNetCore.Authentication +{ + /// + /// Extension methods for the interface. + /// + public static class ServiceCollectionExtensions + { + /// + /// Adds a service to the specified . + /// + /// The to add services to. + /// An that can be used to further configure other services. + public static IServiceCollection AddInMemoryDigestAuthenticationNonceTracker(this IServiceCollection services) + { + Validator.ThrowIfNull(services, nameof(services)); + services.AddSingleton(); + return services; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Bootstrapper.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Bootstrapper.cs new file mode 100644 index 000000000..60d9d849d --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Bootstrapper.cs @@ -0,0 +1,30 @@ +using Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Converters; +using Cuemon.Extensions.Newtonsoft.Json.Formatters; + +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json +{ + internal static class Bootstrapper + { + private static readonly object PadLock = new object(); + private static bool _initialized; + + internal static void Initialize() + { + if (!_initialized) + { + lock (PadLock) + { + if (!_initialized) + { + _initialized = true; + JsonFormatterOptions.DefaultConverters += list => + { + list.AddHttpExceptionDescriptorConverter() + .AddStringValuesConverter(); + }; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/Converters/JsonConverterCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Converters/JsonConverterCollectionExtensions.cs similarity index 97% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Json/Converters/JsonConverterCollectionExtensions.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Converters/JsonConverterCollectionExtensions.cs index 9c63ce0b2..a580e2e0b 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/Converters/JsonConverterCollectionExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Converters/JsonConverterCollectionExtensions.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using Cuemon.AspNetCore.Http; +using Cuemon.AspNetCore.Diagnostics; using Cuemon.Diagnostics; using Cuemon.Extensions.Newtonsoft.Json; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json.Converters +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.Converters { /// /// Extension methods for the class. diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj new file mode 100644 index 000000000..87db9ccfe --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj @@ -0,0 +1,25 @@ + + + + net5.0;netcoreapp3.0;netstandard2.0 + a60adf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon + Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json + Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json + The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json namespace contains both types and extension methods that complements the Cuemon.Extensions.Newtonsoft.Json namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides JSON formatters for ASP.NET Core that is powered by Newtonsoft.Json. + extension-methods extensions json-converters add-json-serialization-formatters add-json-formatter-options + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationInputFormatter.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationInputFormatter.cs similarity index 62% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationInputFormatter.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationInputFormatter.cs index 40fb17982..1b9f678f6 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationInputFormatter.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationInputFormatter.cs @@ -1,11 +1,12 @@ -using System.Text; +using System.IO; +using System.Text; using System.Threading.Tasks; +using Cuemon.AspNetCore.Mvc.Filters.Diagnostics; using Cuemon.Extensions.Newtonsoft.Json.Formatters; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json { /// /// This class handles deserialization of JSON to objects using . @@ -30,15 +31,21 @@ public JsonSerializationInputFormatter(JsonFormatterOptions formatterOptions) /// The . /// The used to read the request body. /// A that on completion deserializes the request body. - public override Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) + public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { Validator.ThrowIfNull(context, nameof(context)); Validator.ThrowIfNull(encoding, nameof(encoding)); - context.HttpContext.Request.EnableBuffering(); + var requestBody = new MemoryStream(); + #if NETCOREAPP + await context.HttpContext.Request.BodyReader.CopyToAsync(requestBody).ConfigureAwait(false); + #else + await context.HttpContext.Request.Body.CopyToAsync(requestBody).ConfigureAwait(false); + #endif + requestBody.Position = 0; var formatter = new JsonFormatter(FormatterOptions); - var deserializedObject = formatter.Deserialize(context.HttpContext.Request.Body, context.ModelType); - context.HttpContext.Request.Body.Position = 0; - return InputFormatterResult.SuccessAsync(deserializedObject); + var deserializedObject = formatter.Deserialize(requestBody, context.ModelType); + context.HttpContext.Items.Add(FaultDescriptorFilter.HttpContextItemsKeyForCapturedRequestBody, requestBody); + return await InputFormatterResult.SuccessAsync(deserializedObject).ConfigureAwait(false); } private JsonFormatterOptions FormatterOptions { get; } diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationMvcOptionsSetup.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationMvcOptionsSetup.cs similarity index 92% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationMvcOptionsSetup.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationMvcOptionsSetup.cs index b96c65aa5..aef5f4dc6 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationMvcOptionsSetup.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationMvcOptionsSetup.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json { /// /// A implementation which will add the JSON serializer formatters to . diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationOutputFormatter.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationOutputFormatter.cs similarity index 97% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationOutputFormatter.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationOutputFormatter.cs index f769b41c8..26246af1e 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializationOutputFormatter.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializationOutputFormatter.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json { /// /// This class handles serialization of objects to JSON using . diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializerSettingsExtensions.cs similarity index 97% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsExtensions.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializerSettingsExtensions.cs index 1520b8a2b..ed8d8368b 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/JsonSerializerSettingsExtensions.cs @@ -1,7 +1,7 @@ using System; using Newtonsoft.Json; -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json { /// /// Extension methods for the class. diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonMvcCoreBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/MvcBuilderExtensions.cs similarity index 54% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonMvcCoreBuilderExtensions.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/MvcBuilderExtensions.cs index 216046f24..68fcd48ff 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/JsonMvcCoreBuilderExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/MvcBuilderExtensions.cs @@ -5,30 +5,26 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Json +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json { /// - /// Extension methods for adding JSON formatters to MVC. + /// Extension methods for the interface. /// - public static class JsonMvcCoreBuilderExtensions + public static class MvcBuilderExtensions { - /// - /// Adds the JSON Serializer formatters to MVC. - /// - /// The . - /// The . - public static IMvcCoreBuilder AddJsonSerializationFormatters(this IMvcCoreBuilder builder) + static MvcBuilderExtensions() { - Validator.ThrowIfNull(builder, nameof(builder)); - builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, JsonSerializationMvcOptionsSetup>()); - return builder; + Bootstrapper.Initialize(); } /// - /// Adds the JSON Serializer formatters to MVC. + /// Adds the JSON serializer formatters to MVC. /// /// The . - /// The . + /// A reference to after the operation has completed. + /// + /// cannot be null. + /// public static IMvcBuilder AddJsonSerializationFormatters(this IMvcBuilder builder) { Validator.ThrowIfNull(builder, nameof(builder)); @@ -37,16 +33,20 @@ public static IMvcBuilder AddJsonSerializationFormatters(this IMvcBuilder builde } /// - /// Adds configuration of for the application. + /// Adds the JSON serializer formatters to MVC. /// /// The . /// The which need to be configured. - /// The . - public static IMvcCoreBuilder AddJsonFormatterOptions(this IMvcCoreBuilder builder, Action setup) + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IMvcBuilder AddJsonSerializationFormatters(this IMvcBuilder builder, Action setup) { Validator.ThrowIfNull(builder, nameof(builder)); - Validator.ThrowIfNull(setup, nameof(setup)); - builder.Services.Configure(setup); + AddJsonSerializationFormatters(builder); + AddJsonFormatterOptions(builder, setup); return builder; } @@ -55,7 +55,11 @@ public static IMvcCoreBuilder AddJsonFormatterOptions(this IMvcCoreBuilder build /// /// The . /// The which need to be configured. - /// The . + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// public static IMvcBuilder AddJsonFormatterOptions(this IMvcBuilder builder, Action setup) { Validator.ThrowIfNull(builder, nameof(builder)); diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/MvcCoreBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/MvcCoreBuilderExtensions.cs new file mode 100644 index 000000000..efddefe38 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/MvcCoreBuilderExtensions.cs @@ -0,0 +1,71 @@ +using System; +using Cuemon.Extensions.Newtonsoft.Json.Formatters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json +{ + /// + /// Extension methods for the interface. + /// + public static class MvcCoreBuilderExtensions + { + static MvcCoreBuilderExtensions() + { + Bootstrapper.Initialize(); + } + + /// + /// Adds the JSON serializer formatters to MVC. + /// + /// The . + /// A reference to after the operation has completed. + /// + /// cannot be null. + /// + public static IMvcCoreBuilder AddJsonSerializationFormatters(this IMvcCoreBuilder builder) + { + Validator.ThrowIfNull(builder, nameof(builder)); + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, JsonSerializationMvcOptionsSetup>()); + return builder; + } + + /// + /// Adds the JSON serializer formatters to MVC. + /// + /// The . + /// The which need to be configured. + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IMvcCoreBuilder AddJsonSerializationFormatters(this IMvcCoreBuilder builder, Action setup) + { + Validator.ThrowIfNull(builder, nameof(builder)); + AddJsonSerializationFormatters(builder); + AddJsonFormatterOptions(builder, setup); + return builder; + } + + /// + /// Adds configuration of for the application. + /// + /// The . + /// The which need to be configured. + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IMvcCoreBuilder AddJsonFormatterOptions(this IMvcCoreBuilder builder, Action setup) + { + Validator.ThrowIfNull(builder, nameof(builder)); + Validator.ThrowIfNull(setup, nameof(setup)); + builder.Services.Configure(setup); + return builder; + } + } +} diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Properties/AssemblyInfo.cs similarity index 100% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Properties/AssemblyInfo.cs diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..ca7cb9e9f --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Properties/PackageReleaseNotes.txt @@ -0,0 +1,13 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.AspNetCore.Mvc.Formatters.Json namespace was removed with this version +- Any types found in the Cuemon.AspNetCore.Mvc.Formatters.Json namespace was merged into the Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json namespace +  +# Breaking Changes +- REMOVED DefaultJsonSerializerSettings class from the Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json namespace as the default settings is given by JsonFormatterOptions +  +# Improvements +- COMPATIBLE with the changes applied to NET Core 3 in regards to only allowing asynchronous I/O +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Bootstrapper.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Bootstrapper.cs new file mode 100644 index 000000000..8b3675953 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Bootstrapper.cs @@ -0,0 +1,34 @@ +using Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters; +using Cuemon.Xml.Serialization.Formatters; + +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml +{ + internal static class Bootstrapper + { + private static readonly object PadLock = new object(); + private static bool _initialized = false; + + internal static void Initialize() + { + if (!_initialized) + { + lock (PadLock) + { + if (!_initialized) + { + _initialized = true; + XmlFormatterOptions.DefaultConverters += list => + { + list.AddHttpExceptionDescriptorConverter() + .AddStringValuesConverter() + .AddHeaderDictionaryConverter() + .AddFormCollectionConverter() + .AddQueryCollectionConverter() + .AddCookieCollectionConverter(); + }; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Converters/XmlConverterExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Converters/XmlConverterExtensions.cs similarity index 92% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Converters/XmlConverterExtensions.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Converters/XmlConverterExtensions.cs index 22fc9539c..b38221ddc 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Converters/XmlConverterExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Converters/XmlConverterExtensions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Cuemon.AspNetCore.Http; +using Cuemon.AspNetCore.Diagnostics; using Cuemon.Diagnostics; using Cuemon.Extensions.Xml; using Cuemon.Extensions.Xml.Serialization.Converters; @@ -18,19 +18,6 @@ namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters /// public static class XmlConverterExtensions { - static XmlConverterExtensions() - { - XmlSerializerOptions.DefaultConverters += list => - { - list.AddHttpExceptionDescriptorConverter() - .AddStringValuesConverter() - .AddHeaderDictionaryConverter() - .AddFormCollectionConverter() - .AddQueryCollectionConverter() - .AddCookieCollectionConverter(); - }; - } - /// /// Adds an XML converter to the list. /// @@ -61,7 +48,7 @@ public static IList AddHttpExceptionDescriptorConverter(this IList foreach (var evidence in descriptor.Evidence) { if (evidence.Value == null) { continue; } - writer.WriteObject(evidence.Value, evidence.Value.GetType(), o => o.RootName = new XmlQualifiedEntity(evidence.Key)); + writer.WriteObject(evidence.Value, evidence.Value.GetType(), o => o.Settings.RootName = new XmlQualifiedEntity(evidence.Key)); } writer.WriteEndElement(); } diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj new file mode 100644 index 000000000..9abe18c18 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.csproj @@ -0,0 +1,24 @@ + + + + net5.0;netcoreapp3.0;netstandard2.0 + a70adf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml + Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml + The Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml namespace contains both types and extension methods that complements the Cuemon.Extensions.Xml namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides XML formatters for ASP.NET Core that offers same flexibility as the one provided by the JSON equivalent from Newtonsoft. + extension-methods extensions xml-converters add-xml-serialization-formatters add-xml-formatter-options + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/GlobalSuppressions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/GlobalSuppressions.cs new file mode 100644 index 000000000..7e95c24bf --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Critical Code Smell", "S3776:Cognitive Complexity of methods should not be too high", Justification = "Clear enough; XML Converter.", Scope = "member", Target = "~M:Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml.Converters.XmlConverterExtensions.AddHttpExceptionDescriptorConverter(System.Collections.Generic.IList{Cuemon.Xml.Serialization.Converters.XmlConverter},System.Action{Cuemon.Diagnostics.ExceptionDescriptorOptions})~System.Collections.Generic.IList{Cuemon.Xml.Serialization.Converters.XmlConverter}")] diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/MvcBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/MvcBuilderExtensions.cs new file mode 100644 index 000000000..8a423d27e --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/MvcBuilderExtensions.cs @@ -0,0 +1,72 @@ +using System; +using Cuemon.Xml.Serialization.Formatters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Extension methods for the interface. + /// + public static class MvcBuilderExtensions + { + static MvcBuilderExtensions() + { + Bootstrapper.Initialize(); + } + + /// + /// Adds the XML serializer formatters to MVC. + /// + /// The to extend. + /// A reference to after the operation has completed. + /// + /// cannot be null. + /// + public static IMvcBuilder AddXmlSerializationFormatters(this IMvcBuilder builder) + { + Validator.ThrowIfNull(builder, nameof(builder)); + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, XmlSerializationMvcOptionsSetup>()); + return builder; + } + + /// + /// Adds the XML serializer formatters to MVC. + /// + /// The to extend. + /// The which need to be configured. + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IMvcBuilder AddXmlSerializationFormatters(this IMvcBuilder builder, Action setup) + { + Validator.ThrowIfNull(builder, nameof(builder)); + AddXmlSerializationFormatters(builder); + AddXmlFormatterOptions(builder, setup); + return builder; + } + + + /// + /// Adds configuration of for the application. + /// + /// The to extend. + /// The which need to be configured. + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IMvcBuilder AddXmlFormatterOptions(this IMvcBuilder builder, Action setup) + { + Validator.ThrowIfNull(builder, nameof(builder)); + Validator.ThrowIfNull(setup, nameof(setup)); + builder.Services.Configure(setup); + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/MvcCoreBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/MvcCoreBuilderExtensions.cs new file mode 100644 index 000000000..7c6ba3f9d --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/MvcCoreBuilderExtensions.cs @@ -0,0 +1,72 @@ +using System; +using Cuemon.Xml.Serialization.Formatters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Extension methods for the interface. + /// + public static class MvcCoreBuilderExtensions + { + static MvcCoreBuilderExtensions() + { + Bootstrapper.Initialize(); + } + + /// + /// Adds the XML serializer formatters to MVC. + /// + /// The . + /// A reference to after the operation has completed. + /// + /// cannot be null. + /// + public static IMvcCoreBuilder AddXmlSerializationFormatters(this IMvcCoreBuilder builder) + { + Validator.ThrowIfNull(builder, nameof(builder)); + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, XmlSerializationMvcOptionsSetup>()); + return builder; + } + + /// + /// Adds the XML serializer formatters to MVC. + /// + /// The . + /// The which need to be configured. + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IMvcCoreBuilder AddXmlSerializationFormatters(this IMvcCoreBuilder builder, Action setup) + { + Validator.ThrowIfNull(builder, nameof(builder)); + AddXmlSerializationFormatters(builder); + AddXmlFormatterOptions(builder, setup); + return builder; + } + + /// + /// Adds configuration of for the application. + /// + /// The . + /// The which need to be configured. + /// A reference to after the operation has completed. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IMvcCoreBuilder AddXmlFormatterOptions(this IMvcCoreBuilder builder, Action setup) + { + Validator.ThrowIfNull(builder, nameof(builder)); + Validator.ThrowIfNull(setup, nameof(setup)); + AddXmlSerializationFormatters(builder); + builder.Services.Configure(setup); + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs similarity index 100% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..eeb529b0a --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/Properties/PackageReleaseNotes.txt @@ -0,0 +1,10 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.AspNetCore.Mvc.Formatters.Xml namespace was removed with this version +- Any types found in the Cuemon.AspNetCore.Mvc.Formatters.Xml namespace was merged into the Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml namespace +  +# Improvements +- COMPATIBLE with the changes applied to NET Core 3 in regards to only allowing asynchronous I/O +  \ No newline at end of file diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlSerializationInputFormatter.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/XmlSerializationInputFormatter.cs similarity index 64% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlSerializationInputFormatter.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/XmlSerializationInputFormatter.cs index bed6bba4d..28eaec2bf 100644 --- a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlSerializationInputFormatter.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/XmlSerializationInputFormatter.cs @@ -1,7 +1,8 @@ -using System.Text; +using System.IO; +using System.Text; using System.Threading.Tasks; +using Cuemon.AspNetCore.Mvc.Filters.Diagnostics; using Cuemon.Xml.Serialization.Formatters; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; @@ -30,15 +31,21 @@ public XmlSerializationInputFormatter(XmlFormatterOptions formatterOptions) /// The . /// The used to read the request body. /// A that on completion deserializes the request body. - public override Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) + public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { Validator.ThrowIfNull(context, nameof(context)); Validator.ThrowIfNull(encoding, nameof(encoding)); - context.HttpContext.Request.EnableBuffering(); + var requestBody = new MemoryStream(); + #if NETCOREAPP + await context.HttpContext.Request.BodyReader.CopyToAsync(requestBody).ConfigureAwait(false); + #else + await context.HttpContext.Request.Body.CopyToAsync(requestBody).ConfigureAwait(false); + #endif + requestBody.Position = 0; var formatter = new XmlFormatter(FormatterOptions); - var deserializedObject = formatter.Deserialize(context.HttpContext.Request.Body, context.ModelType); - context.HttpContext.Request.Body.Position = 0; - return InputFormatterResult.SuccessAsync(deserializedObject); + var deserializedObject = formatter.Deserialize(requestBody, context.ModelType); + context.HttpContext.Items.Add(FaultDescriptorFilter.HttpContextItemsKeyForCapturedRequestBody, requestBody); + return await InputFormatterResult.SuccessAsync(deserializedObject).ConfigureAwait(false); } private XmlFormatterOptions FormatterOptions { get; } diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlSerializationMvcOptionsSetup.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/XmlSerializationMvcOptionsSetup.cs similarity index 100% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlSerializationMvcOptionsSetup.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/XmlSerializationMvcOptionsSetup.cs diff --git a/src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlSerializationOutputFormatter.cs b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/XmlSerializationOutputFormatter.cs similarity index 100% rename from src/Cuemon.AspNetCore.Mvc.Formatters.Xml/XmlSerializationOutputFormatter.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/XmlSerializationOutputFormatter.cs diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/CacheableObjectResultExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc/CacheableObjectResultExtensions.cs index 21d0899c3..4caaea804 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/CacheableObjectResultExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc/CacheableObjectResultExtensions.cs @@ -1,7 +1,6 @@ using System; using Cuemon.AspNetCore.Mvc; using Cuemon.AspNetCore.Mvc.Filters.Cacheable; -using Cuemon.Data; using Cuemon.Data.Integrity; namespace Cuemon.Extensions.AspNetCore.Mvc @@ -54,7 +53,7 @@ public static ICacheableObjectResult MakeCacheable(this object instance, FuncAn implementation. /// /// - /// + /// /// /// /// @@ -110,7 +109,7 @@ public static ICacheableObjectResult MakeCacheable(this T instance, FuncAn implementation. /// /// - /// + /// /// /// /// diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/CacheBusting.cs b/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/CacheBusting.cs deleted file mode 100644 index f47b9bd6d..000000000 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/CacheBusting.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Cuemon.AspNetCore.Configuration; - -namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration -{ - /// - /// Represents a way to provide cache-busting capabilities. - /// - public abstract class CacheBusting : ICacheBusting - { - /// - /// Gets the version to be a part of the link you need cache-busting compatible. - /// - /// The version to be a part of the link you need cache-busting compatible. - public abstract string Version { get; } - } -} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Cuemon.Extensions.AspNetCore.Mvc.csproj b/src/Cuemon.Extensions.AspNetCore.Mvc/Cuemon.Extensions.AspNetCore.Mvc.csproj index 0771f747b..c998d3a12 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Cuemon.Extensions.AspNetCore.Mvc.csproj +++ b/src/Cuemon.Extensions.AspNetCore.Mvc/Cuemon.Extensions.AspNetCore.Mvc.csproj @@ -1,32 +1,15 @@ - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netcoreapp3.0;netstandard2.0 a50adf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.AspNetCore.Mvc Cuemon.Extensions.AspNetCore.Mvc - The Cuemon.Extensions.AspNetCore.Mvc assembly provides extension methods and general improvement to the Cuemon.AspNetCore assembly. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.AspNetCore.Mvc namespace contains both types and extension methods that complements the Cuemon.AspNetCore.Mvc namespace while being an addition to the Microsoft.AspNetCore.Mvc namespace. Provides a set of different cache busting strategies for ASP.NET Core MVC that can be easily customized. + extension-methods extensions add-assembly-cache-busting add-cache-busting add-dynamic-cache-busting use-when make-cacheable diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Cacheable/CacheableAsyncResultFilterListExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Cacheable/CacheableAsyncResultFilterExtensions.cs similarity index 95% rename from src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Cacheable/CacheableAsyncResultFilterListExtensions.cs rename to src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Cacheable/CacheableAsyncResultFilterExtensions.cs index cc7ef0862..8b2db73ba 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Cacheable/CacheableAsyncResultFilterListExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Cacheable/CacheableAsyncResultFilterExtensions.cs @@ -6,9 +6,9 @@ namespace Cuemon.Extensions.AspNetCore.Mvc.Filters.Cacheable { /// - /// Extension methods for the interface. + /// Extension methods for the interface. /// - public static class CacheableAsyncResultFilterListExtensions + public static class CacheableAsyncResultFilterExtensions { /// /// Adds a HTTP related filter to the list. diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Diagnostics/FaultResolverExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Diagnostics/FaultResolverExtensions.cs index ab44cfee7..beffff694 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Diagnostics/FaultResolverExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc/Filters/Diagnostics/FaultResolverExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using Cuemon.AspNetCore.Diagnostics; using Cuemon.AspNetCore.Http; -using Cuemon.AspNetCore.Mvc.Extensions.Filters.Diagnostics; using Cuemon.AspNetCore.Mvc.Filters.Diagnostics; namespace Cuemon.Extensions.AspNetCore.Mvc.Filters.Diagnostics diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..7120475f1 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt @@ -0,0 +1,9 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Breaking Changes +- RENAMED ToCacheableObjectResult{T} --> MakeCacheable{T} on the CacheableObjectResultExtensions class in the Cuemon.Extensions.AspNetCore.Mvc namespace (also included a non-generic variant: MakeCacheable) +  +# New Features +- ADDED ServiceCollectionExtensions class in the Cuemon.Extensions.AspNetCore.Mvc.Configuration namespace that consist of extension methods for the IServiceCollection interface: AddAssemblyCacheBusting, AddDynamicCacheBusting, AddCacheBusting{T} +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/ViewDataDictionaryExtensions.cs b/src/Cuemon.Extensions.AspNetCore.Mvc/ViewDataDictionaryExtensions.cs index f453ca8b1..36407f579 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/ViewDataDictionaryExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore.Mvc/ViewDataDictionaryExtensions.cs @@ -42,7 +42,7 @@ public static void AddBreadcrumbs(this ViewDataDictionary viewData, Controlle list.Add(bc); } - Decorator.Enclose(viewData).TryAddOrUpdate(BreadcrumbKey, list); + Decorator.Enclose(viewData).AddOrUpdate(BreadcrumbKey, list); } /// diff --git a/src/Cuemon.Extensions.AspNetCore/Builder/ApplicationBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Builder/ApplicationBuilderExtensions.cs deleted file mode 100644 index a2197e4bf..000000000 --- a/src/Cuemon.Extensions.AspNetCore/Builder/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using Cuemon.AspNetCore.Builder; -using Cuemon.AspNetCore.Hosting; -using Cuemon.AspNetCore.Http.Headers; -using Cuemon.AspNetCore.Http.Throttling; -using Microsoft.AspNetCore.Builder; - -namespace Cuemon.Extensions.AspNetCore.Builder -{ - /// - /// Extension methods for the interface. - /// - public static class ApplicationBuilderExtensions - { - /// - /// Adds a hosting environment HTTP header to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The middleware which need to be configured. - /// A reference to this instance after the operation has completed. - public static IApplicationBuilder UseHostingEnvironmentHeader(this IApplicationBuilder builder, Action setup = null) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - - /// - /// Adds a correlation identifier HTTP header to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The middleware which need to be configured. - /// A reference to this instance after the operation has completed. - /// Default HTTP header name is X-Correlation-ID. - public static IApplicationBuilder UseCorrelationIdentifierHeader(this IApplicationBuilder builder, Action setup = null) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - - /// - /// Adds a request identifier HTTP header to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The middleware which need to be configured. - /// A reference to this instance after the operation has completed. - /// Default HTTP header name is X-Request-ID. - public static IApplicationBuilder UseRequestIdentifierHeader(this IApplicationBuilder builder, Action setup = null) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - - /// - /// Adds a HTTP User-Agent header parser to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The middleware which need to be configured. - /// A reference to this instance after the operation has completed. - public static IApplicationBuilder UseUserAgentSentinel(this IApplicationBuilder builder, Action setup = null) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - - /// - /// Adds a custom rate limiting / throttling to the request execution pipeline. - /// - /// The type that provides the mechanisms to configure an application’s request pipeline. - /// The middleware which need to be configured. - /// A reference to this instance after the operation has completed. - public static IApplicationBuilder UseCustomThrottlingSentinel(this IApplicationBuilder builder, Action setup) - { - return ApplicationBuilderFactory.UseMiddlewareConfigurable(builder, setup); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/AssemblyCacheBusting.cs b/src/Cuemon.Extensions.AspNetCore/Configuration/AssemblyCacheBusting.cs similarity index 70% rename from src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/AssemblyCacheBusting.cs rename to src/Cuemon.Extensions.AspNetCore/Configuration/AssemblyCacheBusting.cs index d37c6e999..8d365ee78 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/AssemblyCacheBusting.cs +++ b/src/Cuemon.Extensions.AspNetCore/Configuration/AssemblyCacheBusting.cs @@ -1,8 +1,10 @@ using System; +using Cuemon.AspNetCore.Configuration; using Cuemon.Extensions.Data.Integrity; +using Cuemon.Security.Cryptography; using Microsoft.Extensions.Options; -namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration +namespace Cuemon.Extensions.AspNetCore.Configuration { /// /// Provides cache-busting capabilities from an Assembly. This class cannot be inherited. @@ -17,7 +19,10 @@ public sealed class AssemblyCacheBusting : CacheBusting public AssemblyCacheBusting(IOptions setup) { var options = setup.Value; - var version = options.Assembly?.GetCacheValidator(options.ReadByteForByteChecksum, o => o.Algorithm = options.Algorithm).Checksum.ToHexadecimalString() ?? Guid.NewGuid().ToString("N"); // fallback to Guid.NewGuid + var version = options.Assembly?.GetCacheValidator(() => UnkeyedHashFactory.CreateCrypto(options.Algorithm), o => + { + if (options.ReadByteForByteChecksum) { o.BytesToRead = int.MaxValue; } + }).Checksum.ToHexadecimalString() ?? Guid.NewGuid().ToString("N"); // fallback to Guid.NewGuid Version = Decorator.Enclose(version).ToCasing(options.PreferredCasing); } diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/AssemblyCacheBustingOptions.cs b/src/Cuemon.Extensions.AspNetCore/Configuration/AssemblyCacheBustingOptions.cs similarity index 89% rename from src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/AssemblyCacheBustingOptions.cs rename to src/Cuemon.Extensions.AspNetCore/Configuration/AssemblyCacheBustingOptions.cs index ce5337805..37da94a29 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/AssemblyCacheBustingOptions.cs +++ b/src/Cuemon.Extensions.AspNetCore/Configuration/AssemblyCacheBustingOptions.cs @@ -1,7 +1,8 @@ using System.Reflection; +using Cuemon.AspNetCore.Configuration; using Cuemon.Security.Cryptography; -namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration +namespace Cuemon.Extensions.AspNetCore.Configuration { /// /// Specifies options that is related to operations. @@ -25,7 +26,7 @@ public class AssemblyCacheBustingOptions : CacheBustingOptions /// /// /// - /// + /// /// /// /// @@ -36,7 +37,7 @@ public class AssemblyCacheBustingOptions : CacheBustingOptions public AssemblyCacheBustingOptions() { Assembly = Assembly.GetEntryAssembly(); - Algorithm = CryptoAlgorithm.Md5; + Algorithm = UnkeyedCryptoAlgorithm.Md5; ReadByteForByteChecksum = false; } @@ -50,7 +51,7 @@ public AssemblyCacheBustingOptions() /// Gets or sets the hash algorithm to use for the computation of . /// /// The hash algorithm to use for the computation of . - public CryptoAlgorithm Algorithm { get; set; } + public UnkeyedCryptoAlgorithm Algorithm { get; set; } /// /// Gets or sets a value indicating whether the will be read byte-for-byte when computing the checksum. diff --git a/src/Cuemon.Extensions.AspNetCore/Configuration/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Configuration/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..43beb86a2 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore/Configuration/ServiceCollectionExtensions.cs @@ -0,0 +1,43 @@ +using Cuemon.AspNetCore.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Cuemon.Extensions.AspNetCore.Configuration +{ + /// + /// Extension methods for the interface. + /// + public static class ServiceCollectionExtensions + { + /// + /// Adds an service to the specified . + /// + /// The to add services to. + /// An that can be used to further configure other services. + public static IServiceCollection AddAssemblyCacheBusting(this IServiceCollection services) + { + return services.AddCacheBusting(); + } + + /// + /// Adds a service to the specified . + /// + /// The to add services to. + /// An that can be used to further configure other services. + public static IServiceCollection AddDynamicCacheBusting(this IServiceCollection services) + { + return services.AddCacheBusting(); + } + + /// + /// Adds a cache-busting service to the specified . + /// + /// The to add services to. + /// An that can be used to further configure other services. + public static IServiceCollection AddCacheBusting(this IServiceCollection services) where T : class, ICacheBusting + { + Validator.ThrowIfNull(services, nameof(services)); + services.AddSingleton(); + return services; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore/Cuemon.Extensions.AspNetCore.csproj b/src/Cuemon.Extensions.AspNetCore/Cuemon.Extensions.AspNetCore.csproj index 257dab972..dfc529316 100644 --- a/src/Cuemon.Extensions.AspNetCore/Cuemon.Extensions.AspNetCore.csproj +++ b/src/Cuemon.Extensions.AspNetCore/Cuemon.Extensions.AspNetCore.csproj @@ -1,32 +1,15 @@ - netcoreapp3.0;netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netcoreapp3.0;netstandard2.0 a40adf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.AspNetCore Cuemon.Extensions.AspNetCore - The Cuemon.Extensions.AspNetCore assembly provides extension methods and general improvement to the Cuemon.AspNetCore assembly. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.AspNetCore namespace contains both types and extension methods that complements the Cuemon.AspNetCore namespace while being an addition to the Microsoft.AspNetCore namespace. Provides an in-memory implementation of a throttling cache for ASP.NET Core. + extension-methods extensions memory-throttling-cache use-hosting-environment-header use-correlation-identifier-header use-request-identifier-header use-user-agent-sentinel use-custom-throttling-sentinel @@ -36,9 +19,7 @@ - - - + \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore/Data/Integrity/CacheValidatorExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Data/Integrity/CacheValidatorExtensions.cs index c46f2a205..30fe1927c 100644 --- a/src/Cuemon.Extensions.AspNetCore/Data/Integrity/CacheValidatorExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore/Data/Integrity/CacheValidatorExtensions.cs @@ -16,7 +16,7 @@ public static class CacheValidatorExtensions public static EntityTagHeaderValue ToEntityTag(this CacheValidator validator) { Validator.ThrowIfNull(validator, nameof(validator)); - return validator.ToEntityTagHeaderValue(validator.Strength != EntityDataIntegrityStrength.Strong); + return validator.ToEntityTagHeaderValue(validator.Validation != EntityDataIntegrityValidation.Strong); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Diagnostics/ServiceCollectionExtensions.cs similarity index 58% rename from src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/ServiceCollectionExtensions.cs rename to src/Cuemon.Extensions.AspNetCore/Diagnostics/ServiceCollectionExtensions.cs index c0e6c67f1..06384cf3e 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc/Configuration/ServiceCollectionExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore/Diagnostics/ServiceCollectionExtensions.cs @@ -1,7 +1,8 @@ -using Cuemon.AspNetCore.Configuration; +using Cuemon.AspNetCore.Diagnostics; +using Cuemon.AspNetCore.Http.Throttling; using Microsoft.Extensions.DependencyInjection; -namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration +namespace Cuemon.Extensions.AspNetCore.Diagnostics { /// /// Extension methods for the interface. @@ -9,24 +10,24 @@ namespace Cuemon.Extensions.AspNetCore.Mvc.Configuration public static class ServiceCollectionExtensions { /// - /// Adds a cache-busting service to the specified based on a default instance of . + /// Adds a service to the specified . /// /// The to add services to. /// An that can be used to further configure other services. - public static IServiceCollection AddCacheBusting(this IServiceCollection services) + public static IServiceCollection AddServerTiming(this IServiceCollection services) { - return services.AddCacheBusting(); + return services.AddServerTiming(); } /// - /// Adds a cache-busting service to the specified . + /// Adds a throttling cache service to the specified . /// /// The to add services to. /// An that can be used to further configure other services. - public static IServiceCollection AddCacheBusting(this IServiceCollection services) where T : CacheBusting + public static IServiceCollection AddServerTiming(this IServiceCollection services) where T : class, IServerTiming { Validator.ThrowIfNull(services, nameof(services)); - services.AddSingleton(); + services.AddScoped(); return services; } } diff --git a/src/Cuemon.Extensions.AspNetCore/Hosting/ApplicationBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Hosting/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..34ef1a4c8 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore/Hosting/ApplicationBuilderExtensions.cs @@ -0,0 +1,25 @@ +using System; +using Cuemon.AspNetCore.Builder; +using Cuemon.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; + +namespace Cuemon.Extensions.AspNetCore.Hosting +{ + /// + /// Extension methods for the interface. + /// + public static class ApplicationBuilderExtensions + { + /// + /// Adds a hosting environment HTTP header to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The middleware which may be configured. + /// A reference to this instance after the operation has completed. + /// Default HTTP header name is X-Hosting-Environment. + public static IApplicationBuilder UseHostingEnvironment(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore/Http/HeaderDictionaryExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Http/HeaderDictionaryExtensions.cs index 2e180577d..8574127cc 100644 --- a/src/Cuemon.Extensions.AspNetCore/Http/HeaderDictionaryExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore/Http/HeaderDictionaryExtensions.cs @@ -17,10 +17,10 @@ public static class HeaderDictionaryExtensions /// The string to use as the key of the element to add. /// The string to use as the value of the element to add. /// if set to true an ASCII encoding conversion is applied to the . - public static bool TryAddOrUpdateHeader(this IHeaderDictionary dictionary, string key, StringValues value, bool useAsciiEncodingConversion = true) + public static void AddOrUpdateHeader(this IHeaderDictionary dictionary, string key, StringValues value, bool useAsciiEncodingConversion = true) { Validator.ThrowIfNull(dictionary, nameof(dictionary)); - return Decorator.Enclose(dictionary).TryAddOrUpdateHeader(key, value, useAsciiEncodingConversion); + Decorator.Enclose(dictionary).AddOrUpdateHeader(key, value, useAsciiEncodingConversion); } /// @@ -30,7 +30,7 @@ public static bool TryAddOrUpdateHeader(this IHeaderDictionary dictionary, strin /// The to copy. public static void AddOrUpdateHeaders(this IHeaderDictionary dictionary, HttpResponseHeaders responseHeaders) { - Decorator.Enclose(dictionary).TryAddOrUpdateHeaders(responseHeaders); + Decorator.Enclose(dictionary).AddOrUpdateHeaders(responseHeaders); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore/Http/Headers/ApplicationBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Http/Headers/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..dca7921fe --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore/Http/Headers/ApplicationBuilderExtensions.cs @@ -0,0 +1,48 @@ +using System; +using Cuemon.AspNetCore.Builder; +using Cuemon.AspNetCore.Http.Headers; +using Microsoft.AspNetCore.Builder; + +namespace Cuemon.Extensions.AspNetCore.Http.Headers +{ + /// + /// Extension methods for the interface. + /// + public static class ApplicationBuilderExtensions + { + /// + /// Adds a correlation identifier HTTP header to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The middleware which may be configured. + /// A reference to this instance after the operation has completed. + /// Default HTTP header name is X-Correlation-ID. + public static IApplicationBuilder UseCorrelationIdentifier(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + + /// + /// Adds a request identifier HTTP header to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The middleware which may be configured. + /// A reference to this instance after the operation has completed. + /// Default HTTP header name is X-Request-ID. + public static IApplicationBuilder UseRequestIdentifier(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + + /// + /// Adds a HTTP User-Agent header parser to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The middleware which may be configured. + /// A reference to this instance after the operation has completed. + public static IApplicationBuilder UseUserAgentSentinel(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore/Http/HttpResponseExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Http/HttpResponseExtensions.cs index aa9fae794..b5944ac03 100644 --- a/src/Cuemon.Extensions.AspNetCore/Http/HttpResponseExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore/Http/HttpResponseExtensions.cs @@ -24,10 +24,10 @@ public static class HttpResponseExtensions /// cannot be null -or- /// cannot be null. /// - public static void TryAddOrUpdateEntityTagHeader(this HttpResponse response, HttpRequest request, ChecksumBuilder builder, bool isWeak = false) + public static void AddOrUpdateEntityTagHeader(this HttpResponse response, HttpRequest request, ChecksumBuilder builder, bool isWeak = false) { Validator.ThrowIfNull(response, nameof(response)); - Decorator.Enclose(response).TryAddOrUpdateEntityTagHeader(request, builder, isWeak); + Decorator.Enclose(response).AddOrUpdateEntityTagHeader(request, builder, isWeak); } /// @@ -40,10 +40,10 @@ public static void TryAddOrUpdateEntityTagHeader(this HttpResponse response, Htt /// cannot be null -or- /// cannot be null. /// - public static void TryAddOrUpdateLastModifiedHeader(this HttpResponse response, HttpRequest request, DateTime lastModified) + public static void AddOrUpdateLastModifiedHeader(this HttpResponse response, HttpRequest request, DateTime lastModified) { Validator.ThrowIfNull(response, nameof(response)); - Decorator.Enclose(response).TryAddOrUpdateLastModifiedHeader(request, lastModified); + Decorator.Enclose(response).AddOrUpdateLastModifiedHeader(request, lastModified); } /// diff --git a/src/Cuemon.Extensions.AspNetCore/Http/Throttling/ApplicationBuilderExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Http/Throttling/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..d47730c00 --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore/Http/Throttling/ApplicationBuilderExtensions.cs @@ -0,0 +1,24 @@ +using System; +using Cuemon.AspNetCore.Builder; +using Cuemon.AspNetCore.Http.Throttling; +using Microsoft.AspNetCore.Builder; + +namespace Cuemon.Extensions.AspNetCore.Http.Throttling +{ + /// + /// Extension methods for the interface. + /// + public static class ApplicationBuilderExtensions + { + /// + /// Adds a HTTP requests rate limiting / throttling guard to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The middleware which may be configured. + /// A reference to after the operation has completed. + public static IApplicationBuilder UseThrottlingSentinel(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore/Http/Throttling/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.AspNetCore/Http/Throttling/ServiceCollectionExtensions.cs index 52b8da531..06decdfeb 100644 --- a/src/Cuemon.Extensions.AspNetCore/Http/Throttling/ServiceCollectionExtensions.cs +++ b/src/Cuemon.Extensions.AspNetCore/Http/Throttling/ServiceCollectionExtensions.cs @@ -9,13 +9,24 @@ namespace Cuemon.Extensions.AspNetCore.Http.Throttling public static class ServiceCollectionExtensions { /// - /// Adds a memory-based throttling cache service to the specified . + /// Adds a service to the specified . /// /// The to add services to. /// An that can be used to further configure other services. - public static IServiceCollection AddMemoryThrottling(this IServiceCollection services) + public static IServiceCollection AddMemoryThrottlingCache(this IServiceCollection services) { - services.AddSingleton(); + return services.AddThrottlingCache(); + } + + /// + /// Adds a throttling cache service to the specified . + /// + /// The to add services to. + /// An that can be used to further configure other services. + public static IServiceCollection AddThrottlingCache(this IServiceCollection services) where T : class, IThrottlingCache + { + Validator.ThrowIfNull(services, nameof(services)); + services.AddSingleton(); return services; } } diff --git a/src/Cuemon.Extensions.AspNetCore/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.AspNetCore/Properties/AssemblyInfo.cs index f6682d1b8..3ffaab395 100644 --- a/src/Cuemon.Extensions.AspNetCore/Properties/AssemblyInfo.cs +++ b/src/Cuemon.Extensions.AspNetCore/Properties/AssemblyInfo.cs @@ -1,6 +1,4 @@ -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; [assembly: ComVisible(false)] -[assembly: Guid("51e5cc05-3039-437a-8bdf-f489e9986921")] -[assembly: InternalsVisibleTo("Cuemon.Extensions.AspNetCore.Mvc, PublicKey=00240000048000009400000006020000002400005253413100040000010001002F66D8473F676F4E7B47400527D33951A774422DFFC3DF6D7F87C82E5694E9F3AA626D36BEBEA428AD5B800EFCF6CE87B73268F5A0125A7D38739D344703A1C48785AC1A45B1C27EDFDF2EB30BA2B3E3CEA92E5981C30F3A95685A680B7EBEE66F422D176CD1623019D5A05770B9BA498144B1134593BEA6F674F334CF2B90B0")] \ No newline at end of file +[assembly: Guid("51e5cc05-3039-437a-8bdf-f489e9986921")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.AspNetCore/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.AspNetCore/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..ec0962c3b --- /dev/null +++ b/src/Cuemon.Extensions.AspNetCore/Properties/PackageReleaseNotes.txt @@ -0,0 +1,23 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# Upgrade Steps +- HttpResponseMessageExtensions class was not merged to this assembly +  +# Breaking Changes +- RENAMED UseHostingEnvironmentHeader --> UseHostingEnvironment on the ApplicationBuilderExtensions class in the Cuemon.Extensions.AspNetCore.Builder namespace +- RENAMED UseCorrelationIdentifierHeader --> UseCorrelationIdentifier on the ApplicationBuilderExtensions class in the Cuemon.Extensions.AspNetCore.Builder namespace +- RENAMED UseRequestIdentifierHeader --> UseRequestIdentifier on the ApplicationBuilderExtensions class in the Cuemon.Extensions.AspNetCore.Builder namespace +- RENAMED UseCustomThrottlingSentinel --> UseThrottlingSentinel on the ApplicationBuilderExtensions class in the Cuemon.Extensions.AspNetCore.Builder namespace +- RENAMED ToEntityTag --> ToEntityTagHeaderValue on the ChecksumBuilderExtensions class in the Cuemon.Extensions.AspNetCore.Data.Integrity namespace +- RENAMED AddMemoryThrottling --> AddMemoryThrottlingCache on the ServiceCollectionExtensions class in the Cuemon.Extensions.AspNetCore.Http.Throttling namespace +- RENAMED SetEntityTagHeaderInformation --> AddOrUpdateEntityTagHeader on the HttpResponseExtensions class in the Cuemon.Extensions.AspNetCore.Http namespace +- RENAMED SetLastModifiedHeaderInformation --> AddOrUpdateLastModifiedHeader on the HttpResponseExtensions class in the Cuemon.Extensions.AspNetCore.Http namespace +- REMOVED IsSuccessStatusCode from the HttpResponseExtensions class in the Cuemon.Extensions.AspNetCore.Http namespace +- REMOVED IsNotModifiedStatusCode from the HttpResponseExtensions class in the Cuemon.Extensions.AspNetCore.Http namespace +  +# New Features +- EXTENDED ServiceCollectionExtensions class in the Cuemon.Extensions.AspNetCore.Http.Throttling namespace with one new extension method for the IServiceCollection interface: AddThrottlingCache{T} +- EXTENDED HttpResponseExtensions class in the Cuemon.Extensions.AspNetCore.Http namespace with one new extension method for the HttpResponse class: OnStartingInvokeTransformer +- ADDED Int32Extensions class in the Cuemon.Extensions.AspNetCore.Http namespace that consist of extension methods for the Int32 struct: IsInformationStatusCode, IsSuccessStatusCode, IsRedirectionStatusCode, IsNotModifiedStatusCode, IsClientErrorStatusCode, IsServerErrorStatusCode +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Collections.Generic/CollectionExtensions.cs b/src/Cuemon.Extensions.Collections.Generic/CollectionExtensions.cs index 76d641297..58c31d2ea 100644 --- a/src/Cuemon.Extensions.Collections.Generic/CollectionExtensions.cs +++ b/src/Cuemon.Extensions.Collections.Generic/CollectionExtensions.cs @@ -12,7 +12,7 @@ public static class CollectionExtensions /// Extends the specified to support iterating in partitions. /// /// The type of elements in the . - /// The collection to extend. + /// The to extend. /// The size of the partitions. /// An instance of . public static PartitionerCollection ToPartitioner(this ICollection collection, int partitionSize = 128) @@ -24,7 +24,7 @@ public static PartitionerCollection ToPartitioner(this ICollection coll /// Adds the elements of the specified to the . /// /// The type of elements in the . - /// The collection to extend. + /// The to extend. /// The sequence of elements that should be added to . public static void AddRange(this ICollection collection, params T[] source) { @@ -35,7 +35,7 @@ public static void AddRange(this ICollection collection, params T[] source /// Adds the elements of the specified to the . /// /// The type of elements in the . - /// The collection to extend. + /// The to extend. /// The sequence of elements that should be added to . public static void AddRange(this ICollection collection, IEnumerable source) { diff --git a/src/Cuemon.Extensions.Collections.Generic/Cuemon.Extensions.Collections.Generic.csproj b/src/Cuemon.Extensions.Collections.Generic/Cuemon.Extensions.Collections.Generic.csproj index 4c23d8675..d80c4c7ea 100644 --- a/src/Cuemon.Extensions.Collections.Generic/Cuemon.Extensions.Collections.Generic.csproj +++ b/src/Cuemon.Extensions.Collections.Generic/Cuemon.Extensions.Collections.Generic.csproj @@ -1,32 +1,15 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 190bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Extensions.Collections.Generic Cuemon.Extensions.Collections.Generic - The Cuemon.Extensions.Collections.Generic assembly provides extensions methods to the System.Collections.Generic namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Collections.Generic namespace contains extension methods that complements the Cuemon.Collections.Generic namespace while being an addition to the System.Collections.Generic namespace. + extension-methods extensions to-partitioner chunk shuffle random-or-default yield diff --git a/src/Cuemon.Extensions.Collections.Generic/DictionaryExtensions.cs b/src/Cuemon.Extensions.Collections.Generic/DictionaryExtensions.cs index f4b7cf10b..6e415d416 100644 --- a/src/Cuemon.Extensions.Collections.Generic/DictionaryExtensions.cs +++ b/src/Cuemon.Extensions.Collections.Generic/DictionaryExtensions.cs @@ -14,7 +14,7 @@ public static class DictionaryExtensions /// /// The type of the keys in the . /// The type of the values in the . - /// The dictionary to extend. + /// The to extend. /// The key of the value to get. /// Either the value associated with the specified or default() when the key does not exists. /// @@ -32,7 +32,7 @@ public static TValue GetValueOrDefault(this IDictionary /// The type of the keys in the . /// The type of the values in the . - /// The dictionary to extend. + /// The to extend. /// The key of the value to get. /// The function delegate that will provide a default value when the does not exists in the . /// Either the value associated with the specified or a default value through when the key does not exists. @@ -52,7 +52,7 @@ public static TValue GetValueOrDefault(this IDictionary /// The type of the keys in the . /// The type of the values in the . - /// The dictionary to extend. + /// The to extend. /// The key of the value to get. /// The function delegate that will resolve an alternate key from the specified . /// When this method returns, contains the value associated with the specified or the alternate key resolved from , if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized. @@ -71,7 +71,7 @@ public static bool TryGetValueOrFallback(this IDictionary /// The of the key in the resulting . /// The of the value in the resulting . - /// An to convert into a equivalent sequence. + /// The to extend. /// A equivalent sequence of . /// /// is null. @@ -85,7 +85,7 @@ public static IEnumerable> ToEnumerable /// /// Attempts to add the specified and to the . /// - /// The dictionary to extend. + /// The to extend. /// The key of the element to add. /// The value of the element to add. /// The function delegate that specifies the condition for adding the element. @@ -104,7 +104,7 @@ public static bool TryAdd(this IDictionary dictionar /// /// Attempts to add the specified and to the . /// - /// The dictionary to extend. + /// The to extend. /// The key of the element to add. /// The value of the element to add. /// true if the key/value pair was added to the enclosed of the successfully; otherwise, false. @@ -121,17 +121,17 @@ public static bool TryAdd(this IDictionary dictionar /// /// Attempts to add or update an existing element with the provided to the with the specified . /// - /// The dictionary to extend. + /// The to extend. /// The key of the element to add or update. /// The value of the element to add or update. /// /// cannot be null -or- /// cannot be null. /// - public static bool TryAddOrUpdate(this IDictionary dictionary, TKey key, TValue value) + public static void AddOrUpdate(this IDictionary dictionary, TKey key, TValue value) { Validator.ThrowIfNull(dictionary, nameof(dictionary)); - return Decorator.Enclose(dictionary).TryAddOrUpdate(key, value); + Decorator.Enclose(dictionary).AddOrUpdate(key, value); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Collections.Generic/EnumerableExtensions.cs b/src/Cuemon.Extensions.Collections.Generic/EnumerableExtensions.cs index 6d62d1a11..8eeb217b0 100644 --- a/src/Cuemon.Extensions.Collections.Generic/EnumerableExtensions.cs +++ b/src/Cuemon.Extensions.Collections.Generic/EnumerableExtensions.cs @@ -13,8 +13,8 @@ public static class EnumerableExtensions /// /// Returns a chunked sequence with a maximum of the specified . Default is 128. /// - /// The type of the elements of . - /// An to chunk into smaller slices for a batch run or similar. + /// The type of the elements of . + /// An to extend. /// The amount of elements to process at a time. /// An that contains no more than the specified of elements from the sequence. /// @@ -24,20 +24,20 @@ public static class EnumerableExtensions /// is less or equal to 0. /// /// The original is reduced equivalent to the number of elements in the returned sequence. - public static PartitionerEnumerable Chunk(this IEnumerable source, int size = 128) + public static PartitionerEnumerable Chunk(this IEnumerable source, int size = 128) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfLowerThanOrEqual(0, size, nameof(size)); - return new PartitionerEnumerable(source, size); + return new PartitionerEnumerable(source, size); } /// /// Shuffles the specified like a deck of cards. /// - /// The elements to be shuffled in the randomization process. - /// A sequence of with the shuffled . + /// An to extend. + /// A sequence of with the shuffled . /// Fisher–Yates shuffle: https://en.wikipedia.org/wiki/Fisher–Yates_shuffle - public static IEnumerable Shuffle(this IEnumerable source) + public static IEnumerable Shuffle(this IEnumerable source) { return source.Shuffle(Generate.RandomNumber); } @@ -45,11 +45,11 @@ public static IEnumerable Shuffle(this IEnumerable so /// /// Shuffles the specified like a deck of cards. /// - /// The elements to be shuffled in the randomization process. + /// An to extend. /// The function delegate that will handle the randomization of . - /// A sequence of with the shuffled . + /// A sequence of with the shuffled . /// Fisher–Yates shuffle: https://en.wikipedia.org/wiki/Fisher–Yates_shuffle - public static IEnumerable Shuffle(this IEnumerable source, Func randomizer) + public static IEnumerable Shuffle(this IEnumerable source, Func randomizer) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(randomizer, nameof(randomizer)); @@ -68,22 +68,22 @@ public static IEnumerable Shuffle(this IEnumerable so /// /// Returns ascending sorted elements from a sequence by using the default comparer to compare values. /// - /// The type of the elements of . - /// A sequence of values to order. + /// The type of the elements of . + /// An to extend. /// An that contains ascending sorted elements from the source sequence. - public static IEnumerable OrderBy(this IEnumerable source) + public static IEnumerable OrderBy(this IEnumerable source) { - return source.OrderBy(Comparer.Default); + return source.OrderBy(Comparer.Default); } /// /// Returns ascending sorted elements from a sequence by using a specified to compare values. /// - /// The type of the elements of . - /// A sequence of values to order. + /// The type of the elements of . + /// An to extend. /// An to compare values. /// An that contains ascending sorted elements from the source sequence. - public static IEnumerable OrderBy(this IEnumerable source, IComparer comparer) + public static IEnumerable OrderBy(this IEnumerable source, IComparer comparer) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(comparer, nameof(comparer)); @@ -93,22 +93,22 @@ public static IEnumerable OrderBy(this IEnumerable so /// /// Returns descending sorted elements from a sequence by using the default comparer to compare values. /// - /// The type of the elements of . - /// A sequence of values to order. + /// The type of the elements of . + /// An to extend. /// An that contains descending sorted elements from the source sequence. - public static IEnumerable OrderByDescending(this IEnumerable source) + public static IEnumerable OrderByDescending(this IEnumerable source) { - return source.OrderByDescending(Comparer.Default); + return source.OrderByDescending(Comparer.Default); } /// /// Returns descending sorted elements from a sequence by using a specified to compare values. /// - /// The type of the elements of . - /// A sequence of values to order. + /// The type of the elements of . + /// An to extend. /// An to compare values. /// An that contains descending sorted elements from the source sequence. - public static IEnumerable OrderByDescending(this IEnumerable source, IComparer comparer) + public static IEnumerable OrderByDescending(this IEnumerable source, IComparer comparer) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(comparer, nameof(comparer)); @@ -118,13 +118,13 @@ public static IEnumerable OrderByDescending(this IEnumerable /// Returns a random element of a sequence of elements, or a default value if no element is found. /// - /// The type of the elements of . - /// The to return a random element of. + /// The type of the elements of . + /// An to extend. /// default if is empty; otherwise, a random element of . - public static TSource RandomOrDefault(this IEnumerable source) + public static T RandomOrDefault(this IEnumerable source) { Validator.ThrowIfNull(source, nameof(source)); - var collection = source as ICollection ?? new List(source); + var collection = source as ICollection ?? new List(source); return collection.Count == 0 ? default : collection.ElementAt(Generate.RandomNumber(collection.Count)); } @@ -144,7 +144,7 @@ public static IEnumerable Yield(this T value) /// /// The type of keys in the . /// The type of values in the . - /// The sequence to create a from. + /// An to extend. /// A that is equivalent to the specified sequence. /// /// is null. @@ -162,7 +162,7 @@ public static IDictionary ToDictionary(this IEnumera /// /// The type of keys in the . /// The type of values in the . - /// The sequence to create a from. + /// An to extend. /// The implementation to use when comparing keys. /// A that is equivalent to the specified sequence. /// @@ -187,23 +187,23 @@ public static IDictionary ToDictionary(this IEnumera /// /// Extends the specified to support iterating in partitions. /// - /// The type of elements in the . - /// The sequence to extend. + /// The type of elements in the . + /// An to extend. /// The size of the partitions. /// An instance of . - public static PartitionerEnumerable ToPartitioner(this IEnumerable source, int partitionSize = 128) + public static PartitionerEnumerable ToPartitioner(this IEnumerable source, int partitionSize = 128) { - return new PartitionerEnumerable(source, partitionSize); + return new PartitionerEnumerable(source, partitionSize); } /// /// Converts the specified to a paged data sequence. /// - /// The type of the elements of . - /// The source of the sequence to make pageable. + /// The type of the elements of . + /// An to extend. /// An instance of . /// The starting page is set to 1 and the page size is determined by . - public static PagedCollection ToPagedCollection(this IEnumerable source) + public static PagedCollection ToPagedCollection(this IEnumerable source) { return ToPagedCollection(source, 1); } @@ -211,12 +211,12 @@ public static PagedCollection ToPagedCollection(this IEnumerab /// /// Converts the specified to a paged data sequence initialized with starting . /// - /// The type of the elements of . - /// The source of the sequence to make pageable. + /// The type of the elements of . + /// An to extend. /// The page number to start with. /// An instance of . /// The page size is determined by . - public static PagedCollection ToPagedCollection(this IEnumerable source, int pageNumber) + public static PagedCollection ToPagedCollection(this IEnumerable source, int pageNumber) { return ToPagedCollection(source, pageNumber, PagedSettings.DefaultPageSize); } @@ -224,12 +224,12 @@ public static PagedCollection ToPagedCollection(this IEnumerab /// /// Converts the specified to a paged data sequence initialized with starting and . /// - /// The type of the elements of . - /// The source of the sequence to make pageable. + /// The type of the elements of . + /// An to extend. /// The page number to start with. /// The number of elements a page can contain. /// An instance of . - public static PagedCollection ToPagedCollection(this IEnumerable source, int pageNumber, int pageSize) + public static PagedCollection ToPagedCollection(this IEnumerable source, int pageNumber, int pageSize) { return ToPagedCollection(source, new PagedSettings() { PageNumber = pageNumber, PageSize = pageSize }); } @@ -237,25 +237,25 @@ public static PagedCollection ToPagedCollection(this IEnumerab /// /// Converts the specified to a paged data sequence initialized with . /// - /// The type of the elements of . - /// The source of the sequence to make pageable. + /// The type of the elements of . + /// An to extend. /// The settings that specifies the conditions of the converted . /// An instance of initialized with . - public static PagedCollection ToPagedCollection(this IEnumerable source, PagedSettings settings) + public static PagedCollection ToPagedCollection(this IEnumerable source, PagedSettings settings) { - return new PagedCollection(source, settings); + return new PagedCollection(source, settings); } /// /// Converts the specified to a paged data sequence initialized with starting and . /// - /// The type of the elements of . - /// The source of the sequence to make pageable. + /// The type of the elements of . + /// An to extend. /// The page number to start with. /// The number of elements a page can contain. /// The total number of elements in the sequence. /// An instance of . - public static PagedCollection ToPagedCollection(this IEnumerable source, int pageNumber, int pageSize, int totalElementCount) + public static PagedCollection ToPagedCollection(this IEnumerable source, int pageNumber, int pageSize, int totalElementCount) { return ToPagedCollection(source, new PagedSettings() { PageNumber = pageNumber, PageSize = pageSize }, totalElementCount); } @@ -263,14 +263,14 @@ public static PagedCollection ToPagedCollection(this IEnumerab /// /// Converts the specified to a paged data sequence initialized with . /// - /// The type of the elements of . - /// The source of the sequence to make pageable. + /// The type of the elements of . + /// An to extend. /// The settings that specifies the conditions of the converted . /// The total number of elements in the sequence. /// An instance of initialized with . - public static PagedCollection ToPagedCollection(this IEnumerable source, PagedSettings settings, int totalElementCount) + public static PagedCollection ToPagedCollection(this IEnumerable source, PagedSettings settings, int totalElementCount) { - return new PagedCollection(source, settings, totalElementCount); + return new PagedCollection(source, settings, totalElementCount); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Collections.Generic/ListExtensions.cs b/src/Cuemon.Extensions.Collections.Generic/ListExtensions.cs index 57f850791..c1d611813 100644 --- a/src/Cuemon.Extensions.Collections.Generic/ListExtensions.cs +++ b/src/Cuemon.Extensions.Collections.Generic/ListExtensions.cs @@ -12,7 +12,7 @@ public static class ListExtensions /// Removes the first occurrence of a specific object from the . /// /// The type of elements in the . - /// The extended list. + /// The to extend. /// The function delegate that defines the conditions of the element to remove. /// true if item was successfully removed from the , false otherwise. public static bool Remove(this IList list, Func predicate) @@ -29,64 +29,64 @@ public static bool Remove(this IList list, Func predicate) } /// - /// Determines whether the of the is within the range of the . + /// Determines whether the of the is within the range of the . /// - /// The type of elements in the . - /// The elements of the . + /// The type of elements in the . + /// The to extend. /// The index to find. - /// true if the specified is within the range of the ; otherwise, false. + /// true if the specified is within the range of the ; otherwise, false. /// - /// is null. + /// is null. /// - public static bool HasIndex(this IList elements, int index) + public static bool HasIndex(this IList list, int index) { - Validator.ThrowIfNull(elements, nameof(elements)); - return ((elements.Count - 1) >= index); + Validator.ThrowIfNull(list, nameof(list)); + return ((list.Count - 1) >= index); } /// - /// Returns the next element of relative to , or the last element of if is equal or greater than . + /// Returns the next element of relative to , or the last element of if is equal or greater than . /// - /// The type of elements in the . - /// The elements, relative to , to return the next element of. + /// The type of elements in the . + /// The to extend. /// The index of which to advance to the next element from. /// - /// is null. + /// is null. /// /// /// is less than 0. /// - /// default(TSource) if is equal or greater than ; otherwise the next element of relative to . - public static TSource Next(this IList elements, int index) + /// default(TSource) if is equal or greater than ; otherwise the next element of relative to . + public static T Next(this IList list, int index) { - Validator.ThrowIfNull(elements, nameof(elements)); + Validator.ThrowIfNull(list, nameof(list)); Validator.ThrowIfLowerThan(index, 0, nameof(index)); var nextIndex = index + 1; - if (nextIndex >= elements.Count) { return default; } - return elements[nextIndex]; + if (nextIndex >= list.Count) { return default; } + return list[nextIndex]; } /// - /// Returns the previous element of relative to , or the first or last element of if is equal, greater or lower than . + /// Returns the previous element of relative to , or the first or last element of if is equal, greater or lower than . /// - /// The type of elements in the . - /// The elements, relative to , to return the previous element of. + /// The type of elements in the . + /// The to extend. /// The index of which to advance to the previous element from. /// - /// is null. + /// is null. /// /// /// is less than 0. /// - /// default(TSource) if is equal, greater or lower than ; otherwise the previous element of relative to . - public static TSource Previous(this IList elements, int index) + /// default(TSource) if is equal, greater or lower than ; otherwise the previous element of relative to . + public static T Previous(this IList list, int index) { - Validator.ThrowIfNull(elements, nameof(elements)); + Validator.ThrowIfNull(list, nameof(list)); Validator.ThrowIfLowerThan(index, 0, nameof(index)); var previousIndex = index - 1; if (previousIndex < 0) { return default; } - if (previousIndex >= elements.Count) { return default; } - return elements[previousIndex]; + if (previousIndex >= list.Count) { return default; } + return list[previousIndex]; } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Collections.Generic/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Collections.Generic/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..9ec26be35 --- /dev/null +++ b/src/Cuemon.Extensions.Collections.Generic/Properties/PackageReleaseNotes.txt @@ -0,0 +1,9 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED CollectionExtensions class in the Cuemon.Extensions.Collections.Generic namespace that consist of extension methods for the ICollection{T} interface: ToPartitioner{T}, AddRange{T} +- ADDED DictionaryExtensions class in the Cuemon.Extensions.Collections.Generic namespace that consist of extension methods for the IDictionary{TKey, TValue} interface: GetValueOrDefault{TKey, TValue}, TryGetValueOrFallback{TKey, TValue}, ToEnumerable{TKey, TValue}, TryAdd{TKey, TValue}, TryAddOrUpdate{TKey, TValue} +- ADDED EnumerableExtensions class in the Cuemon.Extensions.Collections.Generic namespace that consist of extension methods for the IEnumerable{T} interface: Chunk{T}, Shuffle{T}, OrderBy{T}, OrderByDescending{T}, RandomOrDefault{T}, Yield{T}, ToDictionary{TKey, TValue}, ToPartitioner{T}, ToPagedCollection{T} +- ADDED ListExtensions class in the Cuemon.Extensions.Collections.Generic namespace that consist of extension methods for the IList{T} interface: Remove{T}, HasIndex{T}, Next{T}, Previous{T} +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Collections.Specialized/Cuemon.Extensions.Collections.Specialized.csproj b/src/Cuemon.Extensions.Collections.Specialized/Cuemon.Extensions.Collections.Specialized.csproj index 35701dc3c..c83ad2017 100644 --- a/src/Cuemon.Extensions.Collections.Specialized/Cuemon.Extensions.Collections.Specialized.csproj +++ b/src/Cuemon.Extensions.Collections.Specialized/Cuemon.Extensions.Collections.Specialized.csproj @@ -1,32 +1,15 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 010bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.Collections.Specialized Cuemon.Extensions.Collections.Specialized - The Cuemon.Extensions.Collections.Specialized assembly is a member of the Cuemon .NET Standard family and provides enhancements to the System.Collections.Specialized assembly. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Collections.Specialized namespace contains extension methods that complements the Cuemon.Collections.Specialized namespace while being an addition to the System.Collections.Specialized namespace. + extension-methods extensions to-name-value-collection to-dictionary diff --git a/src/Cuemon.Extensions.Collections.Specialized/DictionaryExtensions.cs b/src/Cuemon.Extensions.Collections.Specialized/DictionaryExtensions.cs index fbd81622b..a6e143006 100644 --- a/src/Cuemon.Extensions.Collections.Specialized/DictionaryExtensions.cs +++ b/src/Cuemon.Extensions.Collections.Specialized/DictionaryExtensions.cs @@ -13,7 +13,7 @@ public static class DictionaryExtensions /// /// Creates a from the specified . /// - /// An to convert into an equivalent. + /// An to extend. /// The which may be configured. /// A that is equivalent to the specified . /// diff --git a/src/Cuemon.Extensions.Collections.Specialized/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Collections.Specialized/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..1cfc9a51f --- /dev/null +++ b/src/Cuemon.Extensions.Collections.Specialized/Properties/PackageReleaseNotes.txt @@ -0,0 +1,7 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED DictionaryExtensions class in the Cuemon.Extensions.Collections.Specialized namespace that consist of extension methods for the IDictionary{string, string[]} interface: ToNameValueCollection +- ADDED NameValueCollectionExtensions class in the Cuemon.Extensions.Collections.Specialized namespace that consist of extension methods for the NameValueCollection class: ContainsKey, ToDictionary +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Core/ConditionExtensions.cs b/src/Cuemon.Extensions.Core/ConditionExtensions.cs index bde1b8ae3..039b91def 100644 --- a/src/Cuemon.Extensions.Core/ConditionExtensions.cs +++ b/src/Cuemon.Extensions.Core/ConditionExtensions.cs @@ -10,33 +10,17 @@ public static class ConditionExtensions /// /// Determines whether there is a set difference between and . /// - /// The to extend. + /// The to extend. /// The value where characters that are not also in will be returned. /// The value to compare with . /// The set difference between and or if no difference. /// /// true if there is a set difference between and ; otherwise false. /// - public static bool HasDifference(this Condition condition, string first, string second, out string difference) + public static bool HasDifference(this Condition _, string first, string second, out string difference) { difference = first.Difference(second); return difference.Any(); } - - /// - /// Determines whether there is a distinct set difference between and . - /// - /// The to extend. - /// The value where distinct characters that are not also in will be returned. - /// The value to distinctively compare with . - /// The distinct set difference between and or if no difference. - /// - /// true if there is a distinct set difference between and ; otherwise false. - /// - public static bool HasDistinctDifference(this Condition condition, string first, string second, out string difference) - { - difference = first.DistinctDifference(second); - return difference.Any(); - } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj b/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj index 5c77a6457..1331aec56 100644 --- a/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj +++ b/src/Cuemon.Extensions.Core/Cuemon.Extensions.Core.csproj @@ -1,33 +1,16 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 020bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions Cuemon.Extensions.Core Cuemon.Extensions - The Cuemon.Extensions.Core assembly is a member of the Cuemon .NET Standard family which provides extension methods to the Cuemon.Core assembly. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions namespace contains extension methods that complements the Cuemon namespace while being an addition to the System namespace. + extension-methods extensions to-byte-array flatten to-encoded-string configure create-instance action-delegate options-pattern diff --git a/src/Cuemon.Extensions.Core/GlobalSuppressions.cs b/src/Cuemon.Extensions.Core/GlobalSuppressions.cs index d4253e350..60bba6569 100644 --- a/src/Cuemon.Extensions.Core/GlobalSuppressions.cs +++ b/src/Cuemon.Extensions.Core/GlobalSuppressions.cs @@ -4,7 +4,5 @@ // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.ValidatorExtensions.IfHasDifference(Cuemon.Validator,System.String,System.String,System.String,System.String)")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.ValidatorExtensions.IfHasDistinctDifference(Cuemon.Validator,System.String,System.String,System.String,System.String)")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.ValidatorExtensions.IfHasNotDifference(Cuemon.Validator,System.String,System.String,System.String,System.String)")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.ValidatorExtensions.IfHasNotDistinctDifference(Cuemon.Validator,System.String,System.String,System.String,System.String)")] \ No newline at end of file +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.ValidatorExtensions.HasDifference(Cuemon.Validator,System.String,System.String,System.String,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.ValidatorExtensions.NoDifference(Cuemon.Validator,System.String,System.String,System.String,System.String)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.ValidatorExtensions.IfHasNotDistinctDifference(Cuemon.Validator,System.String,System.String,System.String,System.String)")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Core/ObjectExtensions.cs b/src/Cuemon.Extensions.Core/ObjectExtensions.cs index d4597b3b4..f9b6ed8ad 100644 --- a/src/Cuemon.Extensions.Core/ObjectExtensions.cs +++ b/src/Cuemon.Extensions.Core/ObjectExtensions.cs @@ -65,7 +65,7 @@ public static int GetHashCode32(this IEnumerable convertibles) where T : I /// /// A sequence of objects implementing the interface. /// A 64-bit signed integer that is the hash code of . - public static long GetHashCode64(this IEnumerable convertibles) where T : struct, IConvertible + public static long GetHashCode64(this IEnumerable convertibles) where T : IConvertible { return Generate.HashCode64(convertibles.Cast()); } @@ -101,21 +101,14 @@ public static T Adjust(this T value, Func tweaker) /// /// Determines whether the specified source is a nullable . /// - /// The type of the of . - /// The source type to check for nullable . + /// The type of the of . + /// The source type to check for nullable . /// /// true if the specified source is nullable; otherwise, false. /// - public static bool IsNullable(this T source) { return false; } - - /// - /// Determines whether the specified source is a nullable . - /// - /// The type of the of . - /// The source type to check for nullable . - /// - /// true if the specified source is nullable; otherwise, false. - /// - public static bool IsNullable(this T? source) where T : struct { return true; } + public static bool IsNullable(this T _) + { + return typeof(T).IsNullable(); + } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Core/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Core/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..8dd068359 --- /dev/null +++ b/src/Cuemon.Extensions.Core/Properties/PackageReleaseNotes.txt @@ -0,0 +1,19 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED ActionExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Action delegate: Configure{TOptions}, CreateInstance{T} +- ADDED ByteExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Byte struct: ToEncodedString, ToHexadecimalString, ToBinaryString, ToUrlEncodedBase64String, ToBase64String, TryDetectUnicodeEncoding +- ADDED CharExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Char struct: ToEnumerable, FromChars +- ADDED ConditionExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Condition class: HasDifference +- ADDED DateTimeExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the DateTime struct: ToUnixEpochTime, ToUtcKind, ToLocalKind, ToDefaultKind, IsWithinRange, IsTimeOfDayNight, IsTimeOfDayMorning, IsTimeOfDayForenoon, IsTimeOfDayAfternoon, IsTimeOfDayEvening, Floor, Ceiling, Round +- ADDED DoubleExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Double struct: FromUnixEpochTime, ToTimeSpan, Factorial, RoundOff +- ADDED ExceptionExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Exception class: Flatten +- ADDED IntegerExtensions class in the Cuemon.Extensions namespace that consist of extension methods for signed integers: Min, Max, IsPrime, IsCountableSequence, IsEven, IsOdd +- ADDED MappingExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Mapping class: Add +- ADDED ObjectExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Object class: UseWrapper{T}, As{T}, GetHashCode32{T}, GetHashCode64{T}, ToDelimitedString{T}, Adjust{T}, IsNullable{T} +- ADDED StringExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the String class: Difference, ToCharArray, ToByteArray, FromUrlEncodedBase64, ToGuid, FromBinaryDigits, FromBase64, Join, ToCasing, ToUri, IsNullOrEmpty, IsNullOrWhiteSpace, IsEmailAddress, IsGuid, IsHex, IsNumeric, IsBase64, IsCountableSequence, SplitDelimited, Count, RemoveAll, ReplaceAll, JsEscape, JsUnescape, ContainsAny, ContainsAll, EqualsAny, StartsWith, TrimAll, IsSequenceOf{T}, FromHexadecimal, ToHexadecimal, ToEnum{TEnum}, ToTimeSpan, SubstringBefore, Chunk, SuffixWith, SuffixWithForwardingSlash, PrefixWith +- ADDED TimeSpanExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the TimeSpan struct: GetTotalNanoseconds, GetTotalMicroseconds, Floor, Ceiling, Round +- ADDED TypeExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Type class: ToFriendlyName, ToTypeCode, HasEqualityComparerImplementation, HasComparableImplementation, HasComparerImplementation, HasEnumerableImplementation, HasDictionaryImplementation, HasKeyValuePairImplementation, IsNullable, HasAnonymousCharacteristics, IsComplex, IsSimple, GetDefaultValue, HasTypes, HasInterfaces, HasAttributes +- ADDED ValidatorExtensions class in the Cuemon.Extensions namespace that consist of extension methods for the Validator class: HasDifference, NoDifference +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Core/StringExtensions.cs b/src/Cuemon.Extensions.Core/StringExtensions.cs index c474a9fc6..2e08a0273 100644 --- a/src/Cuemon.Extensions.Core/StringExtensions.cs +++ b/src/Cuemon.Extensions.Core/StringExtensions.cs @@ -21,26 +21,10 @@ public static class StringExtensions /// The value to compare with . /// >A that contains the set difference between and or if no difference. public static string Difference(this string first, string second) - { - return DifferenceCore(first, second); - } - - /// - /// Returns the distinct set difference between and or if no difference. - /// - /// The value where distinct characters that are not also in will be returned. - /// The value to distinctively compare with . - /// >A that contains the distinct set difference between and or if no difference. - public static string DistinctDifference(this string first, string second) - { - return DifferenceCore(first, second, true); - } - - private static string DifferenceCore(string first, string second, bool distinct = false) { if (first == null) { first = string.Empty; } if (second == null) { second = string.Empty; } - return distinct ? string.Concat(second.Distinct().Except(first.Distinct())) : string.Concat(second.Except(first)); + return string.Concat(second.Except(first)); } /// @@ -96,7 +80,7 @@ public static byte[] ToByteArray(this string input, Action setu /// has illegal base64 characters. /// /// - public static byte[] FromUrlEncodedBase64String(this string input) + public static byte[] FromUrlEncodedBase64(this string input) { return ParserFactory.FromUrlEncodedBase64().Parse(input); } @@ -117,7 +101,7 @@ public static byte[] FromUrlEncodedBase64String(this string input) /// The specified was not recognized to be a GUID. /// /// - public static Guid ToGuid(string input, Action setup = null) + public static Guid ToGuid(this string input, Action setup = null) { return ParserFactory.FromGuid().Parse(input, setup); } @@ -483,7 +467,7 @@ public static string JsEscape(this string value) { if (DoEscapeOrUnescape(character)) { - builder.AppendFormat(CultureInfo.InvariantCulture, character < byte.MaxValue ? "%{0:x2}" : "%u{0:x4}", (uint)character); + builder.AppendFormat(CultureInfo.InvariantCulture, character < byte.MaxValue ? "%{0:X2}" : "%u{0:X4}", (uint)character); } else { @@ -513,7 +497,7 @@ public static string JsUnescape(this string value) { if (DoEscapeOrUnescape(i)) { - builder.Replace(string.Format(CultureInfo.InvariantCulture, "%{0:x2}", i), Convert.ToChar(i).ToString()); + builder.Replace(string.Format(CultureInfo.InvariantCulture, "%{0:X2}", i), Convert.ToChar(i).ToString()); } } return builder.ToString(); diff --git a/src/Cuemon.Extensions.Core/TypeExtensions.cs b/src/Cuemon.Extensions.Core/TypeExtensions.cs index 427920d57..d9441e242 100644 --- a/src/Cuemon.Extensions.Core/TypeExtensions.cs +++ b/src/Cuemon.Extensions.Core/TypeExtensions.cs @@ -131,7 +131,8 @@ public static bool HasKeyValuePairImplementation(this Type type) public static bool IsNullable(this Type type) { Validator.ThrowIfNull(type, nameof(type)); - return Decorator.Enclose(type).IsNullable(); + if (!type.IsValueType) { return false; } + return Nullable.GetUnderlyingType(type) != null; } /// @@ -204,7 +205,7 @@ public static bool HasTypes(this Type type, params Type[] targets) } /// - /// Determines whether the specified contains one or more of the target types specified throughout this member's inheritance chain. + /// Determines whether the specified contains one or more of the target interfaces specified throughout this member's inheritance chain. /// /// The to extend. /// The target interface types to be matched against. diff --git a/src/Cuemon.Extensions.Core/ValidatorExtensions.cs b/src/Cuemon.Extensions.Core/ValidatorExtensions.cs index e3d29cc6e..e72933724 100644 --- a/src/Cuemon.Extensions.Core/ValidatorExtensions.cs +++ b/src/Cuemon.Extensions.Core/ValidatorExtensions.cs @@ -18,7 +18,7 @@ public static class ValidatorExtensions /// /// There is a difference between and . /// - public static void IfHasDifference(this Validator validator, string first, string second, string paramName, string message = null) + public static void HasDifference(this Validator validator, string first, string second, string paramName, string message = null) { if (message == null) { message = FormattableString.Invariant($"Specified arguments has a difference between {nameof(second)} and {nameof(first)}."); } try @@ -31,30 +31,6 @@ public static void IfHasDifference(this Validator validator, string first, strin } } - /// - /// Validates and throws an if there is a distinct difference between and . - /// - /// The to extend. - /// The value that specifies valid characters. - /// The value to distinctively compare with . - /// The name of the parameter that caused the exception. - /// A message that describes the error. - /// - /// There is a distinct difference between and . - /// - public static void IfHasDistinctDifference(this Validator validator, string first, string second, string paramName, string message = null) - { - if (message == null) { message = FormattableString.Invariant($"Specified arguments has a distinct difference between {nameof(second)} and {nameof(first)}."); } - try - { - validator.ThrowWhenCondition(c => c.IsTrue((out string invalidCharacters) => Condition.Query.HasDistinctDifference(first, second, out invalidCharacters)).Create(invalidCharacters => new ArgumentOutOfRangeException(paramName, invalidCharacters, message)).TryThrow()); - } - catch (ArgumentOutOfRangeException ex) - { - throw ex; - } - } - /// /// Validates and throws an if there is no difference between and . /// @@ -66,7 +42,7 @@ public static void IfHasDistinctDifference(this Validator validator, string firs /// /// There is no difference between and . /// - public static void IfHasNotDifference(this Validator validator, string first, string second, string paramName, string message = null) + public static void NoDifference(this Validator validator, string first, string second, string paramName, string message = null) { if (message == null) { message = FormattableString.Invariant($"Specified arguments does not have a difference between {nameof(second)} and {nameof(first)}."); } try @@ -78,29 +54,5 @@ public static void IfHasNotDifference(this Validator validator, string first, st throw ex; } } - - /// - /// Validates and throws an if there is not a distinct difference between and . - /// - /// The to extend. - /// The value that specifies valid characters. - /// The value to distinctively compare with . - /// The name of the parameter that caused the exception. - /// A message that describes the error. - /// - /// There is not a distinct difference between and . - /// - public static void IfHasNotDistinctDifference(this Validator validator, string first, string second, string paramName, string message = null) - { - if (message == null) { message = FormattableString.Invariant($"Specified arguments does not have a distinct difference between {nameof(second)} and {nameof(first)}."); } - try - { - validator.ThrowWhenCondition(c => c.IsFalse(() => Condition.Query.HasDistinctDifference(first, second, out _)).Create(() => new ArgumentOutOfRangeException(paramName, message)).TryThrow()); - } - catch (ArgumentOutOfRangeException ex) - { - throw ex; - } - } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Data.Integrity/AssemblyExtensions.cs b/src/Cuemon.Extensions.Data.Integrity/AssemblyExtensions.cs index 1fb2bc2f9..05fe491fa 100644 --- a/src/Cuemon.Extensions.Data.Integrity/AssemblyExtensions.cs +++ b/src/Cuemon.Extensions.Data.Integrity/AssemblyExtensions.cs @@ -2,7 +2,7 @@ using System.IO; using System.Reflection; using Cuemon.Data.Integrity; -using Cuemon.IO; +using Cuemon.Security; namespace Cuemon.Extensions.Data.Integrity { @@ -15,20 +15,17 @@ public static class AssemblyExtensions /// Returns a from the specified . /// /// The assembly to resolve a from. - /// true to read the byte-for-byte to promote a strong integrity checksum; false to read common properties of the for a weak (but reliable) integrity checksum. - /// The which may be configured. - /// A that fully represents the integrity of the specified . - public static CacheValidator GetCacheValidator(this Assembly assembly, bool readByteForByteChecksum = false, Action setup = null) + /// The function delegate that is invoked to produce the . + /// The which may be configured. + /// A that represents the integrity of the specified . + public static CacheValidator GetCacheValidator(this Assembly assembly, Func hashFactory = null, Action setup = null) { if (assembly == null || assembly.IsDynamic) { return CacheValidator.Default; } var assemblyHashCode64 = Generate.HashCode64(assembly.FullName); var assemblyLocation = assembly.Location; - return string.IsNullOrEmpty(assemblyLocation) ? new CacheValidator(DateTime.MinValue, DateTime.MaxValue, assemblyHashCode64, setup) : new FileInfo(assemblyLocation).GetCacheValidator(Patterns.ConfigureExchange(setup, (cvo, fco) => - { - fco.BytesToRead = readByteForByteChecksum ? int.MaxValue : 0; - fco.Algorithm = cvo.Algorithm; - fco.Method = cvo.Method; - })).CombineWith(assemblyHashCode64); + return string.IsNullOrEmpty(assemblyLocation) + ? new CacheValidator(new EntityInfo(DateTime.MinValue, DateTime.MaxValue, Convertible.GetBytes(assemblyHashCode64)), hashFactory) + : new FileInfo(assemblyLocation).GetCacheValidator(hashFactory, setup).CombineWith(assemblyHashCode64); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Data.Integrity/ChecksumBuilderExtensions.cs b/src/Cuemon.Extensions.Data.Integrity/ChecksumBuilderExtensions.cs index 0a2df9302..3b98a4dbd 100644 --- a/src/Cuemon.Extensions.Data.Integrity/ChecksumBuilderExtensions.cs +++ b/src/Cuemon.Extensions.Data.Integrity/ChecksumBuilderExtensions.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using Cuemon.Data.Integrity; +using Cuemon.Data.Integrity; namespace Cuemon.Extensions.Data.Integrity { @@ -13,128 +11,120 @@ public static class ChecksumBuilderExtensions /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// A array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params double[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// A value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, double additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// An array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params short[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// An value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, short additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// A array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params string[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// A value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, string additionalChecksum) where T : ChecksumBuilder { - List result = new List(); - for (int i = 0; i < additionalChecksum.Length; i++) - { - result.Add(Generate.HashCode64(additionalChecksum[i])); - } - return CombineWith(cb, result.ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// An array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params int[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// An value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, int additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// An array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params long[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// An value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, long additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// A array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params float[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// A value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, float additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// An array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params ushort[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// An value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, ushort additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// An array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params uint[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// An value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, uint additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . - /// An array that contains zero or more checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params ulong[] additionalChecksum) where T : ChecksumBuilder + /// The to extend. + /// An value containing a checksum of the additional data this instance must represent. + /// An updated instance of the specified of . + public static T CombineWith(this T cb, ulong additionalChecksum) where T : ChecksumBuilder { - return CombineWith(cb, additionalChecksum?.SelectMany(x => Convertible.GetBytes(x)).ToArray()); + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } /// /// Combines the to the representation of this instance. /// /// The type of the . - /// An instance of a . + /// The to extend. /// An array of bytes containing a checksum of the additional data this instance must represent. - /// An updated instance the specified of . - public static T CombineWith(this T cb, params byte[] additionalChecksum) where T : ChecksumBuilder + /// An updated instance of the specified of . + public static T CombineWith(this T cb, byte[] additionalChecksum) where T : ChecksumBuilder { - if (additionalChecksum == null) { return cb; } - if (additionalChecksum.Length == 0) { return cb; } - cb.AppendChecksum(additionalChecksum); - return cb; + return Decorator.Enclose(cb).CombineWith(additionalChecksum); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Data.Integrity/Cuemon.Extensions.Data.Integrity.csproj b/src/Cuemon.Extensions.Data.Integrity/Cuemon.Extensions.Data.Integrity.csproj index 36adf2a50..4d17509fc 100644 --- a/src/Cuemon.Extensions.Data.Integrity/Cuemon.Extensions.Data.Integrity.csproj +++ b/src/Cuemon.Extensions.Data.Integrity/Cuemon.Extensions.Data.Integrity.csproj @@ -1,32 +1,15 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 050bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Extensions.Data.Integrity Cuemon.Extensions.Data.Integrity - The Cuemon.Extensions.Data.Integrity assembly provides extension methods to the Cuemon.Data.Integrity namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Data.Integrity namespace contains extension methods that complements the Cuemon.Data.Integrity namespace. + extension-methods extensions get-cache-validator combine-with diff --git a/src/Cuemon.Extensions.Data.Integrity/DateTimeExtensions.cs b/src/Cuemon.Extensions.Data.Integrity/DateTimeExtensions.cs index c00a44293..314eb3b02 100644 --- a/src/Cuemon.Extensions.Data.Integrity/DateTimeExtensions.cs +++ b/src/Cuemon.Extensions.Data.Integrity/DateTimeExtensions.cs @@ -1,5 +1,6 @@ using System; using Cuemon.Data.Integrity; +using Cuemon.Security; namespace Cuemon.Extensions.Data.Integrity { @@ -8,145 +9,18 @@ namespace Cuemon.Extensions.Data.Integrity /// public static class DateTimeExtensions { - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, Action setup = null) - { - return new CacheValidator(created, setup); - } - /// /// Returns a from the specified parameters. /// /// A value for when data this represents was first created. /// A value for when data this represents was last modified. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, Action setup = null) + /// The function delegate that is invoked to produce the . Default is . + /// A enumeration value that indicates how a checksum is manipulated. Default is . + /// A that represents the integrity of the specified parameters. + public static CacheValidator GetCacheValidator(this DateTime created, DateTime? modified = null, Func hashFactory = null, EntityDataIntegrityMethod method = EntityDataIntegrityMethod.Unaltered) { - return new CacheValidator(created, modified, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, double checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, short checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, string checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, int checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, long checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, float checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, ushort checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, uint checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); - } - - /// - /// Returns a from the specified parameters. - /// - /// A value for when data this represents was first created. - /// A value for when data this represents was last modified. - /// A value containing a byte-for-byte checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, ulong checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); + if (hashFactory == null) { hashFactory = () => HashFactory.CreateFnv128(); } + return new CacheValidator(new EntityInfo(created, modified), hashFactory, method); } /// @@ -155,11 +29,14 @@ public static CacheValidator GetCacheValidator(this DateTime created, DateTime m /// A value for when data this represents was first created. /// A value for when data this represents was last modified. /// An array of bytes containing a checksum of the data this represents. - /// The which need to be configured. - /// A that fully represents the integrity of the specified parameters. - public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, byte[] checksum, Action setup = null) - { - return new CacheValidator(created, modified, checksum, setup); + /// A enumeration value that indicates the validation strength of the specified . Default is . + /// The function delegate that is invoked to produce the . Default is . + /// A enumeration value that indicates how a checksum is manipulated. Default is . + /// A that represents the integrity of the specified parameters. + public static CacheValidator GetCacheValidator(this DateTime created, DateTime modified, byte[] checksum, EntityDataIntegrityValidation validation = EntityDataIntegrityValidation.Weak, Func hashFactory = null, EntityDataIntegrityMethod method = EntityDataIntegrityMethod.Unaltered) + { + if (hashFactory == null) { hashFactory = () => HashFactory.CreateFnv128(); } + return new CacheValidator(new EntityInfo(created, modified, checksum, validation), hashFactory, method); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Data.Integrity/FileInfoExtensions.cs b/src/Cuemon.Extensions.Data.Integrity/FileInfoExtensions.cs index 9d28e3498..f6efa7137 100644 --- a/src/Cuemon.Extensions.Data.Integrity/FileInfoExtensions.cs +++ b/src/Cuemon.Extensions.Data.Integrity/FileInfoExtensions.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Cuemon.Data.Integrity; +using Cuemon.Security; namespace Cuemon.Extensions.Data.Integrity { @@ -13,18 +14,19 @@ public static class FileInfoExtensions /// Returns a from the specified . /// /// The to extend. + /// The function delegate that is invoked to produce the . Default is . /// The which may be configured. /// A that represents either a weak, medium or strong integrity check of the specified . /// /// is null. /// /// Should the specified trigger any sort of exception, a is returned. - public static CacheValidator GetCacheValidator(this FileInfo file, Action setup = null) + public static CacheValidator GetCacheValidator(this FileInfo file, Func hashFactory = null, Action setup = null) { Validator.ThrowIfNull(file, nameof(file)); try { - return CacheValidatorFactory.CreateValidator(file, setup); + return CacheValidatorFactory.CreateValidator(file, hashFactory, setup); } catch (Exception) { diff --git a/src/Cuemon.Extensions.Data.Integrity/GlobalSuppressions.cs b/src/Cuemon.Extensions.Data.Integrity/GlobalSuppressions.cs deleted file mode 100644 index 5a887d01a..000000000 --- a/src/Cuemon.Extensions.Data.Integrity/GlobalSuppressions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Major Code Smell", "S3358:Ternary operators should not be nested", Justification = "Not difficult to understand.", Scope = "member", Target = "~M:Cuemon.Extensions.Integrity.AssemblyExtensions.GetCacheValidator(System.Reflection.Assembly,System.Boolean,System.Action{Cuemon.Integrity.CacheValidatorOptions})~Cuemon.Integrity.CacheValidator")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Data.Integrity/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Data.Integrity/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..4d092ab8c --- /dev/null +++ b/src/Cuemon.Extensions.Data.Integrity/Properties/PackageReleaseNotes.txt @@ -0,0 +1,9 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED AssemblyExtensions class in the Cuemon.Extensions.Data.Integrity namespace that consist of extension methods for the Assembly class: GetCacheValidator +- ADDED ChecksumBuilderExtensions class in the Cuemon.Extensions.Data.Integrity namespace that consist of extension methods for the ChecksumBuilder class: CombineWith{T} +- ADDED DateTimeExtensions class in the Cuemon.Extensions.Data.Integrity namespace that consist of extension methods for the DateTime struct: GetCacheValidator +- ADDED FileInfoExtensions class in the Cuemon.Extensions.Data.Integrity namespace that consist of extension methods for the FileInfo class: GetCacheValidator +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Data/Cuemon.Extensions.Data.csproj b/src/Cuemon.Extensions.Data/Cuemon.Extensions.Data.csproj index 28b73bfbb..e39dc4ce0 100644 --- a/src/Cuemon.Extensions.Data/Cuemon.Extensions.Data.csproj +++ b/src/Cuemon.Extensions.Data/Cuemon.Extensions.Data.csproj @@ -1,10 +1,7 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 100bdf91-e7c7-4cb4-a39d-e1a5374c5602 @@ -12,15 +9,8 @@ Cuemon Cuemon.Extensions.Data Cuemon.Extensions.Data - The Cuemon.Extensions.Data assembly provides extension methods related to the Cuemon.Data namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Extensions.Data namespace contains extension methods that complements the Cuemon.Data namespace while being an addition to the System.Data namespace. + extension-methods extensions to-rows to-columns embed diff --git a/src/Cuemon.Extensions.Data/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Data/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..fa83bbf7c --- /dev/null +++ b/src/Cuemon.Extensions.Data/Properties/PackageReleaseNotes.txt @@ -0,0 +1,7 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED DataTransferExtensions class in the Cuemon.Extensions.Data namespace that consist of extension methods for the IDataReader interface: ToColumns, ToRows +- ADDED QueryFormatExtensions class in the Cuemon.Extensions.Data namespace that consist of extension methods for the QueryFormat enum: Embed +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj b/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj index 58275a69b..8c18dcfef 100644 --- a/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj +++ b/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj @@ -1,37 +1,25 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 040bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.DependencyInjection Cuemon.Extensions.DependencyInjection - The Cuemon.Extensions.DependencyInjection assembly provides extension methods to the Microsoft.Extensions.DependencyInjection package. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Extensions.DependencyInjection namespace contains extension methods that complements the Microsoft.Extensions.DependencyInjection namespace. + extension-methods extensions add tryadd - - https://opensource.org/licenses/MIT - https://www.cuemon.net/ - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - + + + + - - - + + + diff --git a/src/Cuemon.Extensions.DependencyInjection/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.DependencyInjection/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..3129c1978 --- /dev/null +++ b/src/Cuemon.Extensions.DependencyInjection/Properties/PackageReleaseNotes.txt @@ -0,0 +1,6 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED ServiceCollectionExtensions class in the Cuemon.Extensions.DependencyInjection namespace that consist of extension methods for the IServiceCollection interface: Add, Add{TOptions}, Add{TService, TImplementation}, Add{TService, TImplementation, TOptions}, TryAdd, TryAdd{TOptions}, TryAdd{TService, TImplementation}, TryAdd{TService, TImplementation, TOptions} +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index 09e687f9b..a9db04406 100644 --- a/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -163,5 +163,168 @@ private static void AddServices(this IServiceCollection services, Type service, break; } } + + /// + /// Adds the specified with the and to the . + /// + /// The type of the service to add. + /// The type of the implementation to use. + /// The to add the service to. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// A reference to after the operation has completed. + public static IServiceCollection Add(this IServiceCollection services, Func implementationFactory, ServiceLifetime lifetime) + where TService : class + where TImplementation : class, TService + { + return services.Add(typeof(TService), implementationFactory, lifetime); + } + + /// + /// Adds the specified with the and to the . + /// + /// The type of the service to add. + /// The type of the implementation to use. + /// The type of the configured options. + /// The to add the service to. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// The which need to be configured. + /// A reference to after the operation has completed. + public static IServiceCollection Add(this IServiceCollection services, Func implementationFactory, ServiceLifetime lifetime, Action setup) + where TService : class + where TImplementation : class, TService + where TOptions : class, new() + { + return services.Add(typeof(TService), implementationFactory, lifetime, setup); + } + + /// + /// Adds the specified with the and to the . + /// + /// The to add the service to. + /// The type of the service to register. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// A reference to after the operation has completed. + public static IServiceCollection Add(this IServiceCollection services, Type service, Func implementationFactory, ServiceLifetime lifetime) + { + Validator.ThrowIfNull(services, nameof(services)); + services.AddServices(service, implementationFactory, lifetime, false); + return services; + } + + /// + /// Adds the specified with the and to the . + /// + /// The type of the configured options. + /// The to add the service to. + /// The type of the service to register. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// The which need to be configured. + /// A reference to after the operation has completed. + public static IServiceCollection Add(this IServiceCollection services, Type service, Func implementationFactory, ServiceLifetime lifetime, Action setup) + where TOptions : class, new() + { + Validator.ThrowIfNull(services, nameof(services)); + Validator.ThrowIfNull(setup, nameof(setup)); + services.AddServices(service, implementationFactory, lifetime, false); + services.Configure(setup); + return services; + } + + /// + /// Adds the specified with the and to the if the service type has not already been registered. + /// + /// The type of the service to add. + /// The type of the implementation to use. + /// The to add the service to. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// A reference to after the operation has completed. + public static IServiceCollection TryAdd(this IServiceCollection services, Func implementationFactory, ServiceLifetime lifetime) + where TService : class + where TImplementation : class, TService + { + Validator.ThrowIfNull(services, nameof(services)); + Validator.ThrowIfNull(implementationFactory, nameof(implementationFactory)); + return services.TryAdd(typeof(TService), implementationFactory, lifetime); + } + + /// + /// Adds the specified with the and to the if the service type has not already been registered. + /// + /// The type of the service to add. + /// The type of the implementation to use. + /// The type of the configured options. + /// The to add the service to. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// The which need to be configured. + /// A reference to after the operation has completed. + public static IServiceCollection TryAdd(this IServiceCollection services, Func implementationFactory, ServiceLifetime lifetime, Action setup) + where TService : class + where TImplementation : class, TService + where TOptions : class, new() + { + Validator.ThrowIfNull(services, nameof(services)); + Validator.ThrowIfNull(implementationFactory, nameof(implementationFactory)); + Validator.ThrowIfNull(setup, nameof(setup)); + return services.TryAdd(typeof(TService), implementationFactory, lifetime, setup); + } + + /// + /// Adds the specified with the and to the if the service type has not already been registered. + /// + /// The to add the service to. + /// The type of the service to register. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// A reference to after the operation has completed. + public static IServiceCollection TryAdd(this IServiceCollection services, Type service, Func implementationFactory, ServiceLifetime lifetime) + { + Validator.ThrowIfNull(services, nameof(services)); + Validator.ThrowIfNull(implementationFactory, nameof(implementationFactory)); + services.AddServices(service, implementationFactory, lifetime, true); + return services; + } + + /// + /// Adds the specified with the and to the if the service type has not already been registered. + /// + /// The type of the configured options. + /// The to add the service to. + /// The type of the service to register. + /// The function delegate that creates the service. + /// The lifetime of the service. + /// The which need to be configured. + /// A reference to after the operation has completed. + public static IServiceCollection TryAdd(this IServiceCollection services, Type service, Func implementationFactory, ServiceLifetime lifetime, Action setup) + where TOptions : class, new() + { + Validator.ThrowIfNull(services, nameof(services)); + Validator.ThrowIfNull(implementationFactory, nameof(implementationFactory)); + Validator.ThrowIfNull(setup, nameof(setup)); + services.AddServices(service, implementationFactory, lifetime, true); + services.Configure(setup); + return services; + } + + private static void AddServices(this IServiceCollection services, Type service, Func implementationFactory, ServiceLifetime lifetime, bool useTesterDoerPattern) + { + switch (lifetime) + { + case ServiceLifetime.Scoped: + Condition.FlipFlop(useTesterDoerPattern, () => services.TryAddScoped(service, implementationFactory), () => services.AddScoped(service, implementationFactory)); + break; + case ServiceLifetime.Singleton: + Condition.FlipFlop(useTesterDoerPattern, () => services.TryAddSingleton(service, implementationFactory), () => services.AddSingleton(service, implementationFactory)); + break; + case ServiceLifetime.Transient: + Condition.FlipFlop(useTesterDoerPattern, () => services.TryAddTransient(service, implementationFactory), () => services.AddTransient(service, implementationFactory)); + break; + } + } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Diagnostics/Cuemon.Extensions.Diagnostics.csproj b/src/Cuemon.Extensions.Diagnostics/Cuemon.Extensions.Diagnostics.csproj index 91d4b068a..dc46f5f81 100644 --- a/src/Cuemon.Extensions.Diagnostics/Cuemon.Extensions.Diagnostics.csproj +++ b/src/Cuemon.Extensions.Diagnostics/Cuemon.Extensions.Diagnostics.csproj @@ -1,26 +1,15 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 0f0bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Extensions.Diagnostics Cuemon.Extensions.Diagnostics - The Cuemon.Extensions.Diagnostics assembly provides extension methods for diagnostics related implementations. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Extensions.Diagnostics namespace contains extension methods that complements the Cuemon.Diagnostics namespace while being an addition to the System.Diagnostics namespace. + extension-methods extensions to-insights-string to-product-version to-file-version diff --git a/src/Cuemon.Extensions.Diagnostics/FileVersionInfoExtensions.cs b/src/Cuemon.Extensions.Diagnostics/FileVersionInfoExtensions.cs index 9fb12f80c..9b1eeba98 100644 --- a/src/Cuemon.Extensions.Diagnostics/FileVersionInfoExtensions.cs +++ b/src/Cuemon.Extensions.Diagnostics/FileVersionInfoExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using Cuemon.Reflection; namespace Cuemon.Extensions.Diagnostics { @@ -9,32 +10,30 @@ namespace Cuemon.Extensions.Diagnostics public static class FileVersionInfoExtensions { /// - /// Returns a from the specified . + /// Returns a from the specified . /// /// An instance of . - /// A that represents the product version that the is distributed with. - /// Should the specified not contain any product version, a initialized to 0.0.0.0 is returned. - public static Version ToProductVersion(this FileVersionInfo fvi) + /// A that represents the product version that the is distributed with. + public static VersionResult ToProductVersion(this FileVersionInfo fvi) { return ToVersion(fvi, info => info.ProductVersion); } /// - /// Returns a from the specified . + /// Returns a from the specified . /// /// An instance of . - /// A that represents the file version that the is distributed with. - /// Should the specified not contain any file version, a initialized to 0.0.0.0 is returned. - public static Version ToFileVersion(this FileVersionInfo fvi) + /// A that represents the file version that the is distributed with. + public static VersionResult ToFileVersion(this FileVersionInfo fvi) { return ToVersion(fvi, info => info.FileVersion); } - private static Version ToVersion(this FileVersionInfo fvi, Func propertySelector) + private static VersionResult ToVersion(this FileVersionInfo fvi, Func propertySelector) { Validator.ThrowIfNull(fvi, nameof(fvi)); var version = propertySelector(fvi); - return new Version(string.IsNullOrWhiteSpace(version) ? "0.0.0.0" : version); + return new VersionResult(version); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Diagnostics/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Diagnostics/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..59d238596 --- /dev/null +++ b/src/Cuemon.Extensions.Diagnostics/Properties/PackageReleaseNotes.txt @@ -0,0 +1,7 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED ExceptionDescriptorExtensions class in the Cuemon.Extensions.Diagnostics namespace that consist of extension methods for the ExceptionDescriptor class: ToInsightsString +- ADDED FileVersionInfoExtensions class in the Cuemon.Extensions.Diagnostics namespace that consist of extension methods for the FileVersionInfo class: ToProductVersion, ToFileVersion +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Hosting/Cuemon.Extensions.Hosting.csproj b/src/Cuemon.Extensions.Hosting/Cuemon.Extensions.Hosting.csproj new file mode 100644 index 000000000..20b41f1d9 --- /dev/null +++ b/src/Cuemon.Extensions.Hosting/Cuemon.Extensions.Hosting.csproj @@ -0,0 +1,31 @@ + + + + net5.0;netcoreapp3.0;netstandard2.0 + 1d0bdf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Extensions.Hosting + Cuemon.Extensions.Hosting + The Cuemon.Extensions.Hosting namespace contains extension methods and features related to the Microsoft.Extensions.Hosting namespace. + extension-methods extensions local-development non-production host hosting + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.Hosting/HostEnvironmentExtensions.cs b/src/Cuemon.Extensions.Hosting/HostEnvironmentExtensions.cs new file mode 100644 index 000000000..7791b89b5 --- /dev/null +++ b/src/Cuemon.Extensions.Hosting/HostEnvironmentExtensions.cs @@ -0,0 +1,33 @@ +#if NETCOREAPP +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Hosting +{ + + /// + /// Extension methods for the interface. + /// + public static class HostEnvironmentExtensions + { + /// + /// Determines whether the specified is equal to LocalDevelopment. + /// + /// The to extend. + /// true if is LocalDevelopment; otherwise false + public static bool IsLocalDevelopment(this IHostEnvironment environment) + { + return environment.IsEnvironment("LocalDevelopment"); + } + + /// + /// Determines whether the specified is different from Production. + /// + /// The to extend. + /// true if is not Production; otherwise false + public static bool IsNonProduction(this IHostEnvironment environment) + { + return !environment.IsProduction(); + } + } +} +#endif \ No newline at end of file diff --git a/src/Cuemon.Extensions.Hosting/HostingEnvironmentExtensions.cs b/src/Cuemon.Extensions.Hosting/HostingEnvironmentExtensions.cs new file mode 100644 index 000000000..983a99d9d --- /dev/null +++ b/src/Cuemon.Extensions.Hosting/HostingEnvironmentExtensions.cs @@ -0,0 +1,32 @@ +#if NETSTANDARD +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Hosting +{ + /// + /// Extension methods for the interface. + /// + public static class HostingEnvironmentExtensions + { + /// + /// Determines whether the specified is equal to LocalDevelopment. + /// + /// The to extend. + /// true if is LocalDevelopment; otherwise false + public static bool IsLocalDevelopment(this IHostingEnvironment environment) + { + return environment.IsEnvironment("LocalDevelopment"); + } + + /// + /// Determines whether the specified is different from Production. + /// + /// The to extend. + /// true if is not Production; otherwise false + public static bool IsNonProduction(this IHostingEnvironment environment) + { + return !environment.IsProduction(); + } + } +} +#endif \ No newline at end of file diff --git a/src/Cuemon.Extensions.Hosting/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.Hosting/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c24d01879 --- /dev/null +++ b/src/Cuemon.Extensions.Hosting/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("e9bc4285-5627-4b81-b00b-712b38757ede")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Hosting/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Hosting/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..53e029c08 --- /dev/null +++ b/src/Cuemon.Extensions.Hosting/Properties/PackageReleaseNotes.txt @@ -0,0 +1,6 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# New Features +- Added extension methods for IHostEnvironment: IsLocalDevelopment and IsNonProduction +- Added extension methods for IHostingEnvironment: IsLocalDevelopment and IsNonProduction \ No newline at end of file diff --git a/src/Cuemon.Extensions.IO/Cuemon.Extensions.IO.csproj b/src/Cuemon.Extensions.IO/Cuemon.Extensions.IO.csproj index 58e53ad13..fe7b399df 100644 --- a/src/Cuemon.Extensions.IO/Cuemon.Extensions.IO.csproj +++ b/src/Cuemon.Extensions.IO/Cuemon.Extensions.IO.csproj @@ -1,32 +1,15 @@  - netstandard2.0;netstandard2.1 - true - ..\cuemon.snk - true + net5.0;netstandard2.1;netstandard2.0 060bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.IO Cuemon.Extensions.IO - The Cuemon.Extensions.IO assembly provides extension methods to the System.IO namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.IO namespace contains extension methods that complements the Cuemon.IO namespace while being an addition to the System.IO namespace. + extension-methods extensions concat to-byte-array to-byte-array-async write-async to-encoded-string to-encoded-string-async compress-brotli compress-brotli-async compress-deflate compress-deflate-async compress-gzip compress-gzip-async diff --git a/src/Cuemon.Extensions.IO/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.IO/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..0650ff211 --- /dev/null +++ b/src/Cuemon.Extensions.IO/Properties/PackageReleaseNotes.txt @@ -0,0 +1,9 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Standard 2.1, NET 5.0 +  +# New Features +- ADDED ByteArrayExtensions class in the Cuemon.Extensions.IO namespace that consist of extension methods for the byte[] struct: ToStream, ToStreamAsync +- ADDED StreamExtensions class in the Cuemon.Extensions.IO namespace that consist of extension methods for the Stream class: Concat, ToCharArray, ToByteArray, ToByteArrayAsync, WriteAsync, TryDetectUnicodeEncoding, ToEncodedString, ToEncodedStringAsync, CompressBrotli, CompressBrotliAsync, CompressDeflate, CompressDeflateAsync, CompressGZip, CompressGZipAsync, DecompressBrotli, DecompressBrotliAsync, DecompressDeflate, DecompressDeflateAsync, DecompressGZip, DecompressGZipAsync +- ADDED StringExtensions class in the Cuemon.Extensions.IO namespace that consist of extension methods for the String class: ToStream, ToStreamAsync, ToTextReader +- ADDED TextReaderExtensions class in the Cuemon.Extensions.IO namespace that consist of extension methods for the TextReader class: CopyToAsync, ReadAllLines, ReadAllLinesAsync +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.IO/StreamExtensions.cs b/src/Cuemon.Extensions.IO/StreamExtensions.cs index b8d2452eb..17a017158 100644 --- a/src/Cuemon.Extensions.IO/StreamExtensions.cs +++ b/src/Cuemon.Extensions.IO/StreamExtensions.cs @@ -106,7 +106,7 @@ public static byte[] ToByteArray(this Stream input, Action se /// Converts the specified to its equivalent representation. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a that is equivalent to . /// /// cannot be null. @@ -114,7 +114,7 @@ public static byte[] ToByteArray(this Stream input, Action se /// /// cannot be read from. /// - public static Task ToByteArrayAsync(this Stream input, Action setup = null) + public static Task ToByteArrayAsync(this Stream input, Action setup = null) { Validator.ThrowIfNull(input, nameof(input)); Validator.ThrowIfFalse(input.CanRead, nameof(input), "Stream cannot be read from."); @@ -182,7 +182,7 @@ public static string ToEncodedString(this Stream value, Action to a . /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a containing the result of the specified . /// will be initialized with and . /// @@ -191,7 +191,7 @@ public static string ToEncodedString(this Stream value, Action /// was initialized with an invalid . /// - public static Task ToEncodedStringAsync(this Stream value, Action setup = null) + public static Task ToEncodedStringAsync(this Stream value, Action setup = null) { Validator.ThrowIfNull(value, nameof(value)); return Decorator.Enclose(value).ToEncodedStringAsync(setup); @@ -220,7 +220,7 @@ public static Stream CompressBrotli(this Stream value, Action using the BROTLI algorithm. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A DEFLATE compressed of the . /// A task that represents the asynchronous operation. The task result contains a BROTLI compressed of the specified . /// @@ -229,7 +229,7 @@ public static Stream CompressBrotli(this Stream value, Action /// does not support write operations such as compression. /// - public static Task CompressBrotliAsync(this Stream value, Action setup = null) + public static Task CompressBrotliAsync(this Stream value, Action setup = null) { Validator.ThrowIfNull(value, nameof(value)); return Decorator.Enclose(value).CompressBrotliAsync(setup); @@ -258,7 +258,7 @@ public static Stream CompressDeflate(this Stream value, Action using the DEFLATE algorithm. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A DEFLATE compressed of the . /// A task that represents the asynchronous operation. The task result contains a DEFLATE compressed of the specified . /// @@ -267,7 +267,7 @@ public static Stream CompressDeflate(this Stream value, Action /// does not support write operations such as compression. /// - public static Task CompressDeflateAsync(this Stream value, Action setup = null) + public static Task CompressDeflateAsync(this Stream value, Action setup = null) { Validator.ThrowIfNull(value, nameof(value)); return Decorator.Enclose(value).CompressDeflateAsync(setup); @@ -295,7 +295,7 @@ public static Stream CompressGZip(this Stream value, Action using the GZIP algorithm. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A DEFLATE compressed of the . /// A task that represents the asynchronous operation. The task result contains a GZIP compressed of the specified . /// @@ -304,7 +304,7 @@ public static Stream CompressGZip(this Stream value, Action /// does not support write operations such as compression. /// - public static Task CompressGZipAsync(this Stream value, Action setup = null) + public static Task CompressGZipAsync(this Stream value, Action setup = null) { Validator.ThrowIfNull(value, nameof(value)); return Decorator.Enclose(value).CompressGZipAsync(setup); @@ -336,7 +336,7 @@ public static Stream DecompressBrotli(this Stream value, Action using the BROTLI data format specification. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A decompressed of the . /// A task that represents the asynchronous operation. The task result contains a decompressed of the specified . /// @@ -348,7 +348,7 @@ public static Stream DecompressBrotli(this Stream value, Action /// was compressed using an unsupported compression method. /// - public static Task DecompressBrotliAsync(this Stream value, Action setup = null) + public static Task DecompressBrotliAsync(this Stream value, Action setup = null) { Validator.ThrowIfNull(value, nameof(value)); return Decorator.Enclose(value).DecompressBrotliAsync(setup); @@ -380,7 +380,7 @@ public static Stream DecompressDeflate(this Stream value, Action using the DEFLATE data format specification. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A decompressed of the . /// A task that represents the asynchronous operation. The task result contains a decompressed of the specified . /// @@ -392,7 +392,7 @@ public static Stream DecompressDeflate(this Stream value, Action /// was compressed using an unsupported compression method. /// - public static Task DecompressDeflateAsync(this Stream value, Action setup = null) + public static Task DecompressDeflateAsync(this Stream value, Action setup = null) { Validator.ThrowIfNull(value, nameof(value)); return Decorator.Enclose(value).DecompressDeflateAsync(setup); @@ -423,7 +423,7 @@ public static Stream DecompressGZip(this Stream value, Action /// Decompresses the using the GZIP data format specification. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A decompressed of the . /// A task that represents the asynchronous operation. The task result contains a decompressed of the specified . /// @@ -435,7 +435,7 @@ public static Stream DecompressGZip(this Stream value, Action /// /// was compressed using an unsupported compression method. /// - public static Task DecompressGZipAsync(this Stream value, Action setup = null) + public static Task DecompressGZipAsync(this Stream value, Action setup = null) { Validator.ThrowIfNull(value, nameof(value)); return Decorator.Enclose(value).DecompressGZipAsync(setup); diff --git a/src/Cuemon.Extensions.IO/StringExtensions.cs b/src/Cuemon.Extensions.IO/StringExtensions.cs index 98ef14c8b..59ac845fd 100644 --- a/src/Cuemon.Extensions.IO/StringExtensions.cs +++ b/src/Cuemon.Extensions.IO/StringExtensions.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.IO; +using System.Threading; using System.Threading.Tasks; using Cuemon.Text; @@ -33,6 +34,7 @@ public static Stream ToStream(this string value, Action setup = /// Converts the specified to a . /// /// The to extend. + /// The token to monitor for cancellation requests. The default value is . /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a containing the result of the specified . /// will be initialized with and . @@ -42,9 +44,9 @@ public static Stream ToStream(this string value, Action setup = /// /// was initialized with an invalid . /// - public static Task ToStreamAsync(this string value, Action setup = null) + public static Task ToStreamAsync(this string value, CancellationToken ct = default, Action setup = null) { - return Decorator.Enclose(value).ToStreamAsync(setup); + return Decorator.Enclose(value).ToStreamAsync(ct, setup); } /// diff --git a/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj b/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj index fe5915404..d64eb54b9 100644 --- a/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj +++ b/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj @@ -1,44 +1,28 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 070bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Extensions.Net Cuemon.Extensions.Net - The Cuemon.Extensions.Net assembly provides extensions methods (query-string parsing, encoding, decoding, security and http communication) and features related to the System.Net namespace. A versatile HttpManager that transparently promotes the HttpClient is included along with a lightweight SMTP Client. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Extensions.Net namespace contains both types and extension methods that complements the Cuemon.Net namespace while being an addition to the System.Net namespace. Includes support for both traditional and factory based ways of working with HttpClient instances while also including a simple and lightweight implementation of the IHttpClientFactory interface named SlimHttpClientFactory. + extension-methods extensions to-signed-uri validate-signed-uri http-manager-factory slim-http-client-factory i-http-client-factory - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT - - - - + + - - + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.Net/Http/HttpMethodExtensions.cs b/src/Cuemon.Extensions.Net/Http/HttpMethodExtensions.cs index c4d7270d7..223be3203 100644 --- a/src/Cuemon.Extensions.Net/Http/HttpMethodExtensions.cs +++ b/src/Cuemon.Extensions.Net/Http/HttpMethodExtensions.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System; +using System.Net.Http; using Cuemon.Net.Http; namespace Cuemon.Extensions.Net.Http @@ -13,6 +14,9 @@ public static class HttpMethodExtensions /// /// The to be converted. /// A representation of the specified . + /// + /// cannot be null. + /// public static HttpMethods ToHttpMethod(this HttpMethod method) { return HttpMethodConverter.ToHttpMethod(method); diff --git a/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs b/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs index c3640b278..13389610d 100644 --- a/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs +++ b/src/Cuemon.Extensions.Net/Http/SlimHttpClientFactory.cs @@ -9,8 +9,6 @@ namespace Cuemon.Extensions.Net.Http { /// /// Provides a simple and lightweight implementation of the interface. - /// Implements the - /// Implements the /// /// /// diff --git a/src/Cuemon.Extensions.Net/Http/UriExtensions.cs b/src/Cuemon.Extensions.Net/Http/UriExtensions.cs index e2268726c..86efc14c7 100644 --- a/src/Cuemon.Extensions.Net/Http/UriExtensions.cs +++ b/src/Cuemon.Extensions.Net/Http/UriExtensions.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Cuemon.Net.Http; +using HttpRequestOptions = Cuemon.Net.Http.HttpRequestOptions; namespace Cuemon.Extensions.Net.Http { @@ -39,9 +40,12 @@ public static IHttpClientFactory DefaultHttpClientFactory /// /// Send a DELETE request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpDeleteAsync(this Uri location, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpDeleteAsync(location, ct).ConfigureAwait(false); @@ -50,9 +54,12 @@ public static async Task HttpDeleteAsync(this Uri location, /// /// Send a GET request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpGetAsync(this Uri location, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpGetAsync(location, ct).ConfigureAwait(false); @@ -61,9 +68,12 @@ public static async Task HttpGetAsync(this Uri location, Ca /// /// Send a HEAD request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpHeadAsync(this Uri location, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpHeadAsync(location, ct).ConfigureAwait(false); @@ -72,9 +82,12 @@ public static async Task HttpHeadAsync(this Uri location, C /// /// Send an OPTIONS request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpOptionsAsync(this Uri location, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpOptionsAsync(location, ct).ConfigureAwait(false); @@ -83,11 +96,14 @@ public static async Task HttpOptionsAsync(this Uri location /// /// Send a POST request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpPostAsync(this Uri location, string contentType, Stream content, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpPostAsync(location, contentType, content, ct).ConfigureAwait(false); @@ -96,11 +112,14 @@ public static async Task HttpPostAsync(this Uri location, s /// /// Send a POST request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpPostAsync(this Uri location, MediaTypeHeaderValue contentType, Stream content, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpPostAsync(location, contentType, content, ct).ConfigureAwait(false); @@ -109,11 +128,14 @@ public static async Task HttpPostAsync(this Uri location, M /// /// Send a PUT request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpPutAsync(this Uri location, string contentType, Stream content, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpPutAsync(location, contentType, content, ct).ConfigureAwait(false); @@ -122,11 +144,14 @@ public static async Task HttpPutAsync(this Uri location, st /// /// Send a PUT request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpPutAsync(this Uri location, MediaTypeHeaderValue contentType, Stream content, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpPutAsync(location, contentType, content, ct).ConfigureAwait(false); @@ -135,11 +160,14 @@ public static async Task HttpPutAsync(this Uri location, Me /// /// Send a PATCH request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpPatchAsync(this Uri location, string contentType, Stream content, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpPatchAsync(location, contentType, content, ct).ConfigureAwait(false); @@ -148,11 +176,14 @@ public static async Task HttpPatchAsync(this Uri location, /// /// Send a PATCH request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpPatchAsync(this Uri location, MediaTypeHeaderValue contentType, Stream content, CancellationToken ct = default) { @@ -162,9 +193,12 @@ public static async Task HttpPatchAsync(this Uri location, /// /// Send a TRACE request to the specified Uri as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpTraceAsync(this Uri location, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpTraceAsync(location, ct).ConfigureAwait(false); @@ -173,12 +207,18 @@ public static async Task HttpTraceAsync(this Uri location, /// /// Send a request as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The HTTP method. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null -or- + /// cannot be null -or- + /// cannot be null. + /// public static async Task HttpAsync(this Uri location, HttpMethod method, string contentType, Stream content, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpAsync(method, location, contentType, content, ct).ConfigureAwait(false); @@ -187,12 +227,18 @@ public static async Task HttpAsync(this Uri location, HttpM /// /// Send a request as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The HTTP method. /// The Content-Type header of the HTTP request sent to the server. /// The HTTP request content sent to the server. /// The cancellation token to cancel operation. /// The task object representing the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null -or- + /// cannot be null -or- + /// cannot be null. + /// public static async Task HttpAsync(this Uri location, HttpMethod method, MediaTypeHeaderValue contentType, Stream content, CancellationToken ct = default) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpAsync(method, location, contentType, content, ct).ConfigureAwait(false); @@ -201,9 +247,12 @@ public static async Task HttpAsync(this Uri location, HttpM /// /// Send a request as an asynchronous operation. /// - /// The to request. + /// The to extend. /// The which need to be configured. /// The task object representing the asynchronous operation. + /// + /// cannot be null. + /// public static async Task HttpAsync(this Uri location, Action setup) { return await HttpManagerFactory.CreateManager(DefaultHttpClientFactory, HandlerName).HttpAsync(location, setup).ConfigureAwait(false); diff --git a/src/Cuemon.Net/HttpStatusCodeExtensions.cs b/src/Cuemon.Extensions.Net/HttpStatusCodeExtensions.cs similarity index 99% rename from src/Cuemon.Net/HttpStatusCodeExtensions.cs rename to src/Cuemon.Extensions.Net/HttpStatusCodeExtensions.cs index b297eb90f..81cff4f40 100644 --- a/src/Cuemon.Net/HttpStatusCodeExtensions.cs +++ b/src/Cuemon.Extensions.Net/HttpStatusCodeExtensions.cs @@ -1,6 +1,6 @@ using System.Net; -namespace Cuemon.Net +namespace Cuemon.Extensions.Net { /// /// Extension methods for the enum. diff --git a/src/Cuemon.Extensions.Net/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Net/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..f4e18a0c7 --- /dev/null +++ b/src/Cuemon.Extensions.Net/Properties/PackageReleaseNotes.txt @@ -0,0 +1,17 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED HttpManagerFactory class in the Cuemon.Extensions.Net.Http namespace that provides access to factory methods for creating and configuring HttpManager instances +- ADDED HttpMethodExtensions class in the Cuemon.Extensions.Net.Http namespace that consist of extension methods for the HttpMethod class: ToHttpMethod +- ADDED SlimHttpClientFactory class in the Cuemon.Extensions.Net.Http namespace that provides a simple and lightweight implementation of the IHttpClientFactory interface +- ADDED SlimHttpClientFactoryOptions class in the Cuemon.Extensions.Net.Http namespace that specifies options related to SlimHttpClientFactory +- ADDED StringExtensions class in the Cuemon.Extensions.Net.Http namespace that consist of extension methods for the Uri class: HttpDeleteAsync, HttpGetAsync, HttpHeadAsync, HttpOptionsAsync, HttpPostAsync, HttpPutAsync, HttpPatchAsync, HttpTraceAsync, HttpAsync +- ADDED SignedUriOptions class in the Cuemon.Extensions.Net.Security namespace that specifies options related to ToSignedUri extensions +- ADDED StringExtensions class in the Cuemon.Extensions.Net.Security namespace that consist of extension methods for the String class: ToSignedUri, ValidateSignedUri +- ADDED UriExtensions class in the Cuemon.Extensions.Net.Security namespace that consist of extension methods for the Uri class: ToSignedUri, ValidateSignedUri +- ADDED ByteArrayExtensions class in the Cuemon.Extensions.Net namespace that consist of extension methods for the byte[] struct: UrlEncode +- ADDED DictionaryExtensions class in the Cuemon.Extensions.Net namespace that consist of extension methods for the IDictionary{string, string[]} interface: ToQueryString +- ADDED NameValueCollectionExtensions class in the Cuemon.Extensions.Net namespace that consist of extension methods for the NameValueCollection class: ToQueryString +- ADDED StringExtensions class in the Cuemon.Extensions.Net namespace that consist of extension methods for the String class: UrlEncode, UrlDecode +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Net/Security/StringExtensions.cs b/src/Cuemon.Extensions.Net/Security/StringExtensions.cs index 677abd417..9860e6d53 100644 --- a/src/Cuemon.Extensions.Net/Security/StringExtensions.cs +++ b/src/Cuemon.Extensions.Net/Security/StringExtensions.cs @@ -39,7 +39,7 @@ public static Uri ToSignedUri(this string uriString, byte[] secret, DateTime? st if (start.HasValue) { qsc.Add(options.StartFieldName, Decorator.Enclose(start.Value).ToUtcKind().ToString("s") + "Z"); } if (expiry.HasValue) { qsc.Add(options.ExpiryFieldName, Decorator.Enclose(expiry.Value).ToUtcKind().ToString("s") + "Z"); } uriString = FormattableString.Invariant($"{uriString.SkipQueryString()}{qsc.ToQueryString(options.UrlEncode)}"); - qsc.Add(options.SignatureFieldName, HashFactory.CreateHmacCrypto(secret, options.Algorithm).ComputeHash(options.CanonicalRepresentationBuilder(uriString)).ToUrlEncodedBase64String()); + qsc.Add(options.SignatureFieldName, KeyedHashFactory.CreateHmacCrypto(secret, options.Algorithm).ComputeHash(options.CanonicalRepresentationBuilder(uriString)).ToUrlEncodedBase64String()); return new Uri(FormattableString.Invariant($"{uriString.SkipQueryString()}{qsc.ToQueryString(options.UrlEncode)}")); } @@ -71,7 +71,7 @@ public static void ValidateSignedUri(this string signedUriString, byte[] secret, if (string.IsNullOrWhiteSpace(signature)) { throw new SecurityException(message); } qsc.Remove(options.SignatureFieldName); - var computedSignature = HashFactory.CreateHmacCrypto(secret, options.Algorithm).ComputeHash(options.CanonicalRepresentationBuilder(FormattableString.Invariant($"{signedUriString.SkipQueryString()}{qsc.ToQueryString()}"))).ToUrlEncodedBase64String(); + var computedSignature = KeyedHashFactory.CreateHmacCrypto(secret, options.Algorithm).ComputeHash(options.CanonicalRepresentationBuilder(FormattableString.Invariant($"{signedUriString.SkipQueryString()}{qsc.ToQueryString()}"))).ToUrlEncodedBase64String(); if (!signature.Equals(computedSignature, StringComparison.Ordinal)) { throw new SecurityException(message); } } diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/Converters/JsonConverterCollectionExtensions.cs b/src/Cuemon.Extensions.Newtonsoft.Json/Converters/JsonConverterCollectionExtensions.cs index dfab20d48..03707bb44 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/Converters/JsonConverterCollectionExtensions.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/Converters/JsonConverterCollectionExtensions.cs @@ -218,10 +218,15 @@ private static void WriteExceptionCore(JsonWriter writer, Exception exception, b private static void WriteInnerExceptions(JsonWriter writer, Exception exception, bool includeStackTrace) { - var aggregated = exception as AggregateException; var innerExceptions = new List(); - if (aggregated != null) { innerExceptions.AddRange(aggregated.Flatten().InnerExceptions); } - if (exception.InnerException != null) { innerExceptions.Add(exception.InnerException); } + if (exception is AggregateException aggregated) + { + innerExceptions.AddRange(aggregated.Flatten().InnerExceptions); + } + else + { + if (exception.InnerException != null) { innerExceptions.Add(exception.InnerException); } + } if (innerExceptions.Count > 0) { var endElementsToWrite = 0; diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/Cuemon.Extensions.Newtonsoft.Json.csproj b/src/Cuemon.Extensions.Newtonsoft.Json/Cuemon.Extensions.Newtonsoft.Json.csproj index f75c0622f..0f6938ea2 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/Cuemon.Extensions.Newtonsoft.Json.csproj +++ b/src/Cuemon.Extensions.Newtonsoft.Json/Cuemon.Extensions.Newtonsoft.Json.csproj @@ -1,36 +1,20 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 080bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.Newtonsoft.Json Cuemon.Extensions.Newtonsoft.Json - The Cuemon.Extensions.Newtonsoft.Json assembly provides extension methods and general improvements to the Newtonsoft.Json package. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Newtonsoft.Json namespace contains both types and extension methods that complements the Newtonsoft.Json namespace by adding new ways of working with JSON; both in terms of serialization and parsing. + extension-methods extensions jdata jdata-result json-converter json-formatter dynamic-contract-resolver dynamic-json-converter + diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/JsonFormatterOptions.cs b/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/JsonFormatterOptions.cs index 56686b7a2..2b9d7d9ad 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/JsonFormatterOptions.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/Formatters/JsonFormatterOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Cuemon.Diagnostics; using Cuemon.Extensions.Newtonsoft.Json.Converters; using Newtonsoft.Json; @@ -11,6 +12,17 @@ namespace Cuemon.Extensions.Newtonsoft.Json.Formatters /// public class JsonFormatterOptions { + static JsonFormatterOptions() + { + DefaultConverters = list => + { + list.AddStringFlagsEnumConverter(); + list.AddStringEnumConverter(); + list.AddTimeSpanConverter(); + list.AddDataPairConverter(); + }; + } + /// /// Initializes a new instance of the class. /// @@ -60,18 +72,21 @@ public JsonFormatterOptions() DateTimeZoneHandling = DateTimeZoneHandling.Utc, ContractResolver = new CamelCasePropertyNamesContractResolver() }; - Settings.Converters.AddStringFlagsEnumConverter(); - Settings.Converters.AddStringEnumConverter(); Settings.Converters.AddExceptionConverter(() => IncludeExceptionStackTrace); Settings.Converters.AddExceptionDescriptorConverter(o => { o.IncludeEvidence = IncludeExceptionDescriptorEvidence; o.IncludeFailure = IncludeExceptionDescriptorFailure; }); - Settings.Converters.AddTimeSpanConverter(); - Settings.Converters.AddDataPairConverter(); + DefaultConverters?.Invoke(Settings.Converters); } + /// + /// Gets or sets a delegate that is invoked when is initialized and propagates registered implementations. + /// + /// The delegate which propagates registered implementations when is initialized. + public static Action> DefaultConverters { get; set; } + /// /// Gets or sets a value indicating whether the stack of an is included in the converter that handles exceptions. /// diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/GlobalSuppressions.cs b/src/Cuemon.Extensions.Newtonsoft.Json/GlobalSuppressions.cs index 3bf039823..ffbb6f1ba 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/GlobalSuppressions.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/GlobalSuppressions.cs @@ -5,5 +5,6 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.Newtonsoft.Json.ValidatorExtensions.IfNotValidJsonDocument(Cuemon.Validator,Newtonsoft.Json.JsonReader@,System.String,System.String)")] -[assembly: SuppressMessage("Major Code Smell", "S907:\"goto\" statement should not be used", Justification = "Lecacy code ;-)", Scope = "member", Target = "~M:Cuemon.Extensions.Newtonsoft.Json.JsonReaderExtensions.ToHierarchy(Newtonsoft.Json.JsonReader)~Cuemon.IHierarchy{Cuemon.DataPair}")] \ No newline at end of file +[assembly: SuppressMessage("Major Code Smell", "S3445:Exceptions should not be explicitly rethrown", Justification = "This is by design; we only want the stacktrace from within the validator method.", Scope = "member", Target = "~M:Cuemon.Extensions.Newtonsoft.Json.ValidatorExtensions.InvalidJsonDocument(Cuemon.Validator,Newtonsoft.Json.JsonReader@,System.String,System.String)")] +[assembly: SuppressMessage("Major Code Smell", "S907:\"goto\" statement should not be used", Justification = "Legacy code ;-)", Scope = "member", Target = "~M:Cuemon.Extensions.Newtonsoft.Json.JsonReaderExtensions.ToHierarchy(Newtonsoft.Json.JsonReader)~Cuemon.IHierarchy{Cuemon.DataPair}")] +[assembly: SuppressMessage("Critical Code Smell", "S3776:Cognitive Complexity of methods should not be too high", Justification = "Legacy code ;-)", Scope = "member", Target = "~M:Cuemon.Extensions.Newtonsoft.Json.JsonReaderExtensions.ToHierarchy(Newtonsoft.Json.JsonReader)~Cuemon.IHierarchy{Cuemon.DataPair}")] diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/JData.cs b/src/Cuemon.Extensions.Newtonsoft.Json/JData.cs index a52a99c6f..6d3d9ed9e 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/JData.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/JData.cs @@ -8,7 +8,7 @@ namespace Cuemon.Extensions.Newtonsoft.Json { /// - /// Provides a factory based way to parse and extract values from various sources of JSON data. + /// Provides a factory based way to parse and extract values from various sources of JSON data. Compliant with RFC 7159 as it uses behind the scene. /// public class JData { @@ -24,7 +24,6 @@ public static IEnumerable ReadAll(Stream json, Action ReadAll(string json) public static IEnumerable ReadAll(JsonReader reader) { Validator.ThrowIfNull(reader, nameof(reader)); - Validator.Throw.IfNotValidJsonDocument(ref reader, nameof(reader)); + Validator.ThrowIf.InvalidJsonDocument(ref reader, nameof(reader)); return new JData(reader).Result.Value; } @@ -69,7 +68,6 @@ internal JData(JsonReader reader) var result = new List(); while (reader.Read()) { - if (reader.Value == null) { continue; } var jr = new JDataResult(); switch (reader.TokenType) { @@ -78,16 +76,20 @@ internal JData(JsonReader reader) reader.Read(); break; } + if (reader.TokenType == JsonToken.StartArray) { - jr.Children = FillArray(reader, jr); + return FillArrayHierarchy(reader, jr); } - else + + if (reader.TokenType == JsonToken.StartObject) { - jr.Value = reader.Value; + return FillObjectHierarchy(reader, jr); } + + jr.Value = reader.Value; jr.Path = reader.Path.RemoveBrackets(); - jr.Type = reader.ValueType ?? typeof(Array); + jr.Type = reader.ValueType; result.Add(jr); } return result; @@ -96,33 +98,23 @@ internal JData(JsonReader reader) private Lazy> Result { get; } - private List FillArray(JsonReader reader, JDataResult parent) + + private List FillArrayHierarchy(JsonReader reader, JDataResult parent) { - var result = new List(); - while (reader.Read()) - { - var jr = new JDataResult { Parent = parent }; - if (reader.TokenType == JsonToken.EndArray) { break; } - if (reader.TokenType == JsonToken.StartObject) - { - jr.Children = FillObjectArray(reader, jr); - } - else - { - jr.Value = reader.Value; - } - jr.Path = reader.Path.RemoveBrackets(); - jr.Type = reader.ValueType ?? typeof(Array); - result.Add(jr); - } - return result; + return FillHierarchy(reader, parent, r => r.TokenType == JsonToken.EndArray); + } + + private List FillObjectHierarchy(JsonReader reader, JDataResult parent) + { + return FillHierarchy(reader, parent, r => r.TokenType == JsonToken.EndObject); } - private List FillObjectArray(JsonReader reader, JDataResult parent) + private List FillHierarchy(JsonReader reader, JDataResult parent, Func skipWhenTrue) { var result = new List(); while (reader.Read()) { + if (skipWhenTrue(reader)) { break; } var jr = new JDataResult { Parent = parent }; switch (reader.TokenType) { @@ -131,23 +123,24 @@ private List FillObjectArray(JsonReader reader, JDataResult parent) reader.Read(); break; } - if (reader.TokenType == JsonToken.EndObject) { break; } if (reader.TokenType == JsonToken.StartArray) { - jr.Children = FillArray(reader, jr); + jr.Children = FillArrayHierarchy(reader, jr); + } + else if (reader.TokenType == JsonToken.StartObject) + { + jr.Children = FillObjectHierarchy(reader, jr); } else { jr.Value = reader.Value; } jr.Path = reader.Path.RemoveBrackets(); - jr.Type = reader.ValueType ?? typeof(Array); + jr.Type = reader.ValueType; result.Add(jr); } return result; } - - } internal static class RegexExtensions diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/JDataResult.cs b/src/Cuemon.Extensions.Newtonsoft.Json/JDataResult.cs index 8b4dd0f5f..5f087f94f 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/JDataResult.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/JDataResult.cs @@ -50,7 +50,8 @@ public class JDataResult /// A that represents this instance. public override string ToString() { - return FormattableString.Invariant($"{Path} ({Type.Name.ToLowerInvariant()}), Children: {Children.Count}"); + var path = string.IsNullOrWhiteSpace(Path) ? "" : $"{Path}, "; + return FormattableString.Invariant($"{path}Children: {Children.Count}"); } } } \ No newline at end of file diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/JDataResultExtensions.cs b/src/Cuemon.Extensions.Newtonsoft.Json/JDataResultExtensions.cs index 7f4d2e487..0cb4cb0e1 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/JDataResultExtensions.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/JDataResultExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Cuemon.Collections.Generic; namespace Cuemon.Extensions.Newtonsoft.Json { @@ -10,16 +11,32 @@ namespace Cuemon.Extensions.Newtonsoft.Json public static class JDataResultExtensions { /// - /// Extracts one or more values from JSON objects using the specified and delegate. + /// Flattens the entirety of the JSON hierarchical into an sequence. + /// + /// The to extend. + /// An sequence of objects. + public static IEnumerable Flatten(this IEnumerable source) + { + Validator.ThrowIfNull(source, nameof(source)); + return FlattenCore(source); + } + + private static IEnumerable FlattenCore(IEnumerable source) + { + return source.SelectMany(s => s.Children.Any() ? Arguments.Yield(s).Concat(Flatten(s.Children)) : Arguments.Yield(s)); + } + + /// + /// Extracts one or more values from JSON objects using the specified and delegate. /// /// The sequence of to parse. /// The comma-delimited property names (JSON path) to math in a JSON document. - /// The delegate that will extract values from . - public static void ExtractObjectValues(this IEnumerable source, string propertyNames, Action> parser) + /// The delegate that will extract values from . + public static void ExtractObjectValues(this IEnumerable source, string propertyNames, Action> extractor) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNullOrWhitespace(propertyNames, nameof(propertyNames)); - Validator.ThrowIfNull(parser, nameof(parser)); + Validator.ThrowIfNull(extractor, nameof(extractor)); var names = propertyNames.Split(',').Select(s => s.Trim()).ToList(); var partial = new List(); @@ -33,23 +50,23 @@ public static void ExtractObjectValues(this IEnumerable source, str if (partial.Count == names.Count) { - parser(partial.ToDictionary(jk => jk.PropertyName, jv => jv)); + extractor(partial.ToDictionary(jk => jk.PropertyName, jv => jv)); partial.Clear(); } } } /// - /// Extracts one or more values from JSON arrays using the specified and delegate. + /// Extracts one or more values from JSON arrays using the specified and delegate. /// /// The sequence of to parse. /// The comma-delimited property names (JSON path) to math in a JSON document. - /// The delegate that will extract values from . - public static void ExtractArrayValues(this IEnumerable source, string propertyNames, Action>> parser) + /// The delegate that will extract values from . + public static void ExtractArrayValues(this IEnumerable source, string propertyNames, Action>> extractor) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNullOrWhitespace(propertyNames, nameof(propertyNames)); - Validator.ThrowIfNull(parser, nameof(parser)); + Validator.ThrowIfNull(extractor, nameof(extractor)); var names = propertyNames.Split(',').Select(s => s.Trim()).ToList(); var partial = new List(); @@ -63,7 +80,7 @@ public static void ExtractArrayValues(this IEnumerable source, stri if (partial.Count == names.Count) { - parser(partial.ToDictionary(jk => jk.PropertyName, jv => jv.Children as IEnumerable)); + extractor(partial.ToDictionary(jk => jk.PropertyName, jv => jv.Children as IEnumerable)); partial.Clear(); } } diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Newtonsoft.Json/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..ac0df4f30 --- /dev/null +++ b/src/Cuemon.Extensions.Newtonsoft.Json/Properties/PackageReleaseNotes.txt @@ -0,0 +1,21 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.Serialization.Json namespace was removed with this version +- Any types found in the Cuemon.Serialization.Json namespace was merged into the Cuemon.Extensions.Newtonsoft.Json namespace +  +# Breaking Changes +- REMOVED JsonReaderParser class from the Cuemon.Extensions.Newtonsoft.Json namespace +- RENAMED JsonReaderResult class in the Cuemon.Extensions.Newtonsoft.Json namespace to JDataResult (including some refactoring) +- REMOVED JsonReaderResultExtensions class from the Cuemon.Extensions.Newtonsoft.Json namespace +  +# New Features +- CHANGED StringFlagsEnumConverter class in the Cuemon.Extensions.Newtonsoft.Json.Converters namespace to comply with Newtonsoft.Json.Serialization.NamingStrategy implementations +- ADDED ExceptionDescriptorExtensions class in the Cuemon.Extensions.Newtonsoft.Json.Diagnostics namespace that consist of extension methods for the ExceptionDescriptor class: ToInsightsJsonString +- EXTENDED JsonFormatterOptions class in the Cuemon.Extensions.Newtonsoft.Json namespace with several new options and a uniform way of adding default converters +- ADDED JData class in the Cuemon.Extensions.Newtonsoft.Json namespace that provides a factory based way to parse and extract values from various sources of JSON data. Compliant with RFC 7159 as it uses JsonTextReader behind the scene +- ADDED JDataResultExtensions class in the Cuemon.Extensions.Newtonsoft.Json namespace that consist of extension methods for the JDataResult class: ExtractArrayValues, ExtractObjectValues +- EXTENDED JsonSerializerSettingsExtensions class in the Cuemon.Extensions.Newtonsoft.Json namespace with one new extension method for the JsonSerializerSettings class: UseCamelCase +- ADDED ValidatorExtensions class in the Cuemon.Extensions.Newtonsoft.Json namespace that consist of extension methods for the Validator class: InvalidJsonDocument +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Newtonsoft.Json/ValidatorExtensions.cs b/src/Cuemon.Extensions.Newtonsoft.Json/ValidatorExtensions.cs index d79ad4d48..905fb57d5 100644 --- a/src/Cuemon.Extensions.Newtonsoft.Json/ValidatorExtensions.cs +++ b/src/Cuemon.Extensions.Newtonsoft.Json/ValidatorExtensions.cs @@ -19,7 +19,7 @@ public static class ValidatorExtensions /// /// must be a JSON representation that complies with RFC 8259. /// - public static void IfNotValidJsonDocument(this Validator validator, ref JsonReader value, string paramName, string message = "Value must be a JSON representation that complies with RFC 8259.") + public static void InvalidJsonDocument(this Validator validator, ref JsonReader value, string paramName, string message = "Value must be a JSON representation that complies with RFC 8259.") { if (value == null) { return; } var reader = value; diff --git a/src/Cuemon.Extensions.Reflection/AssemblyExtensions.cs b/src/Cuemon.Extensions.Reflection/AssemblyExtensions.cs index 663f4d735..bc961cf8e 100644 --- a/src/Cuemon.Extensions.Reflection/AssemblyExtensions.cs +++ b/src/Cuemon.Extensions.Reflection/AssemblyExtensions.cs @@ -10,37 +10,37 @@ namespace Cuemon.Extensions.Reflection public static class AssemblyExtensions { /// - /// Returns a that represents the version number of the specified . + /// Returns a that represents the version number of the specified . /// /// The assembly to resolve a from. - /// A that represents the version number of the specified . - public static Version GetAssemblyVersion(this Assembly assembly) + /// A that represents the version number of the specified . + public static VersionResult GetAssemblyVersion(this Assembly assembly) { return Decorator.Enclose(assembly).GetAssemblyVersion(); } /// - /// Returns a that represents the file version number of the specified . + /// Returns a that represents the file version number of the specified . /// /// The assembly to resolve a from. - /// A that represents the file version number of the specified . + /// A that represents the file version number of the specified . /// /// is null. /// - public static Version GetFileVersion(this Assembly assembly) + public static VersionResult GetFileVersion(this Assembly assembly) { return Decorator.Enclose(assembly).GetFileVersion(); } /// - /// Returns a that represents the version of the product this is distributed with. + /// Returns a that represents the version of the product this is distributed with. /// /// The assembly to resolve a from. - /// A that represents the version of the product this is distributed with. + /// A that represents the version of the product this is distributed with. /// /// is null. /// - public static Version GetProductVersion(this Assembly assembly) + public static VersionResult GetProductVersion(this Assembly assembly) { return Decorator.Enclose(assembly).GetProductVersion(); } diff --git a/src/Cuemon.Extensions.Reflection/Cuemon.Extensions.Reflection.csproj b/src/Cuemon.Extensions.Reflection/Cuemon.Extensions.Reflection.csproj index c9878269c..258d4321b 100644 --- a/src/Cuemon.Extensions.Reflection/Cuemon.Extensions.Reflection.csproj +++ b/src/Cuemon.Extensions.Reflection/Cuemon.Extensions.Reflection.csproj @@ -1,32 +1,15 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 090bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.Reflection Cuemon.Extensions.Reflection - The Cuemon.Extensions.Reflection assembly provides extension to the System.Reflection namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Reflection namespace contains extension methods that complements the Cuemon.Reflection namespace while being an addition to the System.Reflection namespace. + extension-methods extensions get-assembly-version get-file-version get-product-version is-debug-build has-attributes is-auto-property get-runtime-properties-except-of diff --git a/src/Cuemon.Extensions.Reflection/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Reflection/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..686af98f6 --- /dev/null +++ b/src/Cuemon.Extensions.Reflection/Properties/PackageReleaseNotes.txt @@ -0,0 +1,8 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED AssemblyExtensions class in the Cuemon.Extensions.Reflection namespace that consist of extension methods for the Assembly class: GetAssemblyVersion, GetFileVersion, GetProductVersion, IsDebugBuild +- ADDED PropertyInfoExtensions class in the Cuemon.Extensions.Reflection namespace that consist of extension methods for the PropertyInfo class: IsAutoProperty +- ADDED TypeExtensions class in the Cuemon.Extensions.Reflection namespace that consist of extension methods for the Type class: GetEmbeddedResources, ToMethodBase, GetRuntimePropertiesExceptOf{T}, ToFullNameIncludingAssemblyName +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Runtime.Caching/CacheEnumerableExtensions.cs b/src/Cuemon.Extensions.Runtime.Caching/CacheEnumerableExtensions.cs new file mode 100644 index 000000000..d42fa34bf --- /dev/null +++ b/src/Cuemon.Extensions.Runtime.Caching/CacheEnumerableExtensions.cs @@ -0,0 +1,743 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Cuemon.Collections.Generic; +using Cuemon.Reflection; +using Cuemon.Runtime; +using Cuemon.Runtime.Caching; + +namespace Cuemon.Extensions.Runtime.Caching +{ + /// + /// Extension methods for the interface. + /// + public static class CacheEnumerableExtensions + { + /// + /// Represents a cache with a scope of Memoization. + /// + public const string MemoizationScope = "Memoization"; + + private const long MemoizationNullHashCode = 854726591; + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, IDependency dependency, Func valueFactory) + { + return GetOrAdd(cache, key, CacheEntry.NoScope, dependency, valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. Default is . + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, string ns, IDependency dependency, Func valueFactory) + { + return GetOrAdd(cache, key, ns, Arguments.Yield(dependency), valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, IEnumerable dependencies, Func valueFactory) + { + return GetOrAdd(cache, key, CacheEntry.NoScope, dependencies, valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. Default is . + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, string ns, IEnumerable dependencies, Func valueFactory) + { + return GetOrAdd(cache, key, ns, new CacheInvalidation(dependencies), valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, TimeSpan slidingExpiration, Func valueFactory) + { + return GetOrAdd(cache, key, CacheEntry.NoScope, slidingExpiration, valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. Default is . + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, string ns, TimeSpan slidingExpiration, Func valueFactory) + { + return GetOrAdd(cache, key, ns, new CacheInvalidation(slidingExpiration), valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, DateTime absoluteExpiration, Func valueFactory) + { + return GetOrAdd(cache, key, CacheEntry.NoScope, absoluteExpiration, valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. Default is . + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, string ns, DateTime absoluteExpiration, Func valueFactory) + { + return GetOrAdd(cache, key, ns, new CacheInvalidation(absoluteExpiration), valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, CacheInvalidation invalidation, Func valueFactory) + { + return GetOrAdd(cache, key, CacheEntry.NoScope, invalidation, valueFactory); + } + + /// + /// Gets the value for the specified from the cache, or if the does not exists, adds a value to the cache using the specified function delegate . + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. Default is . + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a value for the . + /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value returned by if the was not in the cache. + public static TResult GetOrAdd(this ICacheEnumerable cache, string key, string ns, CacheInvalidation invalidation, Func valueFactory) + { + Validator.ThrowIfNull(cache, nameof(cache)); + Validator.ThrowIfNull(key, nameof(key)); + Validator.ThrowIfNull(invalidation, nameof(invalidation)); + Validator.ThrowIfNull(valueFactory, nameof(valueFactory)); + + if (!cache.TryGet(key, ns, out var value)) + { + value = valueFactory(); + cache.Add(new CacheEntry(key, value, ns), invalidation); + } + return (TResult)value; + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IDependency dependency, Func valueFactory) + { + return Memoize(cache, Arguments.Yield(dependency), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IEnumerable dependencies, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(dependencies), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, TimeSpan slidingExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(slidingExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, DateTime absoluteExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(absoluteExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the value in the cache. + /// The to extend. + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, CacheInvalidation invalidation, Func valueFactory) + { + return delegate + { + var key = ComputeMemoizationCacheKey(valueFactory); + return Memoize(cache, key, invalidation, FuncFactory.Create(valueFactory)); + }; + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IDependency dependency, Func valueFactory) + { + return Memoize(cache, Arguments.Yield(dependency), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IEnumerable dependencies, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(dependencies), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, TimeSpan slidingExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(slidingExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, DateTime absoluteExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(absoluteExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, CacheInvalidation invalidation, Func valueFactory) + { + return delegate (T arg) + { + var key = ComputeMemoizationCacheKey(valueFactory, arg); + return Memoize(cache, key, invalidation, FuncFactory.Create(valueFactory, arg)); + }; + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IDependency dependency, Func valueFactory) + { + return Memoize(cache, Arguments.Yield(dependency), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IEnumerable dependencies, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(dependencies), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, TimeSpan slidingExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(slidingExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, DateTime absoluteExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(absoluteExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, CacheInvalidation invalidation, Func valueFactory) + { + return delegate (T1 arg1, T2 arg2) + { + var key = ComputeMemoizationCacheKey(valueFactory, arg1, arg2); + return Memoize(cache, key, invalidation, FuncFactory.Create(valueFactory, arg1, arg2)); + }; + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IDependency dependency, Func valueFactory) + { + return Memoize(cache, Arguments.Yield(dependency), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IEnumerable dependencies, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(dependencies), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, TimeSpan slidingExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(slidingExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, DateTime absoluteExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(absoluteExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, CacheInvalidation invalidation, Func valueFactory) + { + return delegate (T1 arg1, T2 arg2, T3 arg3) + { + var key = ComputeMemoizationCacheKey(valueFactory, arg1, arg2, arg3); + return Memoize(cache, key, invalidation, FuncFactory.Create(valueFactory, arg1, arg2, arg3)); + }; + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IDependency dependency, Func valueFactory) + { + return Memoize(cache, Arguments.Yield(dependency), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IEnumerable dependencies, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(dependencies), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, TimeSpan slidingExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(slidingExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, DateTime absoluteExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(absoluteExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, CacheInvalidation invalidation, Func valueFactory) + { + return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + var key = ComputeMemoizationCacheKey(valueFactory, arg1, arg2, arg3, arg4); + return Memoize(cache, key, invalidation, FuncFactory.Create(valueFactory, arg1, arg2, arg3, arg4)); + }; + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IDependency dependency, Func valueFactory) + { + return Memoize(cache, Arguments.Yield(dependency), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, IEnumerable dependencies, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(dependencies), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The sliding expiration time from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, TimeSpan slidingExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(slidingExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The absolute expiration date time value from when the cached entry becomes invalid and is removed from the cache. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, DateTime absoluteExpiration, Func valueFactory) + { + return Memoize(cache, new CacheInvalidation(absoluteExpiration), valueFactory); + } + + /// + /// Memoizes the specified in the cache for fast access. + /// + /// The type of the key in the cache. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The type of the value in the cache. + /// The to extend. + /// The object that contains expiration details for a specific cache entry. + /// The function delegate used to provide a memoized value. + /// A memoized function delegate that is otherwise equivalent to . + public static Func Memoize(this ICacheEnumerable cache, CacheInvalidation invalidation, Func valueFactory) + { + return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + var key = ComputeMemoizationCacheKey(valueFactory, arg1, arg2, arg3, arg4, arg5); + return Memoize(cache, key, invalidation, FuncFactory.Create(valueFactory, arg1, arg2, arg3, arg4, arg5)); + }; + } + + private static readonly object PadLock = new object(); + + private static TResult Memoize(ICacheEnumerable cache, string key, CacheInvalidation invalidation, FuncFactory valueFactory) where TTuple : Template + { + if (cache.TryGetCacheEntry(key, MemoizationScope, out var cacheEntry)) { return (TResult)cacheEntry.Value; } + lock (PadLock) + { + if (!cache.TryGet(key, MemoizationScope, out var value)) + { + value = valueFactory.ExecuteMethod(); + cache.Add(new CacheEntry(key, value, MemoizationScope), invalidation); + } + return (TResult)value; + } + } + + private static string ComputeMemoizationCacheKey(Delegate del, params object[] args) + { + var result = del == null || del.GetMethodInfo() == null + ? MemoizationNullHashCode.GetHashCode() + : MethodDescriptor.Create(del.GetMethodInfo()).ToString().GetHashCode(); + + foreach (var arg in args) + { + var current = arg ?? MemoizationNullHashCode; + var bytes = current as byte[]; + result ^= bytes == null + ? current.GetHashCode() + : Generate.HashCode32(bytes.Cast()); + } + + return result.ToString(CultureInfo.InvariantCulture); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Runtime.Caching/Cuemon.Extensions.Runtime.Caching.csproj b/src/Cuemon.Extensions.Runtime.Caching/Cuemon.Extensions.Runtime.Caching.csproj new file mode 100644 index 000000000..1524e8f9e --- /dev/null +++ b/src/Cuemon.Extensions.Runtime.Caching/Cuemon.Extensions.Runtime.Caching.csproj @@ -0,0 +1,19 @@ + + + + net5.0;netstandard2.0 + 1f0bdf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Extensions.Runtime.Caching + Cuemon.Extensions.Runtime.Caching + The Cuemon.Extensions.Runtime.Caching namespace contains extension methods that complements the Cuemon.Runtime.Caching namespace by adding support for Memoization techniques and GetOrAdd convenience; both with vast overloads and extended by the ICacheEnumerable{TKey} interface for loose coupling. + extension-methods extensions memoization memoize get-or-add thread-safe caching + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.Runtime.Caching/GlobalSuppressions.cs b/src/Cuemon.Extensions.Runtime.Caching/GlobalSuppressions.cs new file mode 100644 index 000000000..b17e8ffe3 --- /dev/null +++ b/src/Cuemon.Extensions.Runtime.Caching/GlobalSuppressions.cs @@ -0,0 +1,27 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``4(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.Caching.CacheInvalidation,System.Func{``1,``2,``3})~System.Func{``1,``2,``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``4(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.IDependency,System.Func{``1,``2,``3})~System.Func{``1,``2,``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``4(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.Collections.Generic.IEnumerable{Cuemon.Runtime.IDependency},System.Func{``1,``2,``3})~System.Func{``1,``2,``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``4(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.DateTime,System.Func{``1,``2,``3})~System.Func{``1,``2,``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``4(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.TimeSpan,System.Func{``1,``2,``3})~System.Func{``1,``2,``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``5(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.Caching.CacheInvalidation,System.Func{``1,``2,``3,``4})~System.Func{``1,``2,``3,``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``5(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.IDependency,System.Func{``1,``2,``3,``4})~System.Func{``1,``2,``3,``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``5(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.Collections.Generic.IEnumerable{Cuemon.Runtime.IDependency},System.Func{``1,``2,``3,``4})~System.Func{``1,``2,``3,``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``5(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.DateTime,System.Func{``1,``2,``3,``4})~System.Func{``1,``2,``3,``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``5(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.TimeSpan,System.Func{``1,``2,``3,``4})~System.Func{``1,``2,``3,``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``6(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.Caching.CacheInvalidation,System.Func{``1,``2,``3,``4,``5})~System.Func{``1,``2,``3,``4,``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``6(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.IDependency,System.Func{``1,``2,``3,``4,``5})~System.Func{``1,``2,``3,``4,``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``6(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.Collections.Generic.IEnumerable{Cuemon.Runtime.IDependency},System.Func{``1,``2,``3,``4,``5})~System.Func{``1,``2,``3,``4,``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``6(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.DateTime,System.Func{``1,``2,``3,``4,``5})~System.Func{``1,``2,``3,``4,``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``6(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.TimeSpan,System.Func{``1,``2,``3,``4,``5})~System.Func{``1,``2,``3,``4,``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``7(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.Caching.CacheInvalidation,System.Func{``1,``2,``3,``4,``5,``6})~System.Func{``1,``2,``3,``4,``5,``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``7(Cuemon.Runtime.Caching.ICacheEnumerable{``0},Cuemon.Runtime.IDependency,System.Func{``1,``2,``3,``4,``5,``6})~System.Func{``1,``2,``3,``4,``5,``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``7(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.Collections.Generic.IEnumerable{Cuemon.Runtime.IDependency},System.Func{``1,``2,``3,``4,``5,``6})~System.Func{``1,``2,``3,``4,``5,``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``7(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.DateTime,System.Func{``1,``2,``3,``4,``5,``6})~System.Func{``1,``2,``3,``4,``5,``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments (more under certain conditions).", Scope = "member", Target = "~M:Cuemon.Extensions.Runtime.Caching.CacheEnumerableExtensions.Memoize``7(Cuemon.Runtime.Caching.ICacheEnumerable{``0},System.TimeSpan,System.Func{``1,``2,``3,``4,``5,``6})~System.Func{``1,``2,``3,``4,``5,``6}")] diff --git a/src/Cuemon.Extensions.Runtime.Caching/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.Runtime.Caching/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d1b06c7c3 --- /dev/null +++ b/src/Cuemon.Extensions.Runtime.Caching/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("7db84665-da22-440e-b60b-9ab4a48c4122")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Runtime.Caching/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Runtime.Caching/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..a81acd5e0 --- /dev/null +++ b/src/Cuemon.Extensions.Runtime.Caching/Properties/PackageReleaseNotes.txt @@ -0,0 +1,5 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED CacheEnumerableExtensions class in the Cuemon.Extensions.Runtime.Caching namespace that consist of extension methods for the ICacheEnumerable{TKey} interface: GetOrAdd, Memoize \ No newline at end of file diff --git a/src/Cuemon.Extensions.Text/Cuemon.Extensions.Text.csproj b/src/Cuemon.Extensions.Text/Cuemon.Extensions.Text.csproj index 1ee5e3754..3116acceb 100644 --- a/src/Cuemon.Extensions.Text/Cuemon.Extensions.Text.csproj +++ b/src/Cuemon.Extensions.Text/Cuemon.Extensions.Text.csproj @@ -1,32 +1,15 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 0a0bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.Text Cuemon.Extensions.Text - The Cuemon.Extensions.Text assembly provides extension methods to the Cuemon.Text namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Text namespace contains extension methods that complements the Cuemon.Text namespace while being an addition to the System namespace. + extension-methods extensions to-encoded-string to-ascii-encoded-string diff --git a/src/Cuemon.Extensions.Text/EncodingOptionsExtensions.cs b/src/Cuemon.Extensions.Text/EncodingOptionsExtensions.cs index ca5bd7831..5c741ae31 100644 --- a/src/Cuemon.Extensions.Text/EncodingOptionsExtensions.cs +++ b/src/Cuemon.Extensions.Text/EncodingOptionsExtensions.cs @@ -5,7 +5,7 @@ namespace Cuemon.Extensions.Text { /// - /// Extension methods for the class. + /// Extension methods for the interface. /// public static class EncodingOptionsExtensions { diff --git a/src/Cuemon.Extensions.Text/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Text/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..5558e3cf6 --- /dev/null +++ b/src/Cuemon.Extensions.Text/Properties/PackageReleaseNotes.txt @@ -0,0 +1,7 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED EncodingOptionsExtensions class in the Cuemon.Extensions.Text namespace that consist of extension methods for the EncodingOptionsExtensions class: DetectUnicodeEncoding +- ADDED StringExtensions class in the Cuemon.Extensions.Text namespace that consist of extension methods for the string class: ToEncodedString, ToAsciiEncodedString +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Threading/Cuemon.Extensions.Threading.csproj b/src/Cuemon.Extensions.Threading/Cuemon.Extensions.Threading.csproj index c0dc2ac85..772b88e60 100644 --- a/src/Cuemon.Extensions.Threading/Cuemon.Extensions.Threading.csproj +++ b/src/Cuemon.Extensions.Threading/Cuemon.Extensions.Threading.csproj @@ -1,32 +1,15 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 180bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Extensions.Threading Cuemon.Extensions.Threading - The Cuemon.Extensions.Threading assembly provides enhancements to the System.Threading namespace as well as extensions methods for XML scenarios such as escaping, conversions, parsing, serialization and deserialization. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Threading namespace contains extension methods that complements the System.Threading namespace. + extension-methods extensions continue-with-captured-context continue-with-suppressed-context diff --git a/src/Cuemon.Extensions.Threading/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Threading/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..910f8c3a0 --- /dev/null +++ b/src/Cuemon.Extensions.Threading/Properties/PackageReleaseNotes.txt @@ -0,0 +1,6 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED TaskExtensions class in the Cuemon.Extensions.Threading.Tasks namespace that consist of extension methods for the Task class: ContinueWithCapturedContext, ContinueWithCapturedContext{TResult}, ContinueWithSuppressedContext, ContinueWithSuppressedContext{TResult} +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Threading/Tasks/TaskExtensions.cs b/src/Cuemon.Extensions.Threading/Tasks/TaskExtensions.cs index a7a9d6e04..e9d6d57e2 100644 --- a/src/Cuemon.Extensions.Threading/Tasks/TaskExtensions.cs +++ b/src/Cuemon.Extensions.Threading/Tasks/TaskExtensions.cs @@ -4,7 +4,7 @@ namespace Cuemon.Extensions.Threading.Tasks { /// - /// Extension methods for the . + /// Extension methods for the class. /// public static class TaskExtensions { diff --git a/src/Cuemon.Extensions.Xml/Cuemon.Extensions.Xml.csproj b/src/Cuemon.Extensions.Xml/Cuemon.Extensions.Xml.csproj index bea42dad1..953ccd66b 100644 --- a/src/Cuemon.Extensions.Xml/Cuemon.Extensions.Xml.csproj +++ b/src/Cuemon.Extensions.Xml/Cuemon.Extensions.Xml.csproj @@ -1,35 +1,19 @@  - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 0c0bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Extensions.Xml Cuemon.Extensions.Xml - The Cuemon.Extensions.Xml assembly provides several extension methods for XML scenarios such as escaping, conversions, parsing, sanitizing, serialization and deserialization. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US - - - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT + The Cuemon.Extensions.Xml namespace contains extension methods that complements the Cuemon.Xml namespace while being an addition to the System.Xml namespace. + extension-methods extensions to-xml-reader copy-xml-stream try-detect-xml-encoding chunk to-stream write-object is-xml-string try-parse-xelement + diff --git a/src/Cuemon.Extensions.Xml/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Xml/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..19c144274 --- /dev/null +++ b/src/Cuemon.Extensions.Xml/Properties/PackageReleaseNotes.txt @@ -0,0 +1,17 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Breaking Changes +- RENAMED Copy --> ToStream on the XmlReaderExtensions class in the Cuemon.Xml namespace (also removed generic type parameters) +  +# New Features +- ADDED XmlConverterExtensions class in the Cuemon.Extensions.Xml.Serialization.Converters namespace that consist of extension methods for the IList{XmlConverter} interface: FirstOrDefaultReaderConverter, FirstOrDefaultWriterConverter, AddXmlConverter, InsertXmlConverter, AddEnumerableConverter, AddExceptionDescriptorConverter, AddUriConverter, AddDateTimeConverter, AddTimeSpanConverter, AddStringConverter, AddExceptionConverter +- ADDED ExceptionDescriptorExtensions class in the Cuemon.Extensions.Xml.Serialization.Diagnostics namespace that consist of extension methods for the ExceptionDescriptor class: ToInsightsXmlString +- ADDED XmlSerializerOptionsExtensions class in the Cuemon.Extensions.Xml.Serialization namespace that consist of extension methods for the XmlSerializerOptions class: ApplyToDefaultSettings +- ADDED ByteArrayExtensions class in the Cuemon.Extensions.Xml namespace that consist of extension methods for the byte[] struct: ToXmlReader +- ADDED HierarchyExtensions class in the Cuemon.Extensions.Xml namespace that consist of extension methods for the IHierarchy{T} interface: IsNodeEnumerable, GetXmlRootOrElement, OrderByXmlAttributes +- ADDED StreamExtensions class in the Cuemon.Extensions.Xml namespace that consist of extension methods for the Stream class: ToXmlReader, CopyXmlStream, TryDetectXmlEncoding +- ADDED UriExtensions class in the Cuemon.Extensions.Xml namespace that consist of extension methods for the Uri class: ToXmlReader +- ADDED XmlReaderExtensions class in the Cuemon.Extensions.Xml namespace that consist of extension methods for the XmlReader class: ToHierarchy, MoveToFirstElement +- ADDED XmlWriterExtensions class in the Cuemon.Extensions.Xml namespace that consist of extension methods for the XmlWriter class: WriteObject, WriteObject{T}, WriteStartElement, WriteEncapsulatingElementWhenNotNull{T}, WriteXmlRootElement{T} +  \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xml/StreamExtensions.cs b/src/Cuemon.Extensions.Xml/StreamExtensions.cs index 34a711189..99393e9e2 100644 --- a/src/Cuemon.Extensions.Xml/StreamExtensions.cs +++ b/src/Cuemon.Extensions.Xml/StreamExtensions.cs @@ -51,7 +51,7 @@ public static Stream CopyXmlStream(this Stream value, Action } var options = Patterns.Configure(setup); - return Disposable.SafeInvoke(() => new MemoryStream(), ms => + return Patterns.SafeInvoke(() => new MemoryStream(), ms => { var document = new XmlDocument(); document.Load(value); @@ -94,7 +94,7 @@ public static Stream RemoveXmlNamespaceDeclarations(this Stream value, Action new MemoryStream(), ms => + return Patterns.SafeInvoke(() => new MemoryStream(), ms => { using (var writer = XmlWriter.Create(ms, options)) { diff --git a/src/Cuemon.Extensions.Xml/StringExtensions.cs b/src/Cuemon.Extensions.Xml/StringExtensions.cs index 0ff3aa3be..2cea96ee2 100644 --- a/src/Cuemon.Extensions.Xml/StringExtensions.cs +++ b/src/Cuemon.Extensions.Xml/StringExtensions.cs @@ -8,8 +8,6 @@ namespace Cuemon.Extensions.Xml /// public static class StringExtensions { - private static readonly string[][] EscapeStringPairs = new[] { new[] { "<", ">", """, "'", "&" }, new[] {"<", ">", "\"", "'", "&"} }; - /// /// Escapes the given XML . /// diff --git a/src/Cuemon.Xml/XmlCopyOptions.cs b/src/Cuemon.Extensions.Xml/XmlCopyOptions.cs similarity index 95% rename from src/Cuemon.Xml/XmlCopyOptions.cs rename to src/Cuemon.Extensions.Xml/XmlCopyOptions.cs index 27fbcf933..135a37593 100644 --- a/src/Cuemon.Xml/XmlCopyOptions.cs +++ b/src/Cuemon.Extensions.Xml/XmlCopyOptions.cs @@ -1,7 +1,7 @@ using System; using System.Xml; -namespace Cuemon.Xml +namespace Cuemon.Extensions.Xml { /// /// Configuration options for . diff --git a/src/Cuemon.Extensions.Xml/XmlWriterExtensions.cs b/src/Cuemon.Extensions.Xml/XmlWriterExtensions.cs index dca6cafa8..589575f0c 100644 --- a/src/Cuemon.Extensions.Xml/XmlWriterExtensions.cs +++ b/src/Cuemon.Extensions.Xml/XmlWriterExtensions.cs @@ -2,6 +2,7 @@ using System.Xml; using Cuemon.Xml; using Cuemon.Xml.Serialization; +using Cuemon.Xml.Serialization.Formatters; namespace Cuemon.Extensions.Xml { @@ -16,11 +17,11 @@ public static class XmlWriterExtensions /// The type of the object to serialize. /// The to extend. /// The object to serialize. - /// The which need to be configured. + /// The which may be configured. /// /// cannot be null. /// - public static void WriteObject(this XmlWriter writer, T value, Action setup = null) + public static void WriteObject(this XmlWriter writer, T value, Action setup = null) { WriteObject(writer, value, typeof(T), setup); } @@ -31,11 +32,11 @@ public static void WriteObject(this XmlWriter writer, T value, ActionThe to extend. /// The object to serialize. /// The type of the object to serialize. - /// The which need to be configured. + /// The which may be configured. /// /// cannot be null. /// - public static void WriteObject(this XmlWriter writer, object value, Type objectType, Action setup = null) + public static void WriteObject(this XmlWriter writer, object value, Type objectType, Action setup = null) { Validator.ThrowIfNull(writer, nameof(writer)); Decorator.Enclose(writer).WriteObject(value, objectType, setup); diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/AspNetCoreHostFixtureExtensions.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/AspNetCoreHostFixtureExtensions.cs new file mode 100644 index 000000000..0362342ce --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/AspNetCoreHostFixtureExtensions.cs @@ -0,0 +1,10 @@ +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc +{ + internal static class AspNetCoreHostFixtureExtensions + { + internal static bool HasValidState(this IAspNetCoreHostFixture fixture) + { + return fixture.ConfigureServicesCallback != null && fixture.Host != null && fixture.ServiceProvider != null && fixture.Application != null; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.csproj b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.csproj new file mode 100644 index 000000000..b2e2d9eda --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc.csproj @@ -0,0 +1,25 @@ + + + net5.0;netcoreapp3.1 + 210bdf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc + Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc + The Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core MVC and used in conjunction with Microsoft Dependency Injection. The namespace relates to the Microsoft.AspNetCore.Mvc.Testing namespace. + i-mvc-filter-test mvc-filter-test-factory microsoft dependency injection host configuration hosting-environment service-provider configure-services test-server + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/IMvcFilterTest.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/IMvcFilterTest.cs new file mode 100644 index 000000000..e96a9821a --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/IMvcFilterTest.cs @@ -0,0 +1,17 @@ +using System; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc +{ + /// + /// Represents the members needed for ASP.NET Core MVC filter testing. + /// + /// + /// + /// + /// + /// + /// + public interface IMvcFilterTest : IServiceTest, IPipelineTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable + { + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/MvcFilterAspNetCoreHostTest.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/MvcFilterAspNetCoreHostTest.cs new file mode 100644 index 000000000..c8acffc9d --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/MvcFilterAspNetCoreHostTest.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc +{ + internal class MvcFilterAspNetCoreHostTest : AspNetCoreHostTest, IMvcFilterTest + { + private readonly Action _pipelineConfigurator; + private readonly Action _serviceConfigurator; + + internal MvcFilterAspNetCoreHostTest(Action pipelineConfigurator, Action serviceConfigurator, AspNetCoreHostFixture hostFixture) : base(hostFixture) + { + _pipelineConfigurator = pipelineConfigurator; + _serviceConfigurator = serviceConfigurator; + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.Host.Services; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + } + + protected override void InitializeHostFixture(AspNetCoreHostFixture hostFixture) + { + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + _pipelineConfigurator(app); + } + + public override void ConfigureServices(IServiceCollection services) + { + _serviceConfigurator(services); + } + + /// + /// Called when this object is being disposed by either or having disposing set to true and is false. + /// + protected override void OnDisposeManagedResources() + { + Host?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/MvcFilterTestFactory.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/MvcFilterTestFactory.cs new file mode 100644 index 000000000..6fb1a29c7 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/MvcFilterTestFactory.cs @@ -0,0 +1,27 @@ +using System; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc +{ + /// + /// Provides a set of static methods for ASP.NET Core MVC filter unit testing. + /// + public static class MvcFilterTestFactory + { + /// + /// Creates and returns an implementation. + /// + /// The which may be configured. + /// The which may be configured. + /// An instance of an implementation. + public static IMvcFilterTest CreateMvcFilterTest(Action pipelineSetup = null, Action serviceSetup = null) + { + pipelineSetup ??= app => app.UseFakeHttpResponseTrigger(); + serviceSetup ??= services => services.AddScoped(); + return new MvcFilterAspNetCoreHostTest(pipelineSetup, serviceSetup, new AspNetCoreHostFixture()); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ac11a1b83 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("66569727-6792-48ee-84fe-8cc685772716")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..cae6f5bd9 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc/Properties/PackageReleaseNotes.txt @@ -0,0 +1,6 @@ +Version: 6.0.0 +Availability: NET Core 3.1, NET 5.0 +  +# New Features +- ADDED IMvcFilterTest interface in the Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc namespace that represents the members needed for ASP.NET Core MVC filter testing +- ADDED MvcFilterTestFactory class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc namespace that provides a set of static methods for ASP.NET Core MVC filter unit testing \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/ApplicationBuilderExtensions.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..6a87ba309 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/ApplicationBuilderExtensions.cs @@ -0,0 +1,25 @@ +using System; +using Cuemon.AspNetCore.Builder; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Builder; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Extension methods for the interface. + /// + public static class ApplicationBuilderExtensions + { + /// + /// Adds a to the request execution pipeline. + /// + /// The type that provides the mechanisms to configure an application’s request pipeline. + /// The which may be configured. + /// A reference to this instance after the operation has completed. + /// + public static IApplicationBuilder UseFakeHttpResponseTrigger(this IApplicationBuilder builder, Action setup = null) + { + return MiddlewareBuilderFactory.UseConfigurableMiddleware(builder, setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs new file mode 100644 index 000000000..4b192519d --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs @@ -0,0 +1,92 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Provides a default implementation of the interface. + /// + /// + /// + public class AspNetCoreHostFixture : HostFixture, IAspNetCoreHostFixture + { + /// + /// Initializes a new instance of the class. + /// + public AspNetCoreHostFixture() + { + } + + /// + /// Creates and configures the of this instance. + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + /// + /// is null. + /// + /// + /// is not assignable from . + /// + public override void ConfigureHost(Test hostTest) + { + var hostTestType = hostTest?.GetType(); + Validator.ThrowIfNull(hostTest, nameof(hostTest)); + Validator.ThrowIfNotContainsType(hostTestType, nameof(hostTestType), $"{nameof(hostTest)} is not assignable from AspNetCoreHostTest.", typeof(AspNetCoreHostTest<>)); + + Host = new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder + .UseTestServer() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseEnvironment("Development") + .ConfigureAppConfiguration((context, config) => + { + config.AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true) + .AddEnvironmentVariables(); + + ConfigureCallback(config.Build(), context.HostingEnvironment); + }) + .ConfigureLogging((context, logging) => + { + logging.AddConfiguration(context.Configuration.GetSection("Logging")); + logging.AddConsole(); + logging.AddDebug(); + logging.AddEventSourceLogger(); + }) + .ConfigureServices((context, services) => + { + Configuration = context.Configuration; + HostingEnvironment = context.HostingEnvironment; + ConfigureServicesCallback(services); + }) + .Configure(app => + { + ConfigureApplicationCallback(app); + Application = app; + } + ); + }).Start(); + } + + /// + /// Gets or sets the delegate that configures the HTTP request pipeline. + /// + /// The delegate that configures the HTTP request pipeline. + public Action ConfigureApplicationCallback { get; set; } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IApplicationBuilder Application { get; protected set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs new file mode 100644 index 000000000..986557639 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs @@ -0,0 +1,10 @@ +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + internal static class AspNetCoreHostFixtureExtensions + { + internal static bool HasValidState(this IAspNetCoreHostFixture fixture) + { + return fixture.ConfigureServicesCallback != null && fixture.Host != null && fixture.ServiceProvider != null && fixture.Application != null; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs new file mode 100644 index 000000000..1dcd9b2c7 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection and depends on ASP.NET Core, should derive. + /// + /// The type of the object that implements the interface. + /// + /// + public abstract class AspNetCoreHostTest : HostTest where T : class, IAspNetCoreHostFixture + { + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// An implementation of the interface. + protected AspNetCoreHostTest(T hostFixture, ITestOutputHelper output = null) : base(hostFixture, output) + { + } + + /// + /// Initializes the specified host fixture. + /// + /// The host fixture to initialize. + protected override void InitializeHostFixture(T hostFixture) + { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.Host.Services; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IApplicationBuilder Application { get; protected set; } + + /// + /// Configures the HTTP request pipeline. + /// + /// The type that provides the mechanisms to configure the HTTP request pipeline. + public abstract void ConfigureApplication(IApplicationBuilder app); + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj new file mode 100644 index 000000000..4f51f9ab0 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj @@ -0,0 +1,28 @@ + + + + net5.0;netcoreapp3.1 + 200bdf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Extensions.Xunit.Hosting.AspNetCore + Cuemon.Extensions.Xunit.Hosting.AspNetCore + The Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core and used in conjunction with Microsoft Dependency Injection. The namespace relates to the Microsoft.AspNetCore.TestHost namespace. + asp-net-core-host-test class-fixture asp-net-core-host-fixture middleware-test-factory microsoft dependency injection host configuration hosting-environment service-provider configure-services test-server + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/FakeHttpContextAccessor.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/FakeHttpContextAccessor.cs new file mode 100644 index 000000000..4d995281f --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/FakeHttpContextAccessor.cs @@ -0,0 +1,33 @@ +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features; +using Cuemon.IO; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http +{ + /// + /// Provides a unit test implementation of . + /// + /// + public class FakeHttpContextAccessor : IHttpContextAccessor + { + + /// + /// Initializes a new instance of the class. + /// + public FakeHttpContextAccessor() + { + var fc = new FeatureCollection(); + fc.Set(new FakeHttpResponseFeature()); + fc.Set(new FakeHttpRequestFeature()); + HttpContext = new DefaultHttpContext(fc); + HttpContext.Response.Body = StreamFactory.Create(writer => writer.Write("Hello awesome developers!")); + } + + /// + /// Gets or sets the HTTP context. + /// + /// The HTTP context. + public HttpContext HttpContext { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpRequestFeature.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpRequestFeature.cs new file mode 100644 index 000000000..fba4953f8 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpRequestFeature.cs @@ -0,0 +1,23 @@ +using System.Net.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features +{ + /// + /// Represents a way to support some default values for Request context.. + /// + /// + public class FakeHttpRequestFeature : HttpRequestFeature + { + /// + /// Initializes a new instance of the class. + /// + public FakeHttpRequestFeature() + { + Method = HttpMethod.Get.ToString(); + Path = "/"; + Scheme = "http"; + Protocol = "HTTP/1.1"; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseFeature.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseFeature.cs new file mode 100644 index 000000000..dc0a281a6 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseFeature.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features +{ + /// + /// Represents a way to trigger . + /// + /// + public class FakeHttpResponseFeature : HttpResponseFeature + { + private bool _hasStarted; + private Func _callback; + private object _state; + + /// + /// Initializes a new instance of the class. + /// + public FakeHttpResponseFeature() + { + Headers.Add(HeaderNames.Date, DateTime.UtcNow.ToString("R")); + } + + /// + /// Registers a callback to be invoked just before the response starts. This is the + /// last chance to modify the , , or + /// . + /// + /// The callback to invoke when starting the response. + /// The state to pass into the callback. + public override void OnStarting(Func callback, object state) + { + if (_hasStarted) { return; } + if (ShortCircuitOnStarting) + { + _hasStarted = true; + callback?.Invoke(state); + } + _callback = callback; + _state = state; + } + + /// + /// Gets a value indicating whether this instance has callback. + /// + /// true if this instance has callback; otherwise, false. + public bool HasOnStartingCallback => _callback != null; + + /// + /// Indicates if the response has started. If true, the , + /// , and are now immutable, and + /// OnStarting should no longer be called. + /// + /// true if this instance has started; otherwise, false. + public override bool HasStarted => _hasStarted; + + /// + /// Gets or sets a value indicating whether is invoked immediately upon initialization. + /// + /// true if is invoked immediately upon initialization; otherwise, false. + public bool ShortCircuitOnStarting { get; set; } + + /// + /// Executes the function delegate assigned by . + /// + /// A task that represents the asynchronous operation. + public Task TriggerOnStartingAsync() + { + _hasStarted = true; + return HasOnStartingCallback ? _callback(_state) : Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseMiddleware.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseMiddleware.cs new file mode 100644 index 000000000..202d77935 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseMiddleware.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; +using Cuemon.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Options; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features +{ + /// + /// Provides a fake HTTP response middleware implementation for ASP.NET Core testing. + /// + /// + public class FakeHttpResponseMiddleware : ConfigurableMiddleware + { + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The which need to be configured. + public FakeHttpResponseMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The which need to be configured. + public FakeHttpResponseMiddleware(RequestDelegate next, Action setup) : base(next, setup) + { + } + + /// + /// Executes the . + /// + /// The context of the current request. + /// A task that represents the execution of this middleware. + public override async Task InvokeAsync(HttpContext context) + { + if (context.Features.Get() is FakeHttpResponseFeature feature) + { + if (Options.ShortCircuitOnStarting) + { + feature.ShortCircuitOnStarting = Options.ShortCircuitOnStarting; + await Next(context).ConfigureAwait(false); + } + else + { + await feature.TriggerOnStartingAsync().ConfigureAwait(false); + } + feature.StatusCode = Options.StatusCode; + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseOptions.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseOptions.cs new file mode 100644 index 000000000..6cf5d076f --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Http/Features/FakeHttpResponseOptions.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Http; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features +{ + /// + /// Configuration options for . + /// + public class FakeHttpResponseOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// false + /// + /// + /// + public FakeHttpResponseOptions() + { + StatusCode = StatusCodes.Status200OK; + ShortCircuitOnStarting = false; + } + + /// + /// Gets or sets a value indicating whether is invoked immediately upon initialization. + /// + /// true if is invoked immediately upon initialization; otherwise, false. + public bool ShortCircuitOnStarting { get; set; } + + /// + /// Gets or sets the default status code to set in the feature response. + /// + /// The default status code to set in the feature response. + public int StatusCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IAspNetCoreHostFixture.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IAspNetCoreHostFixture.cs new file mode 100644 index 000000000..17e693a9a --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IAspNetCoreHostFixture.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.AspNetCore.Builder; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Provides a way to use Microsoft Dependency Injection in unit tests tailored for ASP.NET Core. + /// + /// + public interface IAspNetCoreHostFixture : IHostFixture, IPipelineTest + { + /// + /// Gets or sets the delegate that configures the HTTP request pipeline. + /// + /// The delegate that configures the HTTP request pipeline. + Action ConfigureApplicationCallback { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IMiddlewareTest.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IMiddlewareTest.cs new file mode 100644 index 000000000..bc8d91203 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IMiddlewareTest.cs @@ -0,0 +1,16 @@ +using System; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Represents the members needed for ASP.NET Core middleware testing. + /// + /// + /// + /// + /// + /// + public interface IMiddlewareTest : IServiceTest, IPipelineTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable + { + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IPipelineTest.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IPipelineTest.cs new file mode 100644 index 000000000..7988c100d --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/IPipelineTest.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Represents the members needed for ASP.NET Core pipeline testing. + /// + public interface IPipelineTest + { + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + IApplicationBuilder Application { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/MiddlewareAspNetCoreHostTest.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/MiddlewareAspNetCoreHostTest.cs new file mode 100644 index 000000000..eaa266b7e --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/MiddlewareAspNetCoreHostTest.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + internal class MiddlewareAspNetCoreHostTest : AspNetCoreHostTest, IMiddlewareTest + { + private readonly Action _pipelineConfigurator; + private readonly Action _serviceConfigurator; + + internal MiddlewareAspNetCoreHostTest(Action pipelineConfigurator, Action serviceConfigurator, AspNetCoreHostFixture hostFixture) : base(hostFixture) + { + _pipelineConfigurator = pipelineConfigurator; + _serviceConfigurator = serviceConfigurator; + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.Host.Services; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + } + + protected override void InitializeHostFixture(AspNetCoreHostFixture hostFixture) + { + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + _pipelineConfigurator(app); + } + + public override void ConfigureServices(IServiceCollection services) + { + _serviceConfigurator(services); + } + + /// + /// Called when this object is being disposed by either or having disposing set to true and is false. + /// + protected override void OnDisposeManagedResources() + { + Host?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/MiddlewareTestFactory.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/MiddlewareTestFactory.cs new file mode 100644 index 000000000..f32bebff7 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/MiddlewareTestFactory.cs @@ -0,0 +1,27 @@ +using System; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Provides a set of static methods for ASP.NET Core middleware unit testing. + /// + public static class MiddlewareTestFactory + { + /// + /// Creates and returns an implementation. + /// + /// The which may be configured. + /// The which may be configured. + /// An instance of an implementation. + public static IMiddlewareTest CreateMiddlewareTest(Action pipelineSetup = null, Action serviceSetup = null) + { + pipelineSetup ??= app => app.UseFakeHttpResponseTrigger(); + serviceSetup ??= services => services.AddScoped(); + return new MiddlewareAspNetCoreHostTest(pipelineSetup, serviceSetup, new AspNetCoreHostFixture()); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d734d0852 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("eecbaeec-0593-4e91-9ef3-417e986bc341")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..a7641d7d5 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Properties/PackageReleaseNotes.txt @@ -0,0 +1,14 @@ +Version: 6.0.0 +Availability: NET Core 3.1, NET 5.0 +  +# New Features +- ADDED FakeHttpResponseFeature class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features namespace that represents a way to trigger IHttpResponseFeature.OnStarting +- ADDED FakeHttpResponseMiddleware class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http.Features namespace that provides a fake HTTP response middleware implementation for ASP.NET Core testing +- ADDED FakeHttpContextAccessor class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http namespace that provides a unit test implementation of IHttpContextAccessor +- ADDED ApplicationBuilderExtensions class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace that consist of extension methods for the IApplicationBuilder interface: UseFakeHttpResponseTrigger +- ADDED AspNetCoreHostFixture class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace that provides a default implementation of the IAspNetCoreHostFixture interface +- ADDED AspNetCoreHostTest{T} class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace that represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection and depends on ASP.NET Core, should derive +- ADDED IAspNetCoreHostFixture interface in the Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace that provides a way to use Microsoft Dependency Injection in unit tests tailored for ASP.NET Core +- ADDED IMiddlewareTest interface in the Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace that represents the members needed for ASP.NET Core middleware testing +- ADDED IPipelineTest interface in the Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace that represents the members needed for ASP.NET Core pipeline testing +- ADDED MiddlewareTestFactory class in the Cuemon.Extensions.Xunit.Hosting.AspNetCore namespace that provides a set of static methods for ASP.NET Core middleware unit testing \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..3d105b9f2 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/ServiceCollectionExtensions.cs @@ -0,0 +1,35 @@ +using Cuemon.Extensions.DependencyInjection; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Extension methods for the interface. + /// + public static class ServiceCollectionExtensions + { + /// + /// Adds a unit test optimized implementation for the service. + /// + /// The to extend. + /// The lifetime of the service. + /// A reference to after the operation has completed. + public static IServiceCollection AddFakeHttpContextAccessor(this IServiceCollection services, ServiceLifetime lifetime) + { + services.TryAdd(provider => + { + var contextAccessor = new FakeHttpContextAccessor + { + HttpContext = + { + RequestServices = provider + } + }; + return contextAccessor; + }, lifetime); + return services; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/Cuemon.Extensions.Xunit.Hosting.csproj b/src/Cuemon.Extensions.Xunit.Hosting/Cuemon.Extensions.Xunit.Hosting.csproj new file mode 100644 index 000000000..eefae00cc --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/Cuemon.Extensions.Xunit.Hosting.csproj @@ -0,0 +1,46 @@ + + + + net5.0;netcoreapp3.0;netstandard2.0 + 1e0bdf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Extensions.Xunit.Hosting + Cuemon.Extensions.Xunit.Hosting + The Cuemon.Extensions.Xunit.Hosting namespace contains types that provides a uniform way of doing unit testing used in conjunction with Microsoft Dependency Injection. The namespace relates to the Xunit.Abstractions namespace. + host-test class-fixture host-fixture microsoft dependency injection host configuration hosting-environment service-provider configure-services + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/GlobalSuppressions.cs b/src/Cuemon.Extensions.Xunit.Hosting/GlobalSuppressions.cs new file mode 100644 index 000000000..1dd1d65e6 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Minor Code Smell", "S3459:Unassigned members should be removed", Justification = "False-Positive. Convention based assignment to variable.", Scope = "member", Target = "~F:Cuemon.Extensions.Xunit.Hosting.HostTest`1._configuration")] +[assembly: SuppressMessage("Minor Code Smell", "S3459:Unassigned members should be removed", Justification = "False-Positive. Convention based assignment to variable.", Scope = "member", Target = "~F:Cuemon.Extensions.Xunit.Hosting.HostTest`1._hostingEnvironment")] diff --git a/src/Cuemon.Extensions.Xunit.Hosting/HostFixture.cs b/src/Cuemon.Extensions.Xunit.Hosting/HostFixture.cs new file mode 100644 index 000000000..85aca9540 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/HostFixture.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + /// + /// Provides a default implementation of the interface. + /// + /// + /// + public class HostFixture : Disposable, IHostFixture + { + /// + /// Initializes a new instance of the class. + /// + public HostFixture() + { + } + + /// + /// Creates and configures the of this instance. + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + /// + /// is null. + /// + /// + /// is not assignable from . + /// + public virtual void ConfigureHost(Test hostTest) + { + var hostTestType = hostTest?.GetType(); + Validator.ThrowIfNull(hostTest, nameof(hostTest)); + Validator.ThrowIfNotContainsType(hostTestType, nameof(hostTestType), $"{nameof(hostTest)} is not assignable from HostTest.", typeof(HostTest<>)); + + Host = new HostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseEnvironment("Development") + .ConfigureAppConfiguration((context, config) => + { + config + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true) + .AddEnvironmentVariables(); + + ConfigureCallback(config.Build(), context.HostingEnvironment); + }) + .ConfigureServices((context, services) => + { + Configuration = context.Configuration; + HostingEnvironment = context.HostingEnvironment; + ConfigureServicesCallback(services); + }).Build(); + } + + #if NETSTANDARD + /// + /// Gets or sets the delegate that initializes the test class. + /// + /// The delegate that initializes the test class. + /// Mimics the Startup convention. + public Action ConfigureCallback { get; set; } + #elif NETCOREAPP + /// + /// Gets or sets the delegate that initializes the test class. + /// + /// The delegate that initializes the test class. + /// Mimics the Startup convention. + public Action ConfigureCallback { get; set; } + #endif + + /// + /// Gets or sets the delegate that adds services to the container. + /// + /// The delegate that adds services to the container. + public Action ConfigureServicesCallback { get; set; } + + /// + /// Gets or sets the initialized by this instance. + /// + /// The initialized by this instance. + public IHost Host { get; protected set; } + + /// + /// Gets the initialized by this instance. + /// + /// The initialized by this instance. + public IServiceProvider ServiceProvider => Host.Services; + + /// + /// Gets the initialized by this instance. + /// + /// The initialized by this instance. + public IConfiguration Configuration { get; protected set; } + + #if NETSTANDARD + /// + /// Gets the initialized by this instance. + /// + /// The initialized by this instance. + public IHostingEnvironment HostingEnvironment { get; protected set; } + #elif NETCOREAPP + /// + /// Gets the initialized by this instance. + /// + /// The initialized by this instance. + public IHostEnvironment HostingEnvironment { get; protected set; } + #endif + + /// + /// Called when this object is being disposed by either or having disposing set to true and is false. + /// + protected override void OnDisposeManagedResources() + { + if (ServiceProvider is ServiceProvider sp) + { + sp.Dispose(); + } + Host?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/HostFixtureExtensions.cs b/src/Cuemon.Extensions.Xunit.Hosting/HostFixtureExtensions.cs new file mode 100644 index 000000000..779f431a9 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/HostFixtureExtensions.cs @@ -0,0 +1,10 @@ +namespace Cuemon.Extensions.Xunit.Hosting +{ + internal static class HostFixtureExtensions + { + internal static bool HasValidState(this IHostFixture fixture) + { + return fixture.ConfigureServicesCallback != null && fixture.Host != null && fixture.ServiceProvider != null; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/HostTest.cs b/src/Cuemon.Extensions.Xunit.Hosting/HostTest.cs new file mode 100644 index 000000000..756e10bbe --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/HostTest.cs @@ -0,0 +1,121 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + /// + /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection, should derive. + /// + /// The type of the object that implements the interface. + /// + /// + /// The class needed to be designed in this rather complex way, as this is the only way that xUnit supports a shared context. The need for shared context is theoretical at best, but it does opt-in for Scoped instances. + public abstract class HostTest : Test, IClassFixture where T : class, IHostFixture + { + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// An implementation of the interface. + protected HostTest(T hostFixture, ITestOutputHelper output = null) : base(output) + { + Validator.ThrowIfNull(hostFixture, nameof(hostFixture)); + InitializeHostFixture(hostFixture); + } + + /// + /// Initializes the specified host fixture. + /// + /// The host fixture to initialize. + protected virtual void InitializeHostFixture(T hostFixture) + { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.ServiceProvider; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IHost Host { get; protected set; } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IServiceProvider ServiceProvider { get; protected set; } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IConfiguration Configuration + { + get; + private set; + } + + #if NETSTANDARD + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IHostingEnvironment HostingEnvironment + { + get; + private set; + } + #elif NETCOREAPP + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IHostEnvironment HostingEnvironment + { + get; + private set; + } + #endif + + #if NETSTANDARD + /// + /// Adds and to this instance. + /// + /// The initialized by the . + /// The initialized by the . + public virtual void Configure(IConfiguration configuration, IHostingEnvironment environment) + { + Configuration = configuration; + HostingEnvironment = environment; + } + #elif NETCOREAPP + /// + /// Adds and to this instance. + /// + /// The initialized by the . + /// The initialized by the . + public virtual void Configure(IConfiguration configuration, IHostEnvironment environment) + { + Configuration = configuration; + HostingEnvironment = environment; + } + #endif + + /// + /// Adds services to the container. + /// + /// The collection of service descriptors. + public abstract void ConfigureServices(IServiceCollection services); + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/IConfigurationTest.cs b/src/Cuemon.Extensions.Xunit.Hosting/IConfigurationTest.cs new file mode 100644 index 000000000..246ff3ddc --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/IConfigurationTest.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + /// + /// Represents the members needed for ASP.NET Core testing with support for Configuration. + /// + public interface IConfigurationTest + { + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + IConfiguration Configuration { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/IHostFixture.cs b/src/Cuemon.Extensions.Xunit.Hosting/IHostFixture.cs new file mode 100644 index 000000000..a5f4d3318 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/IHostFixture.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + /// + /// Provides a way to use Microsoft Dependency Injection in unit tests. + /// + /// + public interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest + { + #if NETSTANDARD + /// + /// Gets or sets the delegate that adds configuration and environment information to a . + /// + /// The delegate that adds configuration and environment information to a . + Action ConfigureCallback { get; set; } + #elif NETCOREAPP + /// + /// Gets or sets the delegate that adds configuration and environment information to a . + /// + /// The delegate that adds configuration and environment information to a . + Action ConfigureCallback { get; set; } + #endif + + /// + /// Gets or sets the delegate that adds services to the container. + /// + /// The delegate that adds services to the container. + Action ConfigureServicesCallback { get; set; } + + /// + /// Creates and configures the of this . + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + void ConfigureHost(Test hostTest); + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/IHostTest.cs b/src/Cuemon.Extensions.Xunit.Hosting/IHostTest.cs new file mode 100644 index 000000000..172aaa516 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/IHostTest.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + /// + /// Represents the members needed for ASP.NET Core host testing. + /// + public interface IHostTest + { + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + IHost Host { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/IHostingEnvironmentTest.cs b/src/Cuemon.Extensions.Xunit.Hosting/IHostingEnvironmentTest.cs new file mode 100644 index 000000000..9106d18ac --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/IHostingEnvironmentTest.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + /// + /// Represents the members needed for ASP.NET Core testing with support for HostingEnvironment. + /// + public interface IHostingEnvironmentTest + { + #if NETSTANDARD + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + IHostingEnvironment HostingEnvironment { get; } + #elif NETCOREAPP + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + IHostEnvironment HostingEnvironment { get; } + #endif + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/IServiceTest.cs b/src/Cuemon.Extensions.Xunit.Hosting/IServiceTest.cs new file mode 100644 index 000000000..27ccf4816 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/IServiceTest.cs @@ -0,0 +1,17 @@ +using System; +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + /// + /// Represents the members needed for ASP.NET Core services testing. + /// + public interface IServiceTest + { + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + IServiceProvider ServiceProvider { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.Xunit.Hosting/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..891fbbf3f --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("413e5c92-990d-481d-99e9-e6214ae0d0d2")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit.Hosting/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Xunit.Hosting/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..4e453d52d --- /dev/null +++ b/src/Cuemon.Extensions.Xunit.Hosting/Properties/PackageReleaseNotes.txt @@ -0,0 +1,7 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Core 3.0, NET 5.0 +  +# New Features +- ADDED HostTest class in the Cuemon.Extensions.Xunit.Hosting namespace that represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection, should derive +- ADDED IHostFixture interface in the Cuemon.Extensions.Xunit.Hosting namespace that provides a way to use Microsoft Dependency Injection in unit tests +- ADDED HostFixture class in the Cuemon.Extensions.Xunit.Hosting namespace that provides a default implementation of the IHostFixture interface \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit/Cuemon.Extensions.Xunit.csproj b/src/Cuemon.Extensions.Xunit/Cuemon.Extensions.Xunit.csproj index 4edfee77e..e368cfa51 100644 --- a/src/Cuemon.Extensions.Xunit/Cuemon.Extensions.Xunit.csproj +++ b/src/Cuemon.Extensions.Xunit/Cuemon.Extensions.Xunit.csproj @@ -1,48 +1,17 @@  - netstandard2.0;netcoreapp3.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 0d0bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon .NET Standard Cuemon.Extensions.Xunit Cuemon.Extensions.Xunit - The Cuemon.Extensions.Xunit assembly provides extension methods and general improvements to the xunit.abstractions package. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Extensions.Xunit namespace contains types that provides a uniform way of doing unit testing. The namespace relates to the Xunit.Abstractions namespace. + test test-output test-disposable test-cleanup - - https://nblcdn.net/themes/cuemon.net/img/core/128x128x.png - https://www.cuemon.net/ - https://opensource.org/licenses/MIT - - - - - - - - - - - - - - - - diff --git a/src/Cuemon.Extensions.Xunit/HostTest.cs b/src/Cuemon.Extensions.Xunit/HostTest.cs deleted file mode 100644 index ac4df2f58..000000000 --- a/src/Cuemon.Extensions.Xunit/HostTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.IO; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Xunit.Abstractions; - -namespace Cuemon.Extensions.Xunit -{ - public abstract class HostTest : Test - { - protected HostTest(ITestOutputHelper output = null) : base(output) - { - Host = new HostBuilder() - .ConfigureHostConfiguration(config => config.AddEnvironmentVariables("DOTNET_")) - .ConfigureAppConfiguration((context, config) => - { - config.SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true) - .AddEnvironmentVariables(); - }) - .ConfigureServices((context, services) => - { - ConfigureServices(services); - Configuration = context.Configuration; - HostingEnvironment = context.HostingEnvironment; - ServiceProvider = services.BuildServiceProvider(); - }).Build(); - } - - public IHost Host { get; } - - public IServiceProvider ServiceProvider { get; private set; } - - public IConfiguration Configuration { get; private set; } - - #if NETSTANDARD - public IHostingEnvironment HostingEnvironment { get; private set; } - #elif NETCOREAPP - public IHostEnvironment HostingEnvironment { get; private set; } - #endif - - public abstract void ConfigureServices(IServiceCollection services); - - protected override void OnDisposeManagedResources() - { - if (ServiceProvider is ServiceProvider sp) - { - sp.Dispose(); - } - Host.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit/Properties/AssemblyInfo.cs b/src/Cuemon.Extensions.Xunit/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5483dd4ec --- /dev/null +++ b/src/Cuemon.Extensions.Xunit/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("45986a3b-0eed-4d59-9e94-ea478d0d61ce")] \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit/Properties/PackageReleaseNotes.txt b/src/Cuemon.Extensions.Xunit/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..66cd50f63 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit/Properties/PackageReleaseNotes.txt @@ -0,0 +1,5 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED Test class in the Cuemon.Extensions.Xunit namespace that represents the base class from which all implementations of unit testing should derive \ No newline at end of file diff --git a/src/Cuemon.Extensions.Xunit/Test.cs b/src/Cuemon.Extensions.Xunit/Test.cs index 2665a5604..c441e9348 100644 --- a/src/Cuemon.Extensions.Xunit/Test.cs +++ b/src/Cuemon.Extensions.Xunit/Test.cs @@ -2,18 +2,39 @@ namespace Cuemon.Extensions.Xunit { + /// + /// Represents the base class from which all implementations of unit testing should derive. + /// + /// + /// public abstract class Test : Disposable { + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// is initialized automatically in an xUnit project. protected Test(ITestOutputHelper output = null) { TestOutput = output; } + /// + /// Gets the console substitute to write out unit test information. + /// + /// The console substitute to write out unit test information. protected ITestOutputHelper TestOutput { get; } - protected bool HasTestOutputEnabled => TestOutput != null; + /// + /// Gets a value indicating whether has a reference to an implementation of . + /// + /// true if this instance has has a reference to an implementation of ; otherwise, false. + protected bool HasTestOutput => TestOutput != null; + /// + /// Called when this object is being disposed by either or having disposing set to true and is false. + /// protected override void OnDisposeManagedResources() { } diff --git a/src/Cuemon.IO/AsyncDisposableOptions.cs b/src/Cuemon.IO/AsyncDisposableOptions.cs new file mode 100644 index 000000000..34f2d1ecf --- /dev/null +++ b/src/Cuemon.IO/AsyncDisposableOptions.cs @@ -0,0 +1,38 @@ +using System; +using Cuemon.Threading; + +namespace Cuemon.IO +{ + /// + /// Configuration options for . + /// + public class AsyncDisposableOptions : AsyncOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// false + /// + /// + /// + public AsyncDisposableOptions() + { + LeaveOpen = false; + } + + /// + /// Gets or sets a value indicating whether a disposable object should bypass the mechanism for releasing unmanaged resources. Default is false. + /// + /// true if a disposable object should bypass the mechanism for releasing unmanaged resources; otherwise, false. + public bool LeaveOpen { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.IO/AsyncStreamCompressionOptions.cs b/src/Cuemon.IO/AsyncStreamCompressionOptions.cs new file mode 100644 index 000000000..753d4c383 --- /dev/null +++ b/src/Cuemon.IO/AsyncStreamCompressionOptions.cs @@ -0,0 +1,38 @@ +using System.IO; +using System.IO.Compression; + +namespace Cuemon.IO +{ + /// + /// Configuration options for compressed . + /// + public class AsyncStreamCompressionOptions : AsyncStreamCopyOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// + /// + /// + /// + public AsyncStreamCompressionOptions() + { + Level = CompressionLevel.Optimal; + } + + /// + /// Gets or sets the enumeration values that indicates whether to emphasize speed or compression efficiency when compressing the stream. + /// + /// The level of the compression. + public CompressionLevel Level { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.IO/AsyncStreamCopyOptions.cs b/src/Cuemon.IO/AsyncStreamCopyOptions.cs new file mode 100644 index 000000000..39bd1dabd --- /dev/null +++ b/src/Cuemon.IO/AsyncStreamCopyOptions.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; + +namespace Cuemon.IO +{ + /// + /// Configuration options that is related to copy operations. + /// + public class AsyncStreamCopyOptions : AsyncDisposableOptions + { + private int _bufferSize; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// 81920 + /// + /// + /// + public AsyncStreamCopyOptions() + { + BufferSize = 81920; + } + + /// + /// Gets or sets the size of the buffer. + /// + /// The size of the buffer. + /// + /// is lower than or equal to 0. + /// + public int BufferSize + { + get => _bufferSize; + set + { + Validator.ThrowIfLowerThanOrEqual(value, 0, nameof(value)); + _bufferSize = value; + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.IO/AsyncStreamEncodingOptions.cs b/src/Cuemon.IO/AsyncStreamEncodingOptions.cs new file mode 100644 index 000000000..164d00afd --- /dev/null +++ b/src/Cuemon.IO/AsyncStreamEncodingOptions.cs @@ -0,0 +1,50 @@ +using System.IO; +using System.Text; +using Cuemon.Text; + +namespace Cuemon.IO +{ + /// + /// Configuration options for . + /// + public class AsyncStreamEncodingOptions : AsyncDisposableOptions, IEncodingOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public AsyncStreamEncodingOptions() + { + Encoding = EncodingOptions.DefaultEncoding; + Preamble = EncodingOptions.DefaultPreambleSequence; + } + + /// + /// Gets or sets the action to take in regards to encoding related preamble sequences. + /// + /// A value that indicates whether to preserve or remove preamble sequences. + public PreambleSequence Preamble { get; set; } + + /// + /// Gets or sets the character encoding to use for the operation. + /// + /// The character encoding to use for the operation. + public Encoding Encoding { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.IO/AsyncStreamReaderOptions.cs b/src/Cuemon.IO/AsyncStreamReaderOptions.cs new file mode 100644 index 000000000..493934f8f --- /dev/null +++ b/src/Cuemon.IO/AsyncStreamReaderOptions.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; +using Cuemon.Text; + +namespace Cuemon.IO +{ + /// + /// Configuration options for . + /// + public class AsyncStreamReaderOptions : AsyncStreamEncodingOptions + { + private int _bufferSize; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// 81920 + /// + /// + /// + public AsyncStreamReaderOptions() + { + BufferSize = 81920; + } + + /// + /// Gets or sets the size of the buffer. + /// + /// The size of the buffer. + /// + /// is lower than or equal to 0. + /// + public int BufferSize + { + get => _bufferSize; + set + { + Validator.ThrowIfLowerThanOrEqual(value, 0, nameof(value)); + _bufferSize = value; + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.IO/Cuemon.IO.csproj b/src/Cuemon.IO/Cuemon.IO.csproj index e01f5a38e..86bdb4e78 100644 --- a/src/Cuemon.IO/Cuemon.IO.csproj +++ b/src/Cuemon.IO/Cuemon.IO.csproj @@ -1,30 +1,20 @@ - + - netstandard2.0;netstandard2.1 - true - ..\cuemon.snk - true + net5.0;netstandard2.1;netstandard2.0 170bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.IO Cuemon.IO - The Cuemon.IO assembly provides extensions methods and a lightweight resilience framework to support transient fault handling. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.IO namespace contains types primarily focusing on configuration options for IO related operations. The namespace is an addition to the System.IO namespace. + text-reader text-writer compress decompress compression decompression conversion encoding brotli gzip deflate async + \ No newline at end of file diff --git a/src/Cuemon.IO/Extensions/StreamDecoratorExtensions.cs b/src/Cuemon.IO/Extensions/StreamDecoratorExtensions.cs index 0035a4720..0abdff209 100644 --- a/src/Cuemon.IO/Extensions/StreamDecoratorExtensions.cs +++ b/src/Cuemon.IO/Extensions/StreamDecoratorExtensions.cs @@ -1,7 +1,10 @@ using System; +using System.ComponentModel; using System.IO; using System.IO.Compression; +using System.Threading; using System.Threading.Tasks; +using Cuemon.Text; namespace Cuemon.IO { @@ -12,7 +15,194 @@ namespace Cuemon.IO /// public static class StreamDecoratorExtensions { - #if NETSTANDARD2_1 + /// + /// Asynchronously reads the bytes from the enclosed of the specified and writes them to the . + /// + /// The to extend. + /// The to which the contents of the current stream will be copied. + /// The size of the buffer. This value must be greater than zero. The default size is 81920. + /// The token to monitor for cancellation requests. The default value is . + /// if true, the enclosed of the specified will temporarily have its position changed to 0; otherwise the position is left untouched. + /// + /// cannot be null. + /// + public static async Task CopyStreamAsync(this IDecorator decorator, Stream destination, int bufferSize = 81920, CancellationToken ct = default, bool changePosition = true) + { + Validator.ThrowIfNull(decorator, nameof(decorator)); + var source = decorator.Inner; + long lastPosition = 0; + if (changePosition && source.CanSeek) + { + lastPosition = source.Position; + if (source.CanSeek) { source.Position = 0; } + } + + await source.CopyToAsync(destination, bufferSize, ct).ConfigureAwait(false); + await destination.FlushAsync(ct).ConfigureAwait(false); + + if (changePosition && source.CanSeek) { source.Position = lastPosition; } + if (changePosition && destination.CanSeek) { destination.Position = 0; } + } + + /// + /// Converts the enclosed of the specified to its equivalent representation. + /// + /// The to extend. + /// The which may be configured. + /// A that is equivalent to the enclosed of the specified . + /// + /// cannot be null. + /// + /// + /// The enclosed of cannot be read from. + /// + public static byte[] ToByteArray(this IDecorator decorator, Action setup = null) + { + Validator.ThrowIfNull(decorator, nameof(decorator)); + Validator.ThrowIfFalse(decorator.Inner.CanRead, nameof(decorator.Inner), "Stream cannot be read from."); + var options = Patterns.Configure(setup); + return decorator.InvokeToByteArray(options.BufferSize, options.LeaveOpen); + } + + /// + /// Converts the enclosed of the specified to its equivalent representation. + /// + /// The to extend. + /// The which may be configured. + /// A task that represents the asynchronous operation. The task result contains a that is equivalent to the enclosed of the specified . + /// + /// cannot be null. + /// + /// + /// The enclosed of cannot be read from. + /// + public static Task ToByteArrayAsync(this IDecorator decorator, Action setup = null) + { + Validator.ThrowIfNull(decorator, nameof(decorator)); + Validator.ThrowIfFalse(decorator.Inner.CanRead, nameof(decorator.Inner), "Stream cannot be read from."); + return ToByteArrayAsyncCore(decorator, Patterns.Configure(setup)); + } + + private static async Task ToByteArrayAsyncCore(this IDecorator decorator, AsyncStreamCopyOptions options) + { + try + { + if (decorator.Inner is MemoryStream s) + { + return s.ToArray(); + } + + using (var memoryStream = new MemoryStream(new byte[decorator.Inner.Length])) + { + var oldPosition = decorator.Inner.Position; + if (decorator.Inner.CanSeek) + { + decorator.Inner.Position = 0; + } + + await decorator.Inner.CopyToAsync(memoryStream, options.BufferSize, options.CancellationToken).ConfigureAwait(false); + if (decorator.Inner.CanSeek) + { + decorator.Inner.Position = oldPosition; + } + + return memoryStream.ToArray(); + } + } + finally + { + if (!options.LeaveOpen) + { + decorator.Inner.Dispose(); + } + } + } + + /// + /// Converts the enclosed of the specified to a . + /// + /// The to extend. + /// The which may be configured. + /// A containing the result of the enclosed of the specified . + /// will be initialized with and . + /// + /// cannot be null. + /// + /// + /// was initialized with an invalid . + /// + public static string ToEncodedString(this IDecorator decorator, Action setup = null) + { + Validator.ThrowIfNull(decorator, nameof(decorator)); + var options = Patterns.Configure(setup); + if (options.Encoding.Equals(EncodingOptions.DefaultEncoding)) + { + options.Encoding = ByteOrderMark.DetectEncodingOrDefault(decorator.Inner, options.Encoding); + } + + if (options.Preamble < PreambleSequence.Keep || options.Preamble > PreambleSequence.Remove) + { + throw new InvalidEnumArgumentException(nameof(setup), (int) options.Preamble, typeof(PreambleSequence)); + } + + var bytes = Decorator.Enclose(decorator.Inner).ToByteArray(o => + { + o.BufferSize = options.BufferSize; + o.LeaveOpen = options.LeaveOpen; + }); + return Convertible.ToString(bytes, o => + { + o.Encoding = options.Encoding; + o.Preamble = options.Preamble; + }); + } + + /// + /// Converts the enclosed of the specified to a . + /// + /// The to extend. + /// The which may be configured. + /// A task that represents the asynchronous operation. The task result contains a containing the result of the enclosed of the specified . + /// will be initialized with and . + /// + /// cannot be null. + /// + /// + /// was initialized with an invalid . + /// + public static Task ToEncodedStringAsync(this IDecorator decorator, Action setup = null) + { + Validator.ThrowIfNull(decorator, nameof(decorator)); + var options = Patterns.Configure(setup); + if (options.Encoding.Equals(EncodingOptions.DefaultEncoding)) + { + options.Encoding = ByteOrderMark.DetectEncodingOrDefault(decorator.Inner, options.Encoding); + } + + if (options.Preamble < PreambleSequence.Keep || options.Preamble > PreambleSequence.Remove) + { + throw new InvalidEnumArgumentException(nameof(setup), (int) options.Preamble, typeof(PreambleSequence)); + } + + return ToEncodedStringAsyncCore(decorator, options); + } + + private static async Task ToEncodedStringAsyncCore(this IDecorator decorator, AsyncStreamReaderOptions options) + { + var bytes = await Decorator.Enclose(decorator.Inner).ToByteArrayAsync(o => + { + o.BufferSize = options.BufferSize; + o.LeaveOpen = options.LeaveOpen; + o.CancellationToken = options.CancellationToken; + }).ConfigureAwait(false); + return Convertible.ToString(bytes, o => + { + o.Encoding = options.Encoding; + o.Preamble = options.Preamble; + }); + } + + #if NETSTANDARD2_1 || NET5_0 /// /// Compress the enclosed of the specified using the Brotli algorithm. /// @@ -35,7 +225,7 @@ public static Stream CompressBrotli(this IDecorator decorator, Action of the specified using the Brotli algorithm. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a compressed version of the enclosed of the specified . /// /// cannot be null. @@ -43,7 +233,7 @@ public static Stream CompressBrotli(this IDecorator decorator, Action /// The enclosed of does not support write operations such as compression. /// - public static Task CompressBrotliAsync(this IDecorator decorator, Action setup = null) + public static Task CompressBrotliAsync(this IDecorator decorator, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); return CompressAsync(decorator, Patterns.Configure(setup), (stream, level, leaveOpen) => new BrotliStream(stream, level, leaveOpen)); @@ -74,7 +264,7 @@ public static Stream DecompressBrotli(this IDecorator decorator, Action< /// Decompress the enclosed of the specified using Brotli data format specification. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a decompressed version of the enclosed of the specified . /// /// cannot be null. @@ -85,7 +275,7 @@ public static Stream DecompressBrotli(this IDecorator decorator, Action< /// /// The enclosed of was compressed using an unsupported compression method. /// - public static Task DecompressBrotliAsync(this IDecorator decorator, Action setup = null) + public static Task DecompressBrotliAsync(this IDecorator decorator, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); return DecompressAsync(decorator, Patterns.Configure(setup), (stream, mode, leaveOpen) => new BrotliStream(stream, mode, leaveOpen)); @@ -114,7 +304,7 @@ public static Stream CompressGZip(this IDecorator decorator, Action of the specified using the GZip algorithm. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a compressed version of the enclosed of the specified . /// /// cannot be null. @@ -122,7 +312,7 @@ public static Stream CompressGZip(this IDecorator decorator, Action /// The enclosed of does not support write operations such as compression. /// - public static Task CompressGZipAsync(this IDecorator decorator, Action setup = null) + public static Task CompressGZipAsync(this IDecorator decorator, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); return CompressAsync(decorator, Patterns.Configure(setup), (stream, level, leaveOpen) => new GZipStream(stream, level, leaveOpen)); @@ -153,7 +343,7 @@ public static Stream DecompressGZip(this IDecorator decorator, Action of the specified using GZip data format specification. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a decompressed version of the enclosed of the specified . /// /// cannot be null. @@ -164,7 +354,7 @@ public static Stream DecompressGZip(this IDecorator decorator, Action /// The enclosed of was compressed using an unsupported compression method. /// - public static Task DecompressGZipAsync(this IDecorator decorator, Action setup = null) + public static Task DecompressGZipAsync(this IDecorator decorator, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); return DecompressAsync(decorator, Patterns.Configure(setup), (stream, mode, leaveOpen) => new GZipStream(stream, mode, leaveOpen)); @@ -192,7 +382,7 @@ public static Stream CompressDeflate(this IDecorator decorator, Action of the specified using the Deflate algorithm. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a compressed version of the enclosed of the specified . /// /// cannot be null. @@ -200,7 +390,7 @@ public static Stream CompressDeflate(this IDecorator decorator, Action /// The enclosed of does not support write operations such as compression. /// - public static Task CompressDeflateAsync(this IDecorator decorator, Action setup = null) + public static Task CompressDeflateAsync(this IDecorator decorator, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); return CompressAsync(decorator, Patterns.Configure(setup), (stream, level, leaveOpen) => new DeflateStream(stream, level, leaveOpen)); @@ -231,7 +421,7 @@ public static Stream DecompressDeflate(this IDecorator decorator, Action /// Decompress the enclosed of the specified using Deflate data format specification. /// /// The to extend. - /// The which may be configured. + /// The which may be configured. /// A task that represents the asynchronous operation. The task result contains a decompressed version of the enclosed of the specified . /// /// cannot be null. @@ -242,7 +432,7 @@ public static Stream DecompressDeflate(this IDecorator decorator, Action /// /// The enclosed of was compressed using an unsupported compression method. /// - public static Task DecompressDeflateAsync(this IDecorator decorator, Action setup = null) + public static Task DecompressDeflateAsync(this IDecorator decorator, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); return DecompressAsync(decorator, Patterns.Configure(setup), (stream, mode, leaveOpen) => new DeflateStream(stream, mode, leaveOpen)); @@ -250,21 +440,22 @@ public static Task DecompressDeflateAsync(this IDecorator decora private static Stream Compress(IDecorator decorator, StreamCompressionOptions options, Func decompressor) where T : Stream { - return Disposable.SafeInvoke(() => new MemoryStream(), target => + return Patterns.SafeInvoke(() => new MemoryStream(), target => { using (var compressed = decompressor(target, options.Level, true)) { Decorator.Enclose(decorator.Inner).CopyStream(compressed, options.BufferSize); } + target.Flush(); target.Position = 0; return target; }); } - private static Task CompressAsync(IDecorator decorator, StreamCompressionOptions options, Func decompressor) where T : Stream + private static Task CompressAsync(IDecorator decorator, AsyncStreamCompressionOptions options, Func decompressor) where T : Stream { - return Disposable.SafeInvokeAsync(() => new MemoryStream(), async (target, ct) => + return Patterns.SafeInvokeAsync(() => new MemoryStream(), async (target, ct) => { #if NETSTANDARD2_1 await using (var compressed = decompressor(target, options.Level, true)) @@ -285,21 +476,22 @@ private static Task CompressAsync(IDecorator decorator, Strea private static Stream Decompress(IDecorator decorator, StreamCopyOptions options, Func compressor) where T : Stream { - return Disposable.SafeInvoke(() => new MemoryStream(), target => + return Patterns.SafeInvoke(() => new MemoryStream(), target => { using (var uncompressed = compressor(decorator.Inner, CompressionMode.Decompress, true)) { Decorator.Enclose(uncompressed).CopyStream(target, options.BufferSize); } + target.Flush(); target.Position = 0; return target; }); } - private static Task DecompressAsync(IDecorator decorator, StreamCopyOptions options, Func compressor) where T : Stream + private static Task DecompressAsync(IDecorator decorator, AsyncStreamCopyOptions options, Func compressor) where T : Stream { - return Disposable.SafeInvokeAsync(() => new MemoryStream(), async (target, ct) => + return Patterns.SafeInvokeAsync(() => new MemoryStream(), async (target, ct) => { #if NETSTANDARD2_1 await using (var uncompressed = compressor(decorator.Inner, CompressionMode.Decompress, true)) diff --git a/src/Cuemon.Core/IO/InternalStreamWriter.cs b/src/Cuemon.IO/InternalStreamWriter.cs similarity index 100% rename from src/Cuemon.Core/IO/InternalStreamWriter.cs rename to src/Cuemon.IO/InternalStreamWriter.cs diff --git a/src/Cuemon.IO/Properties/PackageReleaseNotes.txt b/src/Cuemon.IO/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..64d2dcd4f --- /dev/null +++ b/src/Cuemon.IO/Properties/PackageReleaseNotes.txt @@ -0,0 +1,31 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET Standard 2.1, NET 5.0 +  +# Upgrade Steps +- The Cuemon.IO.Compression namespace was removed with this version +- Any former extension methods of the Cuemon.IO namespace was merged into the Cuemon.Extensions.IO namespace +  +# Breaking Changes +- REMOVED CompressionType enum from the Cuemon.IO.Compression namespace +- REMOVED CompressionUtility class from the Cuemon.IO.Compression namespace +- REMOVED CompressionUtilityExtensions class from the Cuemon.IO.Compression namespace +- REMOVED FileInfoConverter class from the Cuemon.IO namespace +- REMOVED StreamConverter class from the Cuemon.IO namespace +- REMOVED StreamConverterExtensions class from the Cuemon.IO namespace +- REPLACED StreamWriterUtility class in the Cuemon.IO namespace with StreamFactory (and reduced overloads to max. 5 generic parameters) +- REMOVED TextReaderConverter class from Cuemon.IO namespace +- REMOVED TextReaderConverterExtensions class from the Cuemon.IO namespace +  +# New Features +- ADDED AsyncDisposableOptions class in the Cuemon.IO namespace that specifies options related to a cancelable IDisposable implementation +- ADDED AsyncStreamCompressionOptions class in the Cuemon.IO namespace that specifies options related to a cancelable Stream compression +- ADDED AsyncStreamCopyOptions class in the Cuemon.IO namespace that specifies options related to a cancelable Stream copy operation +- ADDED AsyncStreamEncodingOptions class in the Cuemon.IO namespace that specifies options related to a cancelable Stream encoding +- ADDED AsyncStreamReaderOptions class in the Cuemon.IO namespace that specifies options related to a cancelable StreamReader operation +- ADDED FileInfoOptions class in the Cuemon.IO namespace that specifies options related to FileInfo +- ADDED StreamCompressionOptions class in the Cuemon.IO namespace that specifies options related to a Stream compression +- ADDED StreamCopyOptions class in the Cuemon.IO namespace that specifies options related to a Stream copy operation +- ADDED StreamEncodingOptions class in the Cuemon.IO namespace that specifies options related to a Stream encoding +- ADDED StreamReaderOptions class in the Cuemon.IO namespace that specifies options related to a StreamReader operation +- ADDED StreamWriterOptions class in the Cuemon.IO namespace that specifies options related to a StreamWriter operation +  \ No newline at end of file diff --git a/src/Cuemon.Core/IO/StreamCompressionOptions.cs b/src/Cuemon.IO/StreamCompressionOptions.cs similarity index 100% rename from src/Cuemon.Core/IO/StreamCompressionOptions.cs rename to src/Cuemon.IO/StreamCompressionOptions.cs diff --git a/src/Cuemon.Core/IO/StreamCopyOptions.cs b/src/Cuemon.IO/StreamCopyOptions.cs similarity index 100% rename from src/Cuemon.Core/IO/StreamCopyOptions.cs rename to src/Cuemon.IO/StreamCopyOptions.cs diff --git a/src/Cuemon.Core/IO/StreamEncodingOptions.cs b/src/Cuemon.IO/StreamEncodingOptions.cs similarity index 95% rename from src/Cuemon.Core/IO/StreamEncodingOptions.cs rename to src/Cuemon.IO/StreamEncodingOptions.cs index c8d1a02d0..1f0da32aa 100644 --- a/src/Cuemon.Core/IO/StreamEncodingOptions.cs +++ b/src/Cuemon.IO/StreamEncodingOptions.cs @@ -7,7 +7,7 @@ namespace Cuemon.IO /// /// Configuration options for . /// - public class StreamEncodingOptions : StreamOptions, IEncodingOptions + public class StreamEncodingOptions : DisposableOptions, IEncodingOptions { /// /// Initializes a new instance of the class. diff --git a/src/Cuemon.Core/IO/StreamFactory.cs b/src/Cuemon.IO/StreamFactory.cs similarity index 99% rename from src/Cuemon.Core/IO/StreamFactory.cs rename to src/Cuemon.IO/StreamFactory.cs index ad588c4eb..37ccfd12d 100644 --- a/src/Cuemon.Core/IO/StreamFactory.cs +++ b/src/Cuemon.IO/StreamFactory.cs @@ -115,7 +115,7 @@ public static Stream Create(Action(ActionFactory factory, Action setup = null) where TTuple : Template { var options = Patterns.Configure(setup); - return Disposable.SafeInvoke(() => new MemoryStream(options.BufferSize), (ms, f) => + return Patterns.SafeInvoke(() => new MemoryStream(options.BufferSize), (ms, f) => { var writer = new InternalStreamWriter(ms, options); { diff --git a/src/Cuemon.Core/IO/StreamReaderOptions.cs b/src/Cuemon.IO/StreamReaderOptions.cs similarity index 100% rename from src/Cuemon.Core/IO/StreamReaderOptions.cs rename to src/Cuemon.IO/StreamReaderOptions.cs diff --git a/src/Cuemon.Core/IO/StreamWriterOptions.cs b/src/Cuemon.IO/StreamWriterOptions.cs similarity index 94% rename from src/Cuemon.Core/IO/StreamWriterOptions.cs rename to src/Cuemon.IO/StreamWriterOptions.cs index 88a13ab41..13e11b183 100644 --- a/src/Cuemon.Core/IO/StreamWriterOptions.cs +++ b/src/Cuemon.IO/StreamWriterOptions.cs @@ -6,9 +6,9 @@ namespace Cuemon.IO { /// - /// Specifies options that is related to operations. This class cannot be inherited. + /// Configuration options for . /// - public sealed class StreamWriterOptions : StreamEncodingOptions + public class StreamWriterOptions : StreamEncodingOptions { /// /// Initializes a new instance of the class. diff --git a/src/Cuemon.Net/Cuemon.Net.csproj b/src/Cuemon.Net/Cuemon.Net.csproj index b6cbc9554..aa32713f2 100644 --- a/src/Cuemon.Net/Cuemon.Net.csproj +++ b/src/Cuemon.Net/Cuemon.Net.csproj @@ -1,30 +1,20 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 140bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Net Cuemon.Net - The Cuemon.Net assembly provides access to abstractions related to the System.Xml namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Net namespace contains types that provides a simple programming interface for HTTP and SMTP protocols. The namespace is an addition to the System.Net namespace. + http-manager http-get http-post http-put http-patch http-delete http-trace mail-distributor smtp-client + \ No newline at end of file diff --git a/src/Cuemon.Net/Http/HttpManager.cs b/src/Cuemon.Net/Http/HttpManager.cs index bad600603..e3bb6105d 100644 --- a/src/Cuemon.Net/Http/HttpManager.cs +++ b/src/Cuemon.Net/Http/HttpManager.cs @@ -263,6 +263,7 @@ public Task HttpPatchAsync(Uri location, MediaTypeHeaderVal /// The task object representing the asynchronous operation. /// /// cannot be null -or- + /// cannot be null -or- /// cannot be null -or- /// cannot be null. /// @@ -283,6 +284,7 @@ public Task HttpAsync(HttpMethod method, Uri location, stri /// The task object representing the asynchronous operation. /// /// cannot be null -or- + /// cannot be null -or- /// cannot be null -or- /// cannot be null. /// @@ -307,10 +309,12 @@ public Task HttpAsync(HttpMethod method, Uri location, Medi /// The which need to be configured. /// The task object representing the asynchronous operation. /// + /// cannot be null -or- /// cannot be null. /// public virtual Task HttpAsync(Uri location, Action setup) { + Validator.ThrowIfNull(location, nameof(location)); Validator.ThrowIfNull(setup, nameof(setup)); var options = Patterns.Configure(setup); options.Request.RequestUri = location; diff --git a/src/Cuemon.Net/Http/HttpManagerOptions.cs b/src/Cuemon.Net/Http/HttpManagerOptions.cs index 7cc702548..6d16fd6fa 100644 --- a/src/Cuemon.Net/Http/HttpManagerOptions.cs +++ b/src/Cuemon.Net/Http/HttpManagerOptions.cs @@ -66,10 +66,10 @@ public HttpManagerOptions() public Dictionary DefaultRequestHeaders { get; } /// - /// Gets the HTTP handler stack to use for sending requests. + /// Gets or sets the HTTP handler stack to use for sending requests. /// /// The HTTP handler stack to use for sending requests. - public Func HandlerFactory { get; private set; } + public Func HandlerFactory { get; set; } /// /// Gets or sets the timespan to wait before the request times out. Default is 2 minutes. diff --git a/src/Cuemon.Net/Http/HttpMethodConverter.cs b/src/Cuemon.Net/Http/HttpMethodConverter.cs index 3659d99e5..e81c63a30 100644 --- a/src/Cuemon.Net/Http/HttpMethodConverter.cs +++ b/src/Cuemon.Net/Http/HttpMethodConverter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Net.Http; using Cuemon.Collections.Generic; using Cuemon.Text; @@ -23,13 +24,17 @@ private static IDictionary InitStringToHttpMethodLookupTabl } /// - /// Converts the specified to its equivalent representation. + /// Converts the specified to its equivalent representation. /// - /// The to be converted. - /// A representation of the specified . - public static HttpMethods ToHttpMethod(HttpMethod source) + /// The to be converted. + /// A representation of the specified . + /// + /// cannot be null. + /// + public static HttpMethods ToHttpMethod(HttpMethod method) { - if (!StringToHttpMethodLookupTable.TryGetValue(source.Method, out var result)) + Validator.ThrowIfNull(method, nameof(method)); + if (!StringToHttpMethodLookupTable.TryGetValue(method.Method, out var result)) { result = HttpMethods.Get; } diff --git a/src/Cuemon.Net/NetDependency.cs b/src/Cuemon.Net/NetDependency.cs index 6c2b9d7aa..2dc54cd2c 100644 --- a/src/Cuemon.Net/NetDependency.cs +++ b/src/Cuemon.Net/NetDependency.cs @@ -341,7 +341,7 @@ public override void Start() case UriScheme.File: case UriScheme.Http: case UriScheme.Https: - var watcher = Disposable.SafeInvoke(() => new NetWatcher(uri, DueTime, Period, CheckResponseData), nw => + var watcher = Patterns.SafeInvoke(() => new NetWatcher(uri, DueTime, Period, CheckResponseData), nw => { nw.Changed += WatcherChanged; return nw; diff --git a/src/Cuemon.Net/NetWatcher.cs b/src/Cuemon.Net/NetWatcher.cs index cf1fe4e6d..9bb581f7b 100644 --- a/src/Cuemon.Net/NetWatcher.cs +++ b/src/Cuemon.Net/NetWatcher.cs @@ -3,7 +3,7 @@ using System.IO; using Cuemon.Net.Http; using Cuemon.Runtime; -using Cuemon.Security.Cryptography; +using Cuemon.Security; using Cuemon.Text; namespace Cuemon.Net @@ -163,7 +163,7 @@ private void HandleSignalingFile(ref DateTime utcLastModified, ref string curren using (var stream = new FileStream(RequestUri.LocalPath, FileMode.Open, FileAccess.Read)) { stream.Position = 0; - currentSignature = HashFactory.CreateCryptoSha256().ComputeHash(stream).ToHexadecimalString(); + currentSignature = HashFactory.CreateFnv256().ComputeHash(stream).ToHexadecimalString(); } } } @@ -180,7 +180,7 @@ private void HandleSignalingHttp(ref DateTime utcLastModified, ref string curren { case HttpMethods.Get: var etag = response.Headers.ETag; - currentSignature = string.IsNullOrEmpty(etag.Tag) ? HashFactory.CreateCryptoSha256().ComputeHash(response.Content.ReadAsByteArrayAsync().Result).ToHexadecimalString() : etag.Tag; + currentSignature = string.IsNullOrEmpty(etag.Tag) ? HashFactory.CreateFnv256().ComputeHash(response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()).ToHexadecimalString() : etag.Tag; break; case HttpMethods.Head: utcLastModified = response.Content.Headers.LastModified?.UtcDateTime ?? DateTime.MaxValue; diff --git a/src/Cuemon.Net/Properties/PackageReleaseNotes.txt b/src/Cuemon.Net/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..49b1131e5 --- /dev/null +++ b/src/Cuemon.Net/Properties/PackageReleaseNotes.txt @@ -0,0 +1,19 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.Net.Mail assembly was removed with this version +- Any types found in the former Cuemon.Net.Mail namespace was merged into this assembly with and equivalent namespace +- Any former extension methods of the Cuemon.Net namespace was merged into the Cuemon.Extensions.Net namespace +  +# Breaking Changes +- REMOVED SetHandlerFactory{T} method on the HttpManagerOptions class (opt-in to allow set directly on HandlerFactory property) +  +# New Features +- ADDED MailDistributor class in the Cuemon.Net.Mail namespace that provides a way for applications to distribute one or more e-mails in batches by using the Simple Mail Transfer Protocol (SMTP) +- ADDED FieldValueSeparator enum in the Cuemon.Net namespace that specifies a range of key-value separators +- ADDED QueryStringCollection class in the Cuemon.Net namespace that provides a collection of string values that is equivalent to a query string of an Uri +  +# Improvements +- ADDED HttpManager constructor overload that takes a client factory delegate which creates and configures an HttpClient instance +- CHANGED HttpManagerOptions default value for DisposeHandler from true to false. This is due to the way Microsoft has designed the HttpClient with an implementation of IDisposable that could result in SocketException errors if not instantiated once and re-used throughout the life of an application, This setting reduces the risk of SocketException errors on existing code \ No newline at end of file diff --git a/src/Cuemon.Resilience/Cuemon.Resilience.csproj b/src/Cuemon.Resilience/Cuemon.Resilience.csproj index 6e1490824..9e6c9730a 100644 --- a/src/Cuemon.Resilience/Cuemon.Resilience.csproj +++ b/src/Cuemon.Resilience/Cuemon.Resilience.csproj @@ -1,26 +1,15 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 0e0bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Resilience Cuemon.Resilience - The Cuemon.Resilience assembly provides extension methods and a lightweight resilience framework to support transient fault handling. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Resilience namespace contains types related to applying transient fault handling to existing code using intuitively named methods taking both Action{..} and Func{..} delegates to provide a lightweight resilience framework. + transient-fault-handling transient-fault-evidence transient-fault-exception transient-operation async-transient-operation latency-exception diff --git a/src/Cuemon.Resilience/GlobalSuppressions.cs b/src/Cuemon.Resilience/GlobalSuppressions.cs index e1574ea48..2e5d5cce5 100644 --- a/src/Cuemon.Resilience/GlobalSuppressions.cs +++ b/src/Cuemon.Resilience/GlobalSuppressions.cs @@ -14,3 +14,15 @@ [assembly: SuppressMessage("Major Code Smell", "S1854:Unused assignments should be removed", Justification = "False-positive.", Scope = "member", Target = "~M:Cuemon.Resilience.AsyncFuncTransientWorker`1.ResilientFuncAsync(System.Func{System.Threading.CancellationToken,System.Threading.Tasks.Task{`0}},System.Threading.CancellationToken)~System.Threading.Tasks.Task{`0}")] [assembly: SuppressMessage("Major Code Smell", "S1854:Unused assignments should be removed", Justification = "False-positive.", Scope = "member", Target = "~M:Cuemon.Resilience.FuncTransientWorker`1.ResilientFunc(System.Func{`0})~`0")] [assembly: SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "MethodBase and Type are not serializable; hence the workaround.", Scope = "type", Target = "~T:Cuemon.Resilience.TransientFaultEvidence")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithActionAsync``4(System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,System.Threading.CancellationToken,System.Action{Cuemon.Resilience.TransientOperationOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithAction``4(System.Action{``0,``1,``2,``3},``0,``1,``2,``3,System.Action{Cuemon.Resilience.TransientOperationOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithAction``5(System.Action{``0,``1,``2,``3,``4},``0,``1,``2,``3,``4,System.Action{Cuemon.Resilience.TransientOperationOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithActionAsync``5(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Resilience.TransientOperationOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithActionAsync``5(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Resilience.TransientOperationOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithFunc``4(System.Func{``0,``1,``2,``3},``0,``1,``2,System.Action{Cuemon.Resilience.TransientOperationOptions})~``3")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithFunc``5(System.Func{``0,``1,``2,``3,``4},``0,``1,``2,``3,System.Action{Cuemon.Resilience.TransientOperationOptions})~``4")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithFunc``6(System.Func{``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{Cuemon.Resilience.TransientOperationOptions})~``5")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithFuncAsync``4(System.Func{``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``0,``1,``2,System.Threading.CancellationToken,System.Action{Cuemon.Resilience.TransientOperationOptions})~System.Threading.Tasks.Task{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithFuncAsync``5(System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Threading.CancellationToken,System.Action{Cuemon.Resilience.TransientOperationOptions})~System.Threading.Tasks.Task{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; generic typed parameters for.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithFuncAsync``6(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Resilience.TransientOperationOptions})~System.Threading.Tasks.Task{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; generic typed parameters.", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperation.WithFuncAsync``6(System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Action{Cuemon.Resilience.TransientOperationOptions})~System.Threading.Tasks.Task{``5}")] diff --git a/src/Cuemon.Resilience/Properties/PackageReleaseNotes.txt b/src/Cuemon.Resilience/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..216f9f3cb --- /dev/null +++ b/src/Cuemon.Resilience/Properties/PackageReleaseNotes.txt @@ -0,0 +1,10 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# New Features +- ADDED LatencyException class in the Cuemon.Resilience namespace that represents the exception that is thrown when a latency related operation was taking to long to complete +- ADDED TransientFaultEvidence class in the Cuemon.Resilience namespace that provides evidence about a faulted TransientOperation +- ADDED TransientFaultException class in the Cuemon.Resilience namespace that represents the exception that is thrown when a transient fault handling was unsuccessful +- ADDED TransientOperation class in the Cuemon.Resilience namespace that provides a set of static methods that enable developers to make their applications more resilient by adding robust transient fault handling logic ideal for temporary condition such as network connectivity issues or service unavailability +- ADDED TransientOperationOptions class in the Cuemon.Resilience namespace that specifies options related to TransientOperation +  \ No newline at end of file diff --git a/src/Cuemon.Resilience/TransientOperation.Async.cs b/src/Cuemon.Resilience/TransientOperation.Async.cs index f1349566d..eb5856ac6 100644 --- a/src/Cuemon.Resilience/TransientOperation.Async.cs +++ b/src/Cuemon.Resilience/TransientOperation.Async.cs @@ -10,7 +10,7 @@ public static partial class TransientOperation /// Repetitively executes the specified until the operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. /// /// The type of the return value of the function delegate . - /// The fault sensitive function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. + /// The fault sensitive based function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. /// The token to monitor for cancellation requests. The default value is . /// The which may be configured. /// The result from the . @@ -40,7 +40,7 @@ public static Task WithFuncAsync(Func /// The type of the parameter of the function delegate . /// The type of the return value of the function delegate . - /// The fault sensitive function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. + /// The fault sensitive based function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. /// The parameter of the function delegate . /// The token to monitor for cancellation requests. The default value is . /// The which may be configured. @@ -72,7 +72,7 @@ public static Task WithFuncAsync(FuncThe type of the first parameter of the function delegate . /// The type of the second parameter of the function delegate . /// The type of the return value of the function delegate . - /// The fault sensitive function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. + /// The fault sensitive based function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The token to monitor for cancellation requests. The default value is . @@ -106,7 +106,7 @@ public static Task WithFuncAsync(FuncThe type of the second parameter of the function delegate . /// The type of the third parameter of the function delegate . /// The type of the return value of the function delegate . - /// The fault sensitive function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. + /// The fault sensitive based function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . @@ -142,7 +142,7 @@ public static Task WithFuncAsync(FuncThe type of the third parameter of the function delegate . /// The type of the fourth parameter of the function delegate . /// The type of the return value of the function delegate . - /// The fault sensitive function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. + /// The fault sensitive based function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . @@ -180,7 +180,7 @@ public static Task WithFuncAsync(FuncThe type of the fourth parameter of the function delegate . /// The type of the fifth parameter of the function delegate . /// The type of the return value of the function delegate . - /// The fault sensitive function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. + /// The fault sensitive based function delegate that is invoked until an operation is successful, the amount of retry attempts has been reached, or a failed operation is not considered related to transient fault condition. /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . diff --git a/src/Cuemon.Resilience/TransientOperationOptions.cs b/src/Cuemon.Resilience/TransientOperationOptions.cs index 661b2a9ed..164a7994d 100644 --- a/src/Cuemon.Resilience/TransientOperationOptions.cs +++ b/src/Cuemon.Resilience/TransientOperationOptions.cs @@ -3,9 +3,8 @@ namespace Cuemon.Resilience { /// - /// Specifies options that is related to handling of . + /// Configuration options for . /// - /// . public class TransientOperationOptions { /// diff --git a/src/Cuemon.Runtime.Caching/Cache.cs b/src/Cuemon.Runtime.Caching/Cache.cs deleted file mode 100644 index d6a5d3503..000000000 --- a/src/Cuemon.Runtime.Caching/Cache.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Cuemon.Runtime.Caching -{ - /// - /// An internal representation of a Cache object. - /// - internal class Cache - { - #region Constructors - /// - /// Initializes a new instance of the class. - /// - /// The identifier of this . - /// The cached value of this . - /// The group to associate and organize this by. - /// A sequence of objects for the item. When any dependency changes, the object becomes invalid and is removed from the cache. If there are no dependencies, this parameter contains a null reference (Nothing in Visual Basic). - /// The absolute expiration date time value of this . - /// The sliding expiration value of this . - internal Cache(string key, object value, string group, IEnumerable dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration) - { - Key = key; - Value = value; - Group = group; - Dependencies = dependencies == null ? null : new List(dependencies); - AbsoluteExpiration = absoluteExpiration.ToUniversalTime(); - SlidingExpiration = slidingExpiration; - Created = DateTime.UtcNow; - LastAccessed = Created; - } - #endregion - - #region Events - /// - /// Occurs when a object with a has expired. - /// - public event EventHandler Expired; - #endregion - - #region Properties - /// - /// Gets the identifier of this . - /// - /// The identifier of this . - public string Key { get; private set; } - - /// - /// Gets the value of this . - /// - /// The value of this . - public object Value { get; set; } - - /// - /// Gets the group to associate and organize this by. - /// - /// The group to associate and organize this by. - public string Group { get; private set; } - - /// - /// Gets a sequence of objects implementing the interface assigned to this . - /// - /// A sequence of objects implementing the interface assigned to this . - public IEnumerable Dependencies { get; private set; } - - /// - /// Gets the UTC absolute expiration date time value of this . - /// - /// The UTC absolute expiration date time value of this . - public DateTime AbsoluteExpiration { get; private set; } - - /// - /// Gets the UTC date time value from when this was created. - /// - /// The UTC date time value from when this was created. - public DateTime Created { get; private set; } - - /// - /// Gets the UTC date time value from when this was last accessed. - /// - /// The UTC date time value from when this was last accessed. - public DateTime LastAccessed { get; private set; } - - /// - /// Gets the sliding expiration value of this . - /// - /// The sliding expiration value of this . - public TimeSpan SlidingExpiration { get; private set; } - - /// - /// Gets a value indicating whether this should use the AbsoluteExpiration property for the caching logic. - /// - /// - /// true if this should use the AbsoluteExpiration property for the caching logic; otherwise, false. - /// - public bool UseAbsoluteExpiration - { - get { return (DateTime.MaxValue.ToUniversalTime() != AbsoluteExpiration); } - } - - /// - /// Gets a value indicating whether this should use the SlidingExpiration property for the caching logic. - /// - /// - /// true if this should use the SlidingExpiration property for the caching logic; otherwise, false. - /// - public bool UseSlidingExpiration - { - get { return (TimeSpan.Zero != SlidingExpiration); } - } - - /// - /// Gets a value indicating whether this is relying on a object. - /// - /// true if this is relying on a object; otherwise, false. - public bool UseDependency - { - get { return (Dependencies != null && Dependencies.Any()); } - } - - /// - /// Determines whether the specified time resolves this as expired. - /// - /// The date and time to evaluate against. - /// - /// true if the specified time resolves this as expired; otherwise, false. - /// - public bool HasExpired(DateTime time) - { - if (!CanExpire) { return false; } - if (UseAbsoluteExpiration) - { - if (time >= AbsoluteExpiration) { return true; } - } - else if (UseSlidingExpiration) - { - TimeSpan currentPeriod = (time - LastAccessed); - if (currentPeriod >= SlidingExpiration) { return true; } - } - else if (UseDependency) - { - foreach (IDependency dependency in Dependencies) - { - if (dependency.HasChanged) { return true; } - } - } - return false; - } - - /// - /// Gets a value indicating whether this can expire. - /// - /// - /// true if this can expire; otherwise, false. - /// - public bool CanExpire - { - get { return UseAbsoluteExpiration || UseSlidingExpiration || (UseDependency); } - } - #endregion - - #region Methods - - internal void StartDependencies() - { - if (!UseDependency) { return; } - foreach (IDependency dependency in Dependencies) - { - dependency.DependencyChanged += ProcessDependencyChanged; - dependency.Start(); - } - } - - /// - /// Refreshes the UTC date time value from when this was created. - /// - public void Refresh() - { - LastAccessed = DateTime.UtcNow; - } - - private void ProcessDependencyChanged(object sender, DependencyEventArgs e) - { - OnExpiredRaised(new CacheEventArgs(this)); - if (UseDependency) - { - foreach (IDependency dependency in Dependencies) - { - dependency.DependencyChanged -= ProcessDependencyChanged; - } - } - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnExpiredRaised(CacheEventArgs e) - { - Expired?.Invoke(this, e); - } - - /// - /// Returns a that represents this instance. - /// - /// A that represents this instance. - public override string ToString() - { - return Generate.ObjectPortrayal(this, o => o.BypassOverrideCheck = true); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/CacheCollection.GetOrAdd.cs b/src/Cuemon.Runtime.Caching/CacheCollection.GetOrAdd.cs deleted file mode 100644 index ef00fc008..000000000 --- a/src/Cuemon.Runtime.Caching/CacheCollection.GetOrAdd.cs +++ /dev/null @@ -1,2196 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Cuemon.Runtime.Caching -{ - public sealed partial class CacheCollection - { - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver) - { - return GetOrAdd(key, NoGroup, resolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver); - var f2 = FuncFactory.Create(dependencyResolver); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T arg) - { - return GetOrAdd(key, NoGroup, resolver, arg); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T arg) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T arg, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T arg, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T arg, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T arg, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T arg, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T arg, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg); - var f2 = FuncFactory.Create(dependencyResolver, arg); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the seventh parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The seventh parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the seventh parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The seventh parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the seventh parameter of the function delegate and the function delegate . - /// The type of the eighth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The seventh parameter of the function delegate and the function delegate . - /// The eighth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the seventh parameter of the function delegate and the function delegate . - /// The type of the eighth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The seventh parameter of the function delegate and the function delegate . - /// The eighth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the seventh parameter of the function delegate and the function delegate . - /// The type of the eighth parameter of the function delegate and the function delegate . - /// The type of the ninth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The seventh parameter of the function delegate and the function delegate . - /// The eighth parameter of the function delegate and the function delegate . - /// The ninth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the seventh parameter of the function delegate and the function delegate . - /// The type of the eighth parameter of the function delegate and the function delegate . - /// The type of the ninth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The seventh parameter of the function delegate and the function delegate . - /// The eighth parameter of the function delegate and the function delegate . - /// The ninth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the tenth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The tenth parameter of the function delegate . - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the tenth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The tenth parameter of the function delegate . - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - return (TResult)GetOrAddCore(factory, key, group).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the tenth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The tenth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, DateTime absoluteExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, absoluteExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the tenth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The tenth parameter of the function delegate . - /// The time at which the return value of expires and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, DateTime absoluteExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - return (TResult)GetOrAddCore(factory, key, group, () => absoluteExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the tenth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The tenth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, TimeSpan slidingExpiration) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, slidingExpiration); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the tenth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The tenth parameter of the function delegate . - /// The interval between the time the return value of was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, TimeSpan slidingExpiration) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - var factory = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - return (TResult)GetOrAddCore(factory, key, group, null, () => slidingExpiration).Value; - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the cache. - /// - /// The type of the first parameter of the function delegate . - /// The type of the second parameter of the function delegate . - /// The type of the third parameter of the function delegate . - /// The type of the fourth parameter of the function delegate . - /// The type of the fifth parameter of the function delegate . - /// The type of the sixth parameter of the function delegate . - /// The type of the seventh parameter of the function delegate . - /// The type of the eighth parameter of the function delegate . - /// The type of the ninth parameter of the function delegate . - /// The type of the tenth parameter of the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate . - /// The second parameter of the function delegate . - /// The third parameter of the function delegate . - /// The fourth parameter of the function delegate . - /// The fifth parameter of the function delegate . - /// The sixth parameter of the function delegate . - /// The seventh parameter of the function delegate . - /// The eighth parameter of the function delegate . - /// The ninth parameter of the function delegate . - /// The tenth parameter of the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified . This will either be the existing value if the is already in the cache, or the new value for the as returned by if the was not in the cache. - public TResult GetOrAdd(string key, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, Func> dependencyResolver) - { - return GetOrAdd(key, NoGroup, resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, dependencyResolver); - } - - /// - /// Adds a value to the cache by using the specified function delegate , if the does not already exist in the virtual of the cache. - /// - /// The type of the first parameter of the function delegate and the function delegate . - /// The type of the second parameter of the function delegate and the function delegate . - /// The type of the third parameter of the function delegate and the function delegate . - /// The type of the fourth parameter of the function delegate and the function delegate . - /// The type of the fifth parameter of the function delegate and the function delegate . - /// The type of the sixth parameter of the function delegate and the function delegate . - /// The type of the seventh parameter of the function delegate and the function delegate . - /// The type of the eighth parameter of the function delegate and the function delegate . - /// The type of the ninth parameter of the function delegate and the function delegate . - /// The type of the tenth parameter of the function delegate and the function delegate . - /// The type of the value in the cache. - /// The cache key used to identify the item. - /// The virtual group to associate the with. - /// The function delegate that is used to resolve a value for the . - /// The first parameter of the function delegate and the function delegate . - /// The second parameter of the function delegate and the function delegate . - /// The third parameter of the function delegate and the function delegate . - /// The fourth parameter of the function delegate and the function delegate . - /// The fifth parameter of the function delegate and the function delegate . - /// The sixth parameter of the function delegate and the function delegate . - /// The seventh parameter of the function delegate and the function delegate . - /// The eighth parameter of the function delegate and the function delegate . - /// The ninth parameter of the function delegate and the function delegate . - /// The tenth parameter of the function delegate and the function delegate . - /// The function delegate that is used to assign dependencies to the result of to the cache. When any dependency changes, the object becomes invalid and is removed from the cache. - /// The value for the specified and . This will either be the existing value if the is already in the virtual of the cache, or the new value for the as returned by if the was not in virtual of the cache. - public TResult GetOrAdd(string key, string group, Func resolver, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, Func> dependencyResolver) - { - Validator.ThrowIfNull(resolver, nameof(resolver)); - Validator.ThrowIfNull(dependencyResolver, nameof(dependencyResolver)); - var f1 = FuncFactory.Create(resolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - var f2 = FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - return (TResult)GetOrAddCore(f1, key, group, null, null, f2).Value; - } - - private Cache GetOrAddCore(FuncFactory valueFactory, string key, string group, Func absoluteExpiration = null, Func slidingExpiration = null, FuncFactory> dependenciesFactory = null) - where TTuple : Template - { - Validator.ThrowIfNull(key, nameof(key)); - if (slidingExpiration != null) - { - Validator.ThrowIfLowerThan(slidingExpiration().Ticks, TimeSpan.Zero.Ticks, nameof(slidingExpiration), "The specified sliding expiration cannot be less than TimeSpan.Zero."); - Validator.ThrowIfGreaterThan(slidingExpiration().Ticks, TimeSpan.FromDays(365).Ticks, nameof(slidingExpiration), "The specified sliding expiration cannot exceed one year."); - } - - long groupKey = GenerateGroupKey(key, group); - if (!TryGetCache(key, group, out var result)) - { - result = WrapCacheInThreadSafeDelegate(valueFactory, key, group, absoluteExpiration, slidingExpiration, dependenciesFactory).Value; - _innerCaches.TryAdd(groupKey, result); - } - return _innerCaches.GetOrAdd(groupKey, gk => WrapCacheInThreadSafeDelegate(valueFactory, key, group, absoluteExpiration, slidingExpiration, dependenciesFactory).Value); - } - - private Lazy WrapCacheInThreadSafeDelegate(FuncFactory valueFactory, string key, string group, Func absoluteExpiration = null, Func slidingExpiration = null, FuncFactory> dependenciesFactory = null) - where TTuple : Template - { - return new Lazy(() => - { - var cache = new Cache(key, valueFactory.ExecuteMethod(), group, dependenciesFactory?.ExecuteMethod(), absoluteExpiration?.Invoke() ?? DateTime.MaxValue, slidingExpiration?.Invoke() ?? TimeSpan.Zero); - cache.Expired += CacheExpired; - cache.StartDependencies(); - return cache; - }, LazyThreadSafetyMode.ExecutionAndPublication); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/CacheCollection.Memoization.cs b/src/Cuemon.Runtime.Caching/CacheCollection.Memoization.cs deleted file mode 100644 index 470c6e5f3..000000000 --- a/src/Cuemon.Runtime.Caching/CacheCollection.Memoization.cs +++ /dev/null @@ -1,929 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using Cuemon.Reflection; - -namespace Cuemon.Runtime.Caching -{ - public sealed partial class CacheCollection - { - private const string MemoizationGroup = "Memoization"; - private const long NullHashCode = 854726591; - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate .> - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The function delegate that is used to assign dependencies to the memoized . When any dependency changes, the object becomes invalid and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the tenth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the tenth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The time at which the memoized function delegate expires and is removed from the cache. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, DateTime absoluteExpiration) - { - return MemoizeCore(method, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the tenth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// The interval between the time the memoized function delegate was last accessed and the time at which that memoization expires. If this value is the equivalent of 20 minutes, the memoization expires and is removed from the cache 20 minutes after it was last accessed. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, TimeSpan slidingExpiration) - { - return MemoizeCore(method, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Memoizes the specified in the cache for fast access. - /// - /// The type of the first parameter of the delegate . - /// The type of the second parameter of the delegate . - /// The type of the third parameter of the delegate . - /// The type of the fourth parameter of the delegate . - /// The type of the fifth parameter of the delegate . - /// The type of the sixth parameter of the delegate . - /// The type of the seventh parameter of the delegate . - /// The type of the eighth parameter of the delegate . - /// The type of the ninth parameter of the delegate . - /// The type of the tenth parameter of the delegate . - /// The type of the return value of the function delegate . - /// The function delegate that is invoked once and then stored in cache for fast access. - /// Establishes one or more relations to this memoized function delegate. - /// A memoized function delegate that is otherwise equivalent to . - public Func Memoize(Func method, Func> dependencyResolver) - { - return MemoizeCore(method, DateTime.MaxValue, TimeSpan.Zero, dependencyResolver); - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate - { - string key = CalculateCompositeKey(method); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T arg) - { - string key = CalculateCompositeKey(method, arg); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2) - { - string key = CalculateCompositeKey(method, arg1, arg2); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3, arg4); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3, arg4); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3, arg4, arg5); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3, arg4, arg5); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3, arg4, arg5, arg6); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3, arg4, arg5, arg6); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private Func MemoizeCore(Func method, DateTime absoluteExpiration, TimeSpan slidingExpiration, Func> dependencyResolver) - { - return delegate (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) - { - string key = CalculateCompositeKey(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - if (!TryGetValue(key, MemoizationGroup, out TResult result)) - { - var f1 = FuncFactory.Create(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - var f2 = dependencyResolver == null ? null : FuncFactory.Create(dependencyResolver, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); - result = (TResult)GetOrAddCore(f1, key, MemoizationGroup, () => absoluteExpiration, () => slidingExpiration, f2).Value; - } - return result; - }; - } - - private static string CalculateCompositeKey(Delegate del, params object[] args) - { - int result = del == null || del.GetMethodInfo() == null ? NullHashCode.GetHashCode() : MethodDescriptor.Create(del.GetMethodInfo()).ToString().GetHashCode(); - for (int i = 0; i < args.Length; i++) - { - object current = args[i] ?? NullHashCode; - byte[] bytes = current as byte[]; - result ^= bytes == null ? current.GetHashCode() : Generate.HashCode32(bytes.Cast()); - } - return result.ToString(CultureInfo.InvariantCulture); - } - } -} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/CacheCollection.cs b/src/Cuemon.Runtime.Caching/CacheCollection.cs deleted file mode 100644 index 4c3870acd..000000000 --- a/src/Cuemon.Runtime.Caching/CacheCollection.cs +++ /dev/null @@ -1,599 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using Cuemon.Collections.Generic; - -namespace Cuemon.Runtime.Caching -{ - /// - /// Implements a cache for an application. This class cannot be inherited. - /// - public sealed partial class CacheCollection : IEnumerable> - { - private static readonly CacheCollection Singleton = new CacheCollection(); - private readonly ConcurrentDictionary _innerCaches = new ConcurrentDictionary(); - private const string NoGroup = null; - - internal static CacheCollection Cache - { - get { return Singleton; } - } - - #region Constructors - private CacheCollection() - { - EnableExpirationTimer = true; - ExpirationTimer = new Timer(ExpirationTimerInvoking, null, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(20)); - } - #endregion - - #region Properties - /// - /// Gets or sets a value indicating whether a timer regularly should clean up expired cache items. - /// - /// true if a timer regularly should clean up expired cache items; otherwise, false. - public bool EnableExpirationTimer { get; set; } - - /// - /// Gets the cached item with the specified . - /// - /// The cached item matching the specified . - public object this[string key] - { - get - { - return this[key, NoGroup]; - } - } - - /// - /// Gets the cached item with the specified and . - /// - /// The cached item matching the specified and . - public object this[string key, string group] - { - get - { - return Get(key, group); - } - } - - private Timer ExpirationTimer { get; } - #endregion - - #region Methods - /// - /// Retrieves the specified item from the . - /// - /// The type of the item in the . - /// The identifier of the cache item to retrieve. - /// The retrieved cache item, or the default value of the type parameter T if the key is not found. - public T Get(string key) - { - return Get(key, NoGroup); - } - - /// - /// Retrieves the specified item from the associated group of the . - /// - /// The type of the item in the . - /// The identifier of the cache item to retrieve. - /// The associated group of the cache item to retrieve. - /// The retrieved cache item, or the default value of the type parameter T if the key is not found. - public T Get(string key, string group) - { - if (key == null) { throw new ArgumentNullException(nameof(key)); } - TryGetValue(key, group, out T result); - return result; - } - - /// - /// Updates the specified item from the associated group of the . - /// - /// The type of the item in the . - /// The identifier of the cache item to retrieve and update with . - /// The associated group of the cache item to retrieve and update with . - /// The value to apply to the cached item. - internal void Set(string key, string group, T value) - { - if (key == null) { throw new ArgumentNullException(nameof(key)); } - if (TryGetCache(key, group, out var cache)) - { - cache.Value = value; - cache.Refresh(); - } - } - - /// - /// Gets the UTC date time value from when this item was added to the . - /// - /// The key of the value to get. - /// When this method returns, contains the UTC date time value from when this item, with the specified key, was added; otherwise, if no item could be resolved or the item has expired, . This parameter is passed uninitialized. - /// - /// true if the parameter contains an element with the specified key, and the element has not expired; otherwise, false. - /// - /// - /// is null. - /// - public bool TryGetAdded(string key, out DateTime value) - { - return TryGetAdded(key, NoGroup, out value); - } - - /// - /// Gets the UTC date time value from when this item was added to the . - /// - /// The key of the value to get. - /// The group of the value to get. - /// When this method returns, contains the UTC date time value from when this item, with the specified key, was added; otherwise, if no item could be resolved or the item has expired, . This parameter is passed uninitialized. - /// - /// true if the parameter contains an element with the specified key, and the element has not expired; otherwise, false. - /// - /// - /// is null. - /// - public bool TryGetAdded(string key, string group, out DateTime value) - { - if (key == null) { throw new ArgumentNullException(nameof(key)); } - if (TryGetCache(key, group, out var cache)) - { - value = cache.Created; - return true; - } - value = DateTime.MinValue; - return false; - } - - private static long GenerateGroupKey(string key, string group) - { - return Generate.HashCode64(group == NoGroup ? key : string.Concat(key, group)); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value) - { - Add(key, value, NoGroup); - } - - /// - /// Adds the specified and to the cache.up. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The group to associate the with. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, string group) - { - AddCore(key, value, group, DateTime.MaxValue, TimeSpan.Zero, null); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The time at which the added object expires and is removed from the cache. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, DateTime absoluteExpiration) - { - Add(key, value, NoGroup, absoluteExpiration); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The group to associate the with. - /// The time at which the added object expires and is removed from the cache. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, string group, DateTime absoluteExpiration) - { - AddCore(key, value, group, absoluteExpiration, TimeSpan.Zero, null); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The interval between the time the added object was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it is last accessed. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, TimeSpan slidingExpiration) - { - Add(key, value, NoGroup, slidingExpiration); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The group to associate the with. - /// The interval between the time the added object was last accessed and the time at which that object expires. If this value is the equivalent of 20 minutes, the object expires and is removed from the cache 20 minutes after it was last accessed. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, string group, TimeSpan slidingExpiration) - { - AddCore(key, value, group, DateTime.MaxValue, slidingExpiration, null); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The dependencies for the . When any dependency changes, the becomes invalid and is removed from the cache. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, params IDependency[] dependencies) - { - Add(key, value, NoGroup, dependencies); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The dependencies for the . When any dependency changes, the becomes invalid and is removed from the cache. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, IEnumerable dependencies) - { - Add(key, value, NoGroup, dependencies); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The group to associate the with. - /// The dependencies for the . When any dependency changes, the becomes invalid and is removed from the cache. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, string group, params IDependency[] dependencies) - { - Add(key, value, group, Arguments.ToEnumerableOf(dependencies)); - } - - /// - /// Adds the specified and to the cache. - /// - /// The cache key used to identify the item. - /// The object to be inserted in the cache. - /// The group to associate the with. - /// The dependencies for the . When any dependency changes, the becomes invalid and is removed from the cache. - /// - /// is null. - /// - /// - /// This method will not throw an in case of an existing cache item whose key matches the key parameter. - /// - public void Add(string key, object value, string group, IEnumerable dependencies) - { - AddCore(key, value, group, DateTime.MaxValue, TimeSpan.Zero, dependencies); - } - - private void AddCore(string key, object value, string group, DateTime absoluteExpiration, TimeSpan slidingExpiration, IEnumerable dependencies) - { - var f1 = FuncFactory.Create(() => value); - var f2 = FuncFactory.Create(() => dependencies); - GetOrAddCore(f1, key, group, () => absoluteExpiration, () => slidingExpiration, f2); - } - - private void ExpirationTimerInvoking(object o) - { - HandleExpiration(); - } - - private void RemoveExpired(string key, string group) - { - Remove(key, group); - } - - private void HandleExpiration() - { - if (!EnableExpirationTimer) { return; } - DateTime current = DateTime.UtcNow; - List snapshot = new List(_innerCaches.Values); - if (snapshot.Count > 0) - { - foreach (Cache cache in snapshot) - { - if (cache == null) { continue; } - if (cache.CanExpire && cache.HasExpired(current)) { RemoveExpired(cache.Key, cache.Group); } - } - } - } - - private void CacheExpired(object sender, CacheEventArgs e) - { - e.Cache.Expired -= CacheExpired; - } - - private IList GetCaches(string group) - { - var current = DateTime.UtcNow; - var groupCaches = new List(); - var snapshot = new List(_innerCaches.Values); - foreach (Cache cache in snapshot) - { - if (cache == null) { continue; } // this can happen if a cache has been removed - if (cache.CanExpire && cache.HasExpired(current)) { continue; } - if (group == NoGroup) - { - groupCaches.Add(cache); // return all - } - else if (cache.Group == group) - { - groupCaches.Add(cache); // return filtered by group - } - } - return groupCaches; - } - - /// - /// Determines whether the contains the specified key. - /// - /// The key to locate in the . - /// - /// true if the contains an element with the specified key; otherwise, false. - /// - /// - /// is null. - /// - public bool ContainsKey(string key) - { - return ContainsKey(key, NoGroup); - } - - /// - /// Determines whether the contains the specified key. - /// - /// The key to locate in the . - /// The associated group of the key to locate. - /// - /// true if the contains an element with the specified key; otherwise, false. - /// - /// - /// is null. - /// - public bool ContainsKey(string key, string group) - { - if (key == null) { throw new ArgumentNullException(nameof(key)); } - return TryGetCache(key, group, out _); - } - - /// - /// Removes all keys and values from the . - /// - public void Clear() - { - Clear(NoGroup); - } - - /// - /// Removes all keys and values matching the specified group from the . - /// - public void Clear(string group) - { - if (group == NoGroup) - { - _innerCaches.Clear(); - } - else - { - IList groupCaches = GetCaches(group); - foreach (var cache in groupCaches) - { - Remove(cache.Key, cache.Group); - } - } - } - - /// - /// Gets the number of elements contained in the . - /// - /// - /// - /// The number of elements contained in the . - /// - public int Count() - { - return Count(NoGroup); - } - - /// - /// Gets the number of elements contained in the specified group of the . - /// - /// The associated group to filter the count by. - /// - /// The number of elements contained in the . - /// - /// - public int Count(string group) - { - int count = GetCaches(group).Count; - return count; - } - - /// - /// Gets the value associated with the specified key. - /// - /// The type of the item in the . - /// The key of the value to get. - /// When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. - /// - /// true if the contains an element with the specified key; otherwise, false. - /// - /// - /// is null. - /// - public bool TryGetValue(string key, out T value) - { - return TryGetValue(key, NoGroup, out value); - } - - /// - /// Gets the value associated with the specified key and group. - /// - /// The type of the item in the . - /// The key of the value to get. - /// The group of the value to get. - /// When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. - /// - /// true if the contains an element with the specified key; otherwise, false. - /// - /// - /// is null. - /// - public bool TryGetValue(string key, string group, out T value) - { - if (key == null) { throw new ArgumentNullException(nameof(key)); } - - if (TryGetCache(key, group, out var cache)) - { - value = (T)cache.Value; - return true; - } - value = default; - return false; - } - - private bool TryGetCache(string key, string group, out Cache cache) - { - DateTime current = DateTime.UtcNow; - long groupKey = GenerateGroupKey(key, group); - if (_innerCaches.TryGetValue(groupKey, out cache)) - { - bool hasCacheExpired = cache.HasExpired(current); - if (cache.CanExpire && hasCacheExpired) - { - RemoveExpired(key, group); - return false; - } - if (cache.CanExpire && !hasCacheExpired) { cache.Refresh(); } - return true; - } - return false; - } - - /// - /// Removes the value with the specified key from the . - /// - /// The key of the element to remove. - /// - /// true if the element is successfully found and removed; otherwise, false. This method returns false if is not found in the . - /// - /// - /// is null. - /// - public bool Remove(string key) - { - return Remove(key, NoGroup); - } - - /// - /// Removes the value with the specified key from the associated specified group of the . - /// - /// The key of the element to remove. - /// The associated group to the key of the element to remove. - /// - /// true if the element is successfully found and removed; otherwise, false. This method returns false if combined with is not found in the . - /// - /// - /// is null. - /// - public bool Remove(string key, string group) - { - if (key == null) { throw new ArgumentNullException(nameof(key)); } - long groupKey = GenerateGroupKey(key, group); - return _innerCaches.TryRemove(groupKey, out _); - } - - private IEnumerable> CreateImpostor() - { - foreach (var keyValuePair in _innerCaches) - { - if (keyValuePair.Value != null) - { - yield return new KeyValuePair(keyValuePair.Key, keyValuePair.Value.Value); - } - } - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - - /// - /// Retrieves an enumerator that iterates through the key settings and their values contained in the cache. - /// - /// A that can be used to iterate through the collection. - /// All keys are hashed internally and will not provide useful information. - public IEnumerator> GetEnumerator() - { - List> impostor = new List>(CreateImpostor()); - return impostor.GetEnumerator(); - } - #endregion - } -} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/CacheEntry.cs b/src/Cuemon.Runtime.Caching/CacheEntry.cs new file mode 100644 index 000000000..53699da16 --- /dev/null +++ b/src/Cuemon.Runtime.Caching/CacheEntry.cs @@ -0,0 +1,168 @@ +using System; + +namespace Cuemon.Runtime.Caching +{ + /// + /// Represents an individual cache entry in the cache. + /// + public class CacheEntry + { + /// + /// Represents a cache with a global scope, eg. no namespace. + /// + public const string NoScope = null; + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the cache. + /// The stored value of the cache. + /// The optional namespace that provides a scope to the cache. + /// + /// cannot be null. + /// + public CacheEntry(string key, object value, string ns = NoScope) + { + Validator.ThrowIfNull(key, nameof(key)); + var timestamp = DateTime.UtcNow; + Key = key; + Value = value; + Namespace = ns; + Inserted = timestamp; + Accessed = timestamp; + } + + /// + /// Occurs when a object with an associated has expired. + /// + public event EventHandler Expired; + + /// + /// Gets the unique identifier of this . + /// + /// The unique identifier of this . + public string Key { get; } + + /// + /// Gets the stored value of this . + /// + /// The stored value of this . + public object Value { get; set; } + + /// + /// Gets the optional namespace that provides a scope to this . + /// + /// The optional namespace that provides a scope to this . + public string Namespace { get; } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return Generate.ObjectPortrayal(this, o => o.BypassOverrideCheck = true); + } + + /// + /// Gets the cache invalidation of this . + /// + /// The cache invalidation of this . + public CacheInvalidation Invalidation { get; private set; } + + /// + /// Gets the UTC date time value from when this was inserted. + /// + /// The UTC date time value from when this was inserted. + public DateTime Inserted { get; } + + /// + /// Gets the UTC date time value from when this was last accessed. + /// + /// The UTC date time value from when this was last accessed. + public DateTime Accessed { get; private set; } + + /// + /// Gets a value indicating whether this can expire. + /// + /// + /// true if this can expire; otherwise, false. + /// + public bool CanExpire => Invalidation.UseAbsoluteExpiration || Invalidation.UseSlidingExpiration || Invalidation.UseDependency; + + internal CacheEntry SetInvalidation(CacheInvalidation invalidation) + { + Validator.ThrowIfNull(invalidation, nameof(invalidation)); + Invalidation = invalidation; + return this; + } + + /// + /// Determines whether the specified time resolves this as expired. + /// + /// The date and time to evaluate against. + /// + /// true if the specified time resolves this as expired; otherwise, false. + /// + public bool HasExpired(DateTime time) + { + if (!CanExpire) { return false; } + if (Invalidation.UseAbsoluteExpiration) + { + if (time >= Invalidation.AbsoluteExpiration) { return true; } + } + else if (Invalidation.UseSlidingExpiration) + { + var currentPeriod = (time - Accessed); + if (currentPeriod >= Invalidation.SlidingExpiration) { return true; } + } + else if (Invalidation.UseDependency) + { + foreach (var dependency in Invalidation.Dependencies) + { + if (dependency.HasChanged) { return true; } + } + } + return false; + } + + internal void Refresh() + { + Accessed = DateTime.UtcNow; + } + + internal CacheEntry StartDependencies() + { + if (Invalidation.UseDependency) + { + foreach (var dependency in Invalidation.Dependencies) + { + dependency.DependencyChanged += ProcessDependencyChanged; + dependency.Start(); + } + } + return this; + } + + private void ProcessDependencyChanged(object sender, DependencyEventArgs e) + { + if (Invalidation.UseDependency) + { + OnExpiredRaised(new CacheEntryEventArgs(this)); + foreach (var dependency in Invalidation.Dependencies) + { + dependency.DependencyChanged -= ProcessDependencyChanged; + } + } + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnExpiredRaised(CacheEntryEventArgs e) + { + Expired?.Invoke(this, e); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/CacheEventArgs.cs b/src/Cuemon.Runtime.Caching/CacheEntryEventArgs.cs similarity index 60% rename from src/Cuemon.Runtime.Caching/CacheEventArgs.cs rename to src/Cuemon.Runtime.Caching/CacheEntryEventArgs.cs index 0d45416c7..8eb0081c7 100644 --- a/src/Cuemon.Runtime.Caching/CacheEventArgs.cs +++ b/src/Cuemon.Runtime.Caching/CacheEntryEventArgs.cs @@ -5,13 +5,13 @@ namespace Cuemon.Runtime.Caching /// /// Provides data for cache related operations. This class cannot be inherited. /// - public sealed class CacheEventArgs : EventArgs + public sealed class CacheEntryEventArgs : EventArgs { - internal CacheEventArgs(Cache cache) + internal CacheEntryEventArgs(CacheEntry cache) { Cache = cache; } - internal Cache Cache { get; private set; } + internal CacheEntry Cache { get; } } } \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/CacheInvalidation.cs b/src/Cuemon.Runtime.Caching/CacheInvalidation.cs new file mode 100644 index 000000000..bb3d52cb3 --- /dev/null +++ b/src/Cuemon.Runtime.Caching/CacheInvalidation.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Cuemon.Runtime.Caching +{ + /// + /// Represents a set of eviction and expiration details for a specific cache entry. + /// + public class CacheInvalidation + { + /// + /// Initializes a new instance of the class. + /// + /// The absolute expiration date time value from when the cached value becomes invalid and is removed from the cache. + public CacheInvalidation(DateTime absoluteExpiration) + { + AbsoluteExpiration = absoluteExpiration.ToUniversalTime(); + } + + /// + /// Initializes a new instance of the class. + /// + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached value becomes invalid and is removed from the cache. + public CacheInvalidation(IEnumerable dependencies) + { + Dependencies = dependencies ?? Enumerable.Empty(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The sliding expiration time from when the cached value becomes invalid and is removed from the cache. + public CacheInvalidation(TimeSpan slidingExpiration) + { + Validator.ThrowIfLowerThanOrEqual(slidingExpiration.Ticks, TimeSpan.Zero.Ticks, nameof(slidingExpiration), "The specified sliding expiration cannot be less than or equal to TimeSpan.Zero."); + Validator.ThrowIfGreaterThan(slidingExpiration.Ticks, TimeSpan.FromDays(365).Ticks, nameof(slidingExpiration), "The specified sliding expiration cannot exceed one year."); + SlidingExpiration = slidingExpiration; + } + + /// + /// Gets a sequence of objects implementing the interface assigned to a . + /// + /// A sequence of objects implementing the interface assigned to a . + public IEnumerable Dependencies { get; } + + /// + /// Gets the UTC absolute expiration date time value of a . + /// + /// The UTC absolute expiration date time value of a . + public DateTime? AbsoluteExpiration { get; } + + /// + /// Gets the sliding expiration time of a . + /// + /// The sliding expiration time of a . + public TimeSpan? SlidingExpiration { get; } + + /// + /// Gets a value indicating whether a should use property for cache invalidation. + /// + /// + /// true if a should use property for cache invalidation; otherwise, false. + /// + public bool UseAbsoluteExpiration => AbsoluteExpiration.HasValue; + + /// + /// Gets a value indicating whether a should use property for cache invalidation. + /// + /// + /// true if a should use property for cache invalidation; otherwise, false. + /// + public bool UseSlidingExpiration => SlidingExpiration.HasValue; + + /// + /// Gets a value indicating whether this is relying on an implementation for cache invalidation. + /// + /// true if a is relying on an implementation for cache invalidation; otherwise, false. + public bool UseDependency => Dependencies != null && Dependencies.Any(); + } +} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/CachingManager.cs b/src/Cuemon.Runtime.Caching/CachingManager.cs index 8b9abb06a..2b750c362 100644 --- a/src/Cuemon.Runtime.Caching/CachingManager.cs +++ b/src/Cuemon.Runtime.Caching/CachingManager.cs @@ -1,17 +1,19 @@ -namespace Cuemon.Runtime.Caching +using System; +using System.Threading; + +namespace Cuemon.Runtime.Caching { /// /// Provides access to caching in an application. /// public static class CachingManager { + private static readonly Lazy Singleton = new Lazy(LazyThreadSafetyMode.ExecutionAndPublication); + /// - /// Gets a collection of cached objects for the current application domain. + /// Gets a singleton instance of that is an in-memory cache for an application. /// - /// A collection of cached objects for the current application domain. - public static CacheCollection Cache - { - get { return CacheCollection.Cache; } - } + /// A singleton instance of that is an in-memory cache for an application. + public static SlimMemoryCache Cache => Singleton.Value; } } \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/Cuemon.Runtime.Caching.csproj b/src/Cuemon.Runtime.Caching/Cuemon.Runtime.Caching.csproj index d70b38eed..e984a64ab 100644 --- a/src/Cuemon.Runtime.Caching/Cuemon.Runtime.Caching.csproj +++ b/src/Cuemon.Runtime.Caching/Cuemon.Runtime.Caching.csproj @@ -1,30 +1,20 @@ - + - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 160bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Runtime.Caching Cuemon.Runtime.Caching - The Cuemon.Runtime.Caching assembly provides access to abstractions related to the System.Threading namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Runtime.Caching namespace contains types related to interfaces for generic caching in applications while providing a concrete in-memory cache implementation named SlimMemoryCache. The namespace is an addition to the System.Runtime.Caching namespace. + i-cache-enumerable slim-memory-cache cache-entry cache-invalidation thread-safe + \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/ICacheEnumerable.cs b/src/Cuemon.Runtime.Caching/ICacheEnumerable.cs new file mode 100644 index 000000000..0664931c0 --- /dev/null +++ b/src/Cuemon.Runtime.Caching/ICacheEnumerable.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; + +namespace Cuemon.Runtime.Caching +{ + /// + /// An interface that is used to provide cache implementations for an application. + /// + /// The type of the key in the cache. + public interface ICacheEnumerable : IEnumerable> + { + /// + /// Gets or sets a value in the cache by using the default indexer property for an instance of the class. + /// + /// The unique identifier for the cache value to get or set. + /// The optional named group associated with the cache value. + /// The value in the cache for the specified , if the entry exists; otherwise, null. + object this[string key, string ns = CacheEntry.NoScope] + { + get; + set; + } + + /// + /// Gets the function delegate that is responsible for providing a unique identifier for the cache entry. + /// + /// The function delegate that is responsible for providing a unique identifier for the cache entry. + Func KeyProvider { get; } + + /// + /// Inserts a cache entry into the cache as a instance, and adds details about how the entry should be evicted. + /// + /// The object representing the cached value for a cache entry. + /// The object that contains expiration details for a specific cache entry. + bool Add(CacheEntry entry, CacheInvalidation invalidation); + + /// + /// Determines whether a cache entry exists in the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// true if the cache contains a cache entry whose key matches ; otherwise, false. + bool Contains(string key, string ns = CacheEntry.NoScope); + + /// + /// Gets the number of entries associated with the contained in the cache. + /// + /// The optional namespace that provides a scope to the cache. + /// The number of entries contained in the cache. + int Count(string ns = CacheEntry.NoScope); + + /// + /// Removes all entries associated with the contained in the cache. + /// + /// The optional namespace that provides a scope to the cache. + void RemoveAll(string ns = CacheEntry.NoScope); + + /// + /// Returns an entry from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// A reference to the value in the cache container that is identified by , if the entry exists; otherwise, null. + object Get(string key, string ns = CacheEntry.NoScope); + + /// + /// Returns an entry from the cache as a instance. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// A reference to the that is identified by , if the entry exists; otherwise, null. + CacheEntry GetCacheEntry(string key, string ns = CacheEntry.NoScope); + + /// + /// Removes a cache entry from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// If the entry is found in the cache, a reference to the value in the cache container of the removed cache entry; otherwise, null. + object Remove(string key, string ns = default); + + /// + /// Inserts a cache entry into the cache. + /// + /// The unique identifier of the cache. + /// The stored value of the cache. + /// The object that contains expiration details for a specific cache entry. + /// The optional namespace that provides a scope to the cache. + void Set(string key, object value, CacheInvalidation invalidation, string ns = CacheEntry.NoScope); + + /// + /// Attempts to get the associated with the specified from the cache. + /// + /// The unique identifier for the cache entry. + /// When this method returns, contains the cache entry associated with the specified , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + bool TryGetCacheEntry(string key, out CacheEntry cacheEntry); + + /// + /// Attempts to get the associated with the specified and from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// When this method returns, contains the cache entry associated with the specified and , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + bool TryGetCacheEntry(string key, string ns, out CacheEntry cacheEntry); + + /// + /// Attempts to get the value associated with the specified from the cache. + /// + /// The unique identifier for the cache entry. + /// When this method returns, contains the value associated with the specified , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + bool TryGet(string key, out object value); + + /// + /// Attempts to get the value associated with the specified and from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// When this method returns, contains the value associated with the specified and , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + bool TryGet(string key, string ns, out object value); + } +} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/Properties/PackageReleaseNotes.txt b/src/Cuemon.Runtime.Caching/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..09c2639bd --- /dev/null +++ b/src/Cuemon.Runtime.Caching/Properties/PackageReleaseNotes.txt @@ -0,0 +1,18 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- Some features (such as Memoization techniques and GetOrAdd convenience) was moved to the Cuemon.Extensions.Runtime.Caching namespace as extension methods (to keep the ICacheEnumerable{TKey} slim) +- The extent of refactoring applied to this project resulted in so many breaking changes that a git diff is advisable +  +# Breaking Changes +- CHANGED CachingManager class to return a singleton of SlimMemoryCache with default options (kept for legacy and convenience) +- REPLACED Cache class in the Cuemon.Runtime.Caching namespace with CacheEntry and split the cache invalidation part into its own class; CacheInvalidation +- REPLACED CacheCollection class in the Cuemon.Runtime.Caching namespace with SlimMemoryCache +  +# New Features +- ADDED CacheEntry class in the Cuemon.Runtime.Caching namespace that represents an individual cache entry in the cache +- ADDED CacheInvalidation class in the Cuemon.Runtime.Caching namespace that represents a set of eviction and expiration details for a specific cache entry +- ADDED ICacheEnumerable{TKey} interface in the Cuemon.Runtime.Caching namespace that is used to provide cache implementations for an application +- ADDED SlimMemoryCache class in the Cuemon.Runtime.Caching namespace that represents the type that implements an in-memory cache for an application +- ADDED SlimMemoryCacheOptions class in the Cuemon.Runtime.Caching namespace that specifies options related to SlimMemoryCache \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/SlimMemoryCache.cs b/src/Cuemon.Runtime.Caching/SlimMemoryCache.cs new file mode 100644 index 000000000..f244b64e1 --- /dev/null +++ b/src/Cuemon.Runtime.Caching/SlimMemoryCache.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using Cuemon.Collections.Generic; +using Cuemon.Threading; + +namespace Cuemon.Runtime.Caching +{ + /// + /// Represents the type that implements an in-memory cache for an application. + /// + /// + /// + public class SlimMemoryCache : Disposable, ICacheEnumerable + { + private readonly ConcurrentDictionary _innerCaches = new ConcurrentDictionary(); + private readonly Timer _expirationTimer; + + /// + /// Initializes a new instance of the class. + /// + /// The which may be configured. + public SlimMemoryCache(Action setup = null) + { + var options = Patterns.Configure(setup); + KeyProvider = options.KeyProvider; + if (options.EnableCleanup) + { + _expirationTimer = TimerFactory.CreateNonCapturingTimer(state => ((SlimMemoryCache)state).OnAutomatedSweepCleanup(), this, options.FirstSweep, options.SucceedingSweep); + } + } + + /// + /// Gets or sets a value in the cache by using the default indexer property for an instance of the class. + /// + /// The unique identifier for the cache value to get or set. + /// The optional namespace that provides a scope to the cache. + /// The value in the cache for the specified , if the entry exists; otherwise, null. + public object this[string key, string ns = CacheEntry.NoScope] + { + get + { + if (TryGetCacheEntry(key, ns, out var cache)) + { + return cache.Value; + } + return null; + } + set + { + if (TryGetCacheEntry(key, ns, out var cache)) + { + cache.Value = value; + } + } + } + + /// + /// Gets the function delegate that is responsible for providing a unique identifier for the cache entry. + /// + /// The function delegate that is responsible for providing a unique identifier for the cache entry. + public Func KeyProvider { get; } + + /// + /// Inserts a cache entry into the cache and adds details about how the entry should be evicted. + /// + /// The unique identifier of the cache. + /// The stored value of the cache. + /// The absolute expiration date time value from when the cached becomes invalid and is removed from the cache. + /// The optional namespace that provides a scope to the cache. + /// true if insertion succeeded; otherwise, false when there is already an entry in the cache with the same key. + /// + /// cannot be null. + /// + public bool Add(string key, object value, DateTime absoluteExpiration, string ns = CacheEntry.NoScope) + { + return Add(new CacheEntry(key, value, ns), new CacheInvalidation(absoluteExpiration)); + } + + /// + /// Inserts a cache entry into the cache and adds details about how the entry should be evicted. + /// + /// The unique identifier of the cache. + /// The stored value of the cache. + /// An implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached becomes invalid and is removed from the cache. + /// The optional namespace that provides a scope to the cache. + /// true if insertion succeeded; otherwise, false when there is already an entry in the cache with the same key. + /// + /// cannot be null. + /// + public bool Add(string key, object value, IDependency dependency, string ns = CacheEntry.NoScope) + { + return Add(new CacheEntry(key, value, ns), new CacheInvalidation(Arguments.Yield(dependency))); + } + + /// + /// Inserts a cache entry into the cache and adds details about how the entry should be evicted. + /// + /// The unique identifier of the cache. + /// The stored value of the cache. + /// A sequence of implementations that monitors changes in the state of the data which a cache entry depends on. If a state change is registered, the cached becomes invalid and is removed from the cache. + /// The optional namespace that provides a scope to the cache. + /// true if insertion succeeded; otherwise, false when there is already an entry in the cache with the same key. + /// + /// cannot be null. + /// + public bool Add(string key, object value, IEnumerable dependencies, string ns = CacheEntry.NoScope) + { + return Add(new CacheEntry(key, value, ns), new CacheInvalidation(dependencies)); + } + + /// + /// Inserts a cache entry into the cache and adds details about how the entry should be evicted. + /// + /// The unique identifier of the cache. + /// The stored value of the cache. + /// The sliding expiration time from when the cached becomes invalid and is removed from the cache. + /// The optional namespace that provides a scope to the cache. + /// true if insertion succeeded; otherwise, false when there is already an entry in the cache with the same key. + /// + /// cannot be null. + /// + public bool Add(string key, object value, TimeSpan slidingExpiration, string ns = CacheEntry.NoScope) + { + return Add(new CacheEntry(key, value, ns), new CacheInvalidation(slidingExpiration)); + } + + /// + /// Inserts a cache entry into the cache and adds details about how the entry should be evicted. + /// + /// The object representing the cached value for a cache entry. + /// The object that contains expiration details for a specific cache entry. + /// true if insertion succeeded; otherwise, false when there is already an entry in the cache with the same key. + /// + /// cannot be null -or- + /// cannot be null. + /// + public bool Add(CacheEntry entry, CacheInvalidation invalidation) + { + Validator.ThrowIfNull(entry, nameof(entry)); + Validator.ThrowIfNull(invalidation, nameof(invalidation)); + var nsKey = KeyProvider(entry.Key, entry.Namespace); + return _innerCaches.TryAdd(nsKey, entry.SetInvalidation(invalidation).StartDependencies()); + } + + /// + /// Determines whether a cache entry exists in the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// true if the cache contains a cache entry whose key matches ; otherwise, false. + /// + /// cannot be null. + /// + public bool Contains(string key, string ns = CacheEntry.NoScope) + { + return TryGetCacheEntry(key, ns, out _); + } + + /// + /// Gets the number of entries associated with the contained in the cache. + /// + /// The optional namespace that provides a scope to the cache. + /// The number of entries contained in the cache. + public int Count(string ns = CacheEntry.NoScope) + { + return ListCacheEntries(ns).Count; + } + + /// + /// Removes all entries associated with the contained in the cache. + /// + /// The optional namespace that provides a scope to the cache. + public void RemoveAll(string ns = CacheEntry.NoScope) + { + var entries = ListCacheEntries(ns); + foreach (var cacheEntry in entries) + { + Remove(cacheEntry.Key, cacheEntry.Namespace); + } + } + + /// + /// Returns an entry from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// A reference to the value in the cache container that is identified by , if the entry exists; otherwise, null. + /// + /// cannot be null. + /// + public object Get(string key, string ns = CacheEntry.NoScope) + { + return GetCacheEntry(key, ns)?.Value; + } + + /// + /// Returns an entry from the cache as a instance. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// A reference to the that is identified by , if the entry exists; otherwise, null. + /// + /// cannot be null. + /// + public CacheEntry GetCacheEntry(string key, string ns = CacheEntry.NoScope) + { + if (TryGetCacheEntry(key, ns, out var cacheEntry)) + { + return cacheEntry; + } + return null; + } + + /// + /// Removes a cache entry from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// If the entry is found in the cache, a reference to the value in the cache container of the removed cache entry; otherwise, null. + /// + /// cannot be null. + /// + public object Remove(string key, string ns = CacheEntry.NoScope) + { + Validator.ThrowIfNull(key, nameof(key)); + var nsKey = KeyProvider(key, ns); + if (_innerCaches.TryRemove(nsKey, out var cacheEntry)) + { + return cacheEntry.Value; + } + return null; + } + + /// + /// Inserts a cache entry into the cache. + /// + /// The unique identifier of the cache. + /// The stored value of the cache. + /// The object that contains expiration details for a specific cache entry. + /// The optional namespace that provides a scope to the cache. + /// + /// cannot be null -or- + /// cannot be null. + /// + /// The method always puts a cache value in the cache, regardless whether an entry already exists with the same key. If the specified entry does not exist in the cache, a new cache entry is inserted. If the specified entry exists, its value is updated. + public void Set(string key, object value, CacheInvalidation invalidation, string ns = CacheEntry.NoScope) + { + Validator.ThrowIfNull(key, nameof(key)); + if (TryGetCacheEntry(key, ns, out var cacheEntry)) + { + cacheEntry.Value = value; + cacheEntry.Refresh(); + } + else + { + Add(new CacheEntry(key, value, ns), invalidation); + } + } + + /// + /// Attempts to get the associated with the specified from the cache. + /// + /// The unique identifier for the cache entry. + /// When this method returns, contains the cache entry associated with the specified , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + public bool TryGetCacheEntry(string key, out CacheEntry cacheEntry) + { + return TryGetCacheEntry(key, CacheEntry.NoScope, out cacheEntry); + } + + /// + /// Attempts to get the associated with the specified and from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// When this method returns, contains the cache entry associated with the specified and , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + /// + /// cannot be null. + /// + public bool TryGetCacheEntry(string key, string ns, out CacheEntry cacheEntry) + { + Validator.ThrowIfNull(key, nameof(key)); + cacheEntry = null; + var utcNow = DateTime.UtcNow; + var nsKey = KeyProvider(key, ns); + if (_innerCaches.TryGetValue(nsKey, out var ce)) + { + var hasCacheExpired = ce.HasExpired(utcNow); + if (ce.CanExpire && hasCacheExpired) + { + Remove(key, ns); + return false; + } + + if (ce.CanExpire && !hasCacheExpired) + { + cacheEntry = ce; + ce.Refresh(); + } + return true; + } + return false; + } + + /// + /// Attempts to get the value associated with the specified from the cache. + /// + /// The unique identifier for the cache entry. + /// When this method returns, contains the value associated with the specified , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + public bool TryGet(string key, out object value) + { + return TryGet(key, CacheEntry.NoScope, out value); + } + + /// + /// Attempts to get the value associated with the specified and from the cache. + /// + /// The unique identifier for the cache entry. + /// The optional namespace that provides a scope to the cache. + /// When this method returns, contains the value associated with the specified and , or null if the operation failed. + /// true if the was found in the cache; otherwise, false. + /// + /// cannot be null. + /// + public bool TryGet(string key, string ns, out object value) + { + var success = TryGetCacheEntry(key, ns, out var cacheEntry); + value = success ? cacheEntry.Value : null; + return success; + } + + private IList ListCacheEntries(string ns) + { + var utcNow = DateTime.UtcNow; + var entries = new List(); + var snapshot = new List(_innerCaches.Values); + foreach (var cacheEntry in snapshot) + { + if (cacheEntry == null) { continue; } // this can happen if a cache has been removed + if (cacheEntry.CanExpire && cacheEntry.HasExpired(utcNow)) { continue; } + if (cacheEntry.Namespace == ns) + { + entries.Add(cacheEntry); + } + } + return entries; + } + + /// + /// Called when this object is being disposed by either or having disposing set to true and is false. + /// + protected override void OnDisposeManagedResources() + { + _expirationTimer?.Dispose(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + public IEnumerator> GetEnumerator() + { + return _innerCaches.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private void OnAutomatedSweepCleanup() + { + var utcNow = DateTime.UtcNow; + var snapshot = new List(_innerCaches.Values); + if (snapshot.Count > 0) + { + foreach (var cacheEntry in snapshot) + { + if (cacheEntry == null) { continue; } + if (cacheEntry.CanExpire && cacheEntry.HasExpired(utcNow)) { Remove(cacheEntry.Key, cacheEntry.Namespace); } + } + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Runtime.Caching/SlimMemoryCacheOptions.cs b/src/Cuemon.Runtime.Caching/SlimMemoryCacheOptions.cs new file mode 100644 index 000000000..c9f16fbc5 --- /dev/null +++ b/src/Cuemon.Runtime.Caching/SlimMemoryCacheOptions.cs @@ -0,0 +1,70 @@ +using System; + +namespace Cuemon.Runtime.Caching +{ + /// + /// Configuration options for . + /// + public class SlimMemoryCacheOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// true + /// + /// + /// + /// After 30 seconds + /// + /// + /// + /// Every 2 minutes + /// + /// + /// + /// (key, ns) => Generate.HashCode64(ns == Cache.NoScope ? key.ToUpperInvariant() : $"{key}^{nameof(SlimMemoryCache)}^{ns}".ToUpperInvariant()); + /// + /// + /// + public SlimMemoryCacheOptions() + { + EnableCleanup = true; + FirstSweep = TimeSpan.FromSeconds(30); + SucceedingSweep = TimeSpan.FromMinutes(2); + KeyProvider = (key, ns) => Generate.HashCode64(ns == CacheEntry.NoScope ? key.ToUpperInvariant() : $"{key}^{nameof(SlimMemoryCache)}^{ns}".ToUpperInvariant()); + } + + /// + /// Gets or sets a value indicating whether a periodic sweep clean-up is done on the cache. + /// + /// true if a periodic sweep clean-up is done on the cache; otherwise, false. + public bool EnableCleanup { get; set; } + + /// + /// Gets or sets the that specifies the amount of time to wait before the initial first sweep clean-up. + /// + /// The that specifies the amount of time to wait before the initial first sweep clean-up. + public TimeSpan FirstSweep { get; set; } + + /// + /// Gets or sets the that specifies the interval for every succeeding sweep clean-up after the initial . + /// + /// The that specifies the interval for every succeeding sweep clean-up. + public TimeSpan SucceedingSweep { get; set; } + + /// + /// Gets or sets the function delegate that is responsible for providing a unique identifier for a cache entry. + /// + /// The function delegate that is responsible for providing a unique identifier for a cache entry. + public Func KeyProvider { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Security/Cryptography/AesCryptor.cs b/src/Cuemon.Security.Cryptography/AesCryptor.cs similarity index 98% rename from src/Cuemon.Core/Security/Cryptography/AesCryptor.cs rename to src/Cuemon.Security.Cryptography/AesCryptor.cs index b2baf8b17..c1f333330 100644 --- a/src/Cuemon.Core/Security/Cryptography/AesCryptor.cs +++ b/src/Cuemon.Security.Cryptography/AesCryptor.cs @@ -93,7 +93,7 @@ private byte[] CryptoTransformCore(byte[] value, AesMode mode, Action new MemoryStream(), (ms, rijndael, bytes) => + using (var sms = Patterns.SafeInvoke(() => new MemoryStream(), (ms, rijndael, bytes) => { CryptoStream cryptoStream; switch (mode) diff --git a/src/Cuemon.Core/Security/Cryptography/AesCryptorOptions.cs b/src/Cuemon.Security.Cryptography/AesCryptorOptions.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/AesCryptorOptions.cs rename to src/Cuemon.Security.Cryptography/AesCryptorOptions.cs diff --git a/src/Cuemon.Core/Security/Cryptography/AesKeyOptions.cs b/src/Cuemon.Security.Cryptography/AesKeyOptions.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/AesKeyOptions.cs rename to src/Cuemon.Security.Cryptography/AesKeyOptions.cs diff --git a/src/Cuemon.Core/Security/Cryptography/AesSize.cs b/src/Cuemon.Security.Cryptography/AesSize.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/AesSize.cs rename to src/Cuemon.Security.Cryptography/AesSize.cs diff --git a/src/Cuemon.Security.Cryptography/Cuemon.Security.Cryptography.csproj b/src/Cuemon.Security.Cryptography/Cuemon.Security.Cryptography.csproj new file mode 100644 index 000000000..4103ed5ed --- /dev/null +++ b/src/Cuemon.Security.Cryptography/Cuemon.Security.Cryptography.csproj @@ -0,0 +1,19 @@ + + + + net5.0;netstandard2.0 + 1b0bdf91-e7c7-4cb4-a39d-e1a5374c5602 + + + + Cuemon.Security.Cryptography + Cuemon.Security.Cryptography + The Cuemon.Security.Cryptography namespace contains types related to cryptographic hashing (both keyed and non-keyed) and a ready-to-use implementation of the Advanced Encryption Standard (AES) symmetric algorithm. The namespace is an addition to the System.Security.Cryptography namespace. + aes-cryptor keyed-hash-factory hmac unkeyed-hash-factory unkeyed-crypto-hash keyed-crypto-hash hmac-message-digest-5 hmac-md5 hmac-secure-hash-algorithm-1 hmac-sha1 hmac-secure-hash-algorithm-256 hmac-sha256 hmac-secure-hash-algorithm-384 hmac-sha384 hmac-secure-hash-algorithm-512 hmac-sha512 message-digest-5 md5 secure-hash-algorithm-1 sha1 secure-hash-algorithm-256 sha256 secure-hash-algorithm-384 sha384 secure-hash-algorithm-512 sha512 + + + + + + + \ No newline at end of file diff --git a/src/Cuemon.Core/Security/Cryptography/HmacMessageDigest5.cs b/src/Cuemon.Security.Cryptography/HmacMessageDigest5.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/HmacMessageDigest5.cs rename to src/Cuemon.Security.Cryptography/HmacMessageDigest5.cs diff --git a/src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm1.cs b/src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm1.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm1.cs rename to src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm1.cs diff --git a/src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm256.cs b/src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm256.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm256.cs rename to src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm256.cs diff --git a/src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm384.cs b/src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm384.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm384.cs rename to src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm384.cs diff --git a/src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm512.cs b/src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm512.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/HmacSecureHashAlgorithm512.cs rename to src/Cuemon.Security.Cryptography/HmacSecureHashAlgorithm512.cs diff --git a/src/Cuemon.Core/Security/Cryptography/KeyedCryptoAlgorithm.cs b/src/Cuemon.Security.Cryptography/KeyedCryptoAlgorithm.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/KeyedCryptoAlgorithm.cs rename to src/Cuemon.Security.Cryptography/KeyedCryptoAlgorithm.cs diff --git a/src/Cuemon.Core/Security/Cryptography/KeyedCryptoHash.cs b/src/Cuemon.Security.Cryptography/KeyedCryptoHash.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/KeyedCryptoHash.cs rename to src/Cuemon.Security.Cryptography/KeyedCryptoHash.cs diff --git a/src/Cuemon.Security.Cryptography/KeyedHashFactory.cs b/src/Cuemon.Security.Cryptography/KeyedHashFactory.cs new file mode 100644 index 000000000..0f0ea8475 --- /dev/null +++ b/src/Cuemon.Security.Cryptography/KeyedHashFactory.cs @@ -0,0 +1,112 @@ +using System; + +namespace Cuemon.Security.Cryptography +{ + /// + /// Provides access to factory methods for creating and configuring instances based on . + /// + public static class KeyedHashFactory + { + /// + /// Creates an instance of a HMAC cryptographic implementation that derives from with the specified . Default is . + /// + /// The secret key for the encryption. + /// The that defines the HMAC cryptographic implementation. Default is . + /// The which may be configured. + /// A implementation of the by parameter specified . + /// + /// cannot be null. + /// + public static Hash CreateHmacCrypto(byte[] secret, KeyedCryptoAlgorithm algorithm = default, Action setup = null) + { + switch (algorithm) + { + case KeyedCryptoAlgorithm.HmacMd5: + return CreateHmacCryptoMd5(secret, setup); + case KeyedCryptoAlgorithm.HmacSha1: + return CreateHmacCryptoSha1(secret, setup); + case KeyedCryptoAlgorithm.HmacSha384: + return CreateHmacCryptoSha384(secret, setup); + case KeyedCryptoAlgorithm.HmacSha512: + return CreateHmacCryptoSha512(secret, setup); + default: + return CreateHmacCryptoSha256(secret, setup); + } + } + + /// + /// Creates an instance of . + /// + /// The secret key for encryption. The key can be any length. However, the recommended size is 128 bytes. If the key is more than 128 bytes long, it is hashed (using SHA-384) to derive a 128-byte key. If it is less than 128 bytes long, it is padded to 128 bytes. + /// The which may be configured. + /// A implementation of . + /// + /// cannot be null. + /// + public static Hash CreateHmacCryptoSha512(byte[] secret, Action setup = null) + { + Validator.ThrowIfNull(secret, nameof(secret)); + return new HmacSecureHashAlgorithm512(secret, setup); + } + + /// + /// Creates an instance of . + /// + /// The secret key for encryption. The key can be any length. However, the recommended size is 128 bytes. If the key is more than 128 bytes long, it is hashed (using SHA-384) to derive a 128-byte key. If it is less than 128 bytes long, it is padded to 128 bytes. + /// The which may be configured. + /// A implementation of . + /// + /// cannot be null. + /// + public static Hash CreateHmacCryptoSha384(byte[] secret, Action setup = null) + { + Validator.ThrowIfNull(secret, nameof(secret)); + return new HmacSecureHashAlgorithm384(secret, setup); + } + + /// + /// Creates an instance of . + /// + /// The secret key for encryption. The key can be any length. However, the recommended size is 64 bytes. If the key is more than 64 bytes long, it is hashed (using SHA-256) to derive a 64-byte key. If it is less than 64 bytes long, it is padded to 64 bytes. + /// The which may be configured. + /// A implementation of . + /// + /// cannot be null. + /// + public static Hash CreateHmacCryptoSha256(byte[] secret, Action setup = null) + { + Validator.ThrowIfNull(secret, nameof(secret)); + return new HmacSecureHashAlgorithm256(secret, setup); + } + + /// + /// Creates an instance of . + /// + /// The secret key for encryption. The key can be any length, but if it is more than 64 bytes long it will be hashed (using SHA-1) to derive a 64-byte key. Therefore, the recommended size of the secret key is 64 bytes. + /// The which may be configured. + /// A implementation of . + /// + /// cannot be null. + /// + public static Hash CreateHmacCryptoSha1(byte[] secret, Action setup = null) + { + Validator.ThrowIfNull(secret, nameof(secret)); + return new HmacSecureHashAlgorithm1(secret, setup); + } + + /// + /// Creates an instance of . + /// + /// The secret key for encryption. The key can be any length, but if it is more than 64 bytes long it will be hashed (using SHA-1) to derive a 64-byte key. Therefore, the recommended size of the secret key is 64 bytes. + /// The which may be configured. + /// A implementation of . + /// + /// cannot be null. + /// + public static Hash CreateHmacCryptoMd5(byte[] secret, Action setup = null) + { + Validator.ThrowIfNull(secret, nameof(secret)); + return new HmacMessageDigest5(secret, setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Security/Cryptography/MessageDigest5.cs b/src/Cuemon.Security.Cryptography/MessageDigest5.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/MessageDigest5.cs rename to src/Cuemon.Security.Cryptography/MessageDigest5.cs diff --git a/src/Cuemon.Security.Cryptography/Properties/AssemblyInfo.cs b/src/Cuemon.Security.Cryptography/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..f297d4cf6 --- /dev/null +++ b/src/Cuemon.Security.Cryptography/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: Guid("7d28f2f4-baa5-4ec0-b14f-f7ed3417bfcd")] \ No newline at end of file diff --git a/src/Cuemon.Security.Cryptography/Properties/PackageReleaseNotes.txt b/src/Cuemon.Security.Cryptography/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..d25ffd286 --- /dev/null +++ b/src/Cuemon.Security.Cryptography/Properties/PackageReleaseNotes.txt @@ -0,0 +1,47 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.Security assembly was removed with this version +- Any types found in the former Cuemon.Security namespace was merged either into this namespace (Cuemon.Security.Cryptography) or the Cuemon.Security namespace (Cuemon.Core assembly) +- Any former extension methods of the Cuemon.Security namespace was removed completely due to the new intuitive static factory classes (HashFactory, KeyedHashFactory and UnkeyedHashFactory) +- The extent of refactoring applied to this project resulted in so many breaking changes that a git diff is advisable +  +# Breaking Changes +- REPLACED AdvancedEncryptionStandardKeySize enum in the Cuemon.Security.Cryptography namespace with AesSize +- REMOVED AdvancedEncryptionStandardUtility class from the Cuemon.Security.Cryptography namespace +- REMOVED CyclicRedundancyCheck class from the Cuemon.Security.Cryptography namespace +- REMOVED CyclicRedundancyCheck32 class from the Cuemon.Security.Cryptography namespace +- REPLACED HashAlgorithmType enum in the Cuemon.Security.Cryptography namespace with UnkeyedCryptoAlgorithm +- REMOVED HashOptions class from the Cuemon.Security.Cryptography namespace +- MOVED HashResult class to the Cuemon.Security namespace +- REPLACED HashUtility class in the Cuemon.Security.Cryptography namespace with UnkeyedHashFactory +- REMOVED HashUtilityExtensions class from the Cuemon.Security.Cryptography namespace +- REPLACED HmacAlgorithmType enum in the Cuemon.Security.Cryptography namespace with KeyedCryptoAlgorithm +- REPLACED HmacUtility class in the Cuemon.Security.Cryptography namespace with KeyedHashFactory +- REMOVED HmacUtilityExtensions class from the Cuemon.Security.Cryptography namespace +- REMOVED KeyedHashOptions class from the Cuemon.Security.Cryptography namespace +- REMOVED PolynomialRepresentation enum from the Cuemon.Security.Cryptography namespace +- REMOVED StreamHashOptions class from the Cuemon.Security.Cryptography namespace +- REMOVED StreamKeyedHashOptions class from the Cuemon.Security.Cryptography namespace +- REMOVED StringHashOptions class from the Cuemon.Security.Cryptography namespace +- REMOVED StringKeyedHashOptions class from the Cuemon.Security.Cryptography namespace +- REMOVED StrongNumberUtility class from the Cuemon.Security.Cryptography namespace (replaced with Generate.RandomNumber in the Cuemon namespace) +  +# New Features +- ADDED AesCryptor class in the Cuemon.Security.Cryptography namespace that provides an implementation of the Advanced Encryption Standard (AES) symmetric algorithm +- ADDED AesCryptorOptions class in the Cuemon.Security.Cryptography namespace that specifies options related to AesCryptor +- ADDED AesKeyOptions class in the Cuemon.Security.Cryptography namespace that specifies options related to AesCryptor.GenerateKey +- ADDED HmacMessageDigest5 class in the Cuemon.Security.Cryptography namespace that provides a Hash-based Message Authentication Code (HMAC) using the MD5 hash function +- ADDED HmacSecureHashAlgorithm1 class in the Cuemon.Security.Cryptography namespace that provides a Hash-based Message Authentication Code (HMAC) using the SHA1 hash function +- ADDED HmacSecureHashAlgorithm256 class in the Cuemon.Security.Cryptography namespace that provides a Hash-based Message Authentication Code (HMAC) using the SHA256 hash function +- ADDED HmacSecureHashAlgorithm384 class in the Cuemon.Security.Cryptography namespace that provides a Hash-based Message Authentication Code (HMAC) using the SHA384 hash function +- ADDED HmacSecureHashAlgorithm512 class in the Cuemon.Security.Cryptography namespace that provides a Hash-based Message Authentication Code (HMAC) using the SHA512 hash function +- ADDED KeyedCryptoHash class in the Cuemon.Security.Cryptography namespace that represents the base class from which all implementations of Hash-based Message Authentication Code (HMAC) should derive +- ADDED MessageDigest5 class in the Cuemon.Security.Cryptography namespace that provides a MD5 implementation of the MD (Message Digest) cryptographic hashing algorithm for 128-bit hash values +- ADDED SecureHashAlgorithm1 class in the Cuemon.Security.Cryptography namespace that provides a SHA-1 implementation of the SHA (Secure Hash Algorithm) cryptographic hashing algorithm for 160-bit hash values +- ADDED SecureHashAlgorithm256 class in the Cuemon.Security.Cryptography namespace that provides a SHA-256 implementation of the SHA (Secure Hash Algorithm) cryptographic hashing algorithm for 256-bit hash values +- ADDED SecureHashAlgorithm384 class in the Cuemon.Security.Cryptography namespace that provides a SHA-384 implementation of the SHA (Secure Hash Algorithm) cryptographic hashing algorithm for 384-bit hash values +- ADDED SecureHashAlgorithm512 class in the Cuemon.Security.Cryptography namespace that provides a SHA-512 implementation of the SHA (Secure Hash Algorithm) cryptographic hashing algorithm for 512-bit hash values +- ADDED UnkeyedCryptoHash class in the Cuemon.Security.Cryptography namespace that represents the base class from which all implementations of cryptographic hashing algorithm should derive +  \ No newline at end of file diff --git a/src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm1.cs b/src/Cuemon.Security.Cryptography/SecureHashAlgorithm1.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm1.cs rename to src/Cuemon.Security.Cryptography/SecureHashAlgorithm1.cs diff --git a/src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm256.cs b/src/Cuemon.Security.Cryptography/SecureHashAlgorithm256.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm256.cs rename to src/Cuemon.Security.Cryptography/SecureHashAlgorithm256.cs diff --git a/src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm384.cs b/src/Cuemon.Security.Cryptography/SecureHashAlgorithm384.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm384.cs rename to src/Cuemon.Security.Cryptography/SecureHashAlgorithm384.cs diff --git a/src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm512.cs b/src/Cuemon.Security.Cryptography/SecureHashAlgorithm512.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/SecureHashAlgorithm512.cs rename to src/Cuemon.Security.Cryptography/SecureHashAlgorithm512.cs diff --git a/src/Cuemon.Core/Security/Cryptography/CryptoAlgorithm.cs b/src/Cuemon.Security.Cryptography/UnkeyedCryptoAlgorithm.cs similarity index 95% rename from src/Cuemon.Core/Security/Cryptography/CryptoAlgorithm.cs rename to src/Cuemon.Security.Cryptography/UnkeyedCryptoAlgorithm.cs index 5086d0b51..782f2fbc7 100644 --- a/src/Cuemon.Core/Security/Cryptography/CryptoAlgorithm.cs +++ b/src/Cuemon.Security.Cryptography/UnkeyedCryptoAlgorithm.cs @@ -3,7 +3,7 @@ /// /// Specifies the different implementations of a cryptographic hashing algorithm. /// - public enum CryptoAlgorithm + public enum UnkeyedCryptoAlgorithm { /// /// The Message Digest 5 (MD5) algorithm (128 bits). diff --git a/src/Cuemon.Core/Security/Cryptography/UnkeyedCryptoHash.cs b/src/Cuemon.Security.Cryptography/UnkeyedCryptoHash.cs similarity index 100% rename from src/Cuemon.Core/Security/Cryptography/UnkeyedCryptoHash.cs rename to src/Cuemon.Security.Cryptography/UnkeyedCryptoHash.cs diff --git a/src/Cuemon.Security.Cryptography/UnkeyedHashFactory.cs b/src/Cuemon.Security.Cryptography/UnkeyedHashFactory.cs new file mode 100644 index 000000000..48a88ece2 --- /dev/null +++ b/src/Cuemon.Security.Cryptography/UnkeyedHashFactory.cs @@ -0,0 +1,83 @@ +using System; + +namespace Cuemon.Security.Cryptography +{ + /// + /// Provides access to factory methods for creating and configuring instances based on . + /// + public static class UnkeyedHashFactory + { + /// + /// Creates an instance of a cryptographic implementation that derives from with the specified . Default is . + /// + /// The that defines the cryptographic implementation. Default is . + /// The which may be configured. + /// A implementation of the by parameter specified . + public static Hash CreateCrypto(UnkeyedCryptoAlgorithm algorithm = default, Action setup = null) + { + switch (algorithm) + { + case UnkeyedCryptoAlgorithm.Md5: + return CreateCryptoMd5(setup); + case UnkeyedCryptoAlgorithm.Sha1: + return CreateCryptoSha1(setup); + case UnkeyedCryptoAlgorithm.Sha384: + return CreateCryptoSha384(setup); + case UnkeyedCryptoAlgorithm.Sha512: + return CreateCryptoSha512(setup); + default: + return CreateCryptoSha256(setup); + } + } + + /// + /// Creates an instance of . + /// + /// The which may be configured. + /// A implementation of . + public static Hash CreateCryptoSha512(Action setup = null) + { + return new SecureHashAlgorithm512(setup); + } + + /// + /// Creates an instance of . + /// + /// The which may be configured. + /// A implementation of . + public static Hash CreateCryptoSha384(Action setup = null) + { + return new SecureHashAlgorithm384(setup); + } + + /// + /// Creates an instance of . + /// + /// The which may be configured. + /// A implementation of . + public static Hash CreateCryptoSha256(Action setup = null) + { + return new SecureHashAlgorithm256(setup); + } + + /// + /// Creates an instance of . + /// + /// The which may be configured. + /// A implementation of . + public static Hash CreateCryptoSha1(Action setup = null) + { + return new SecureHashAlgorithm1(setup); + } + + /// + /// Creates an instance of . + /// + /// The which may be configured. + /// A implementation of . + public static Hash CreateCryptoMd5(Action setup = null) + { + return new MessageDigest5(setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ActionForEachSynchronousLoop.cs b/src/Cuemon.Threading/ActionForEachSynchronousLoop.cs new file mode 100644 index 000000000..259dbd9e0 --- /dev/null +++ b/src/Cuemon.Threading/ActionForEachSynchronousLoop.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Cuemon.Collections.Generic; + +namespace Cuemon.Threading +{ + internal sealed class ActionForEachSynchronousLoop : ForEachSynchronousLoop + { + public ActionForEachSynchronousLoop(IEnumerable source, Action setup) : base(source, setup) + { + Partitioner = new PartitionerEnumerable(source, Options.PartitionSize); + WhileCondition = () => Partitioner.HasPartitions; + } + + private PartitionerEnumerable Partitioner { get; set; } + + protected override void FillWorkQueueWorkerFactory(TemplateFactory worker, long sorter) + { + if (worker is ActionFactory wf) { wf.ExecuteMethod(); } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ActionForSynchronousLoop.cs b/src/Cuemon.Threading/ActionForSynchronousLoop.cs new file mode 100644 index 000000000..54fc8ce61 --- /dev/null +++ b/src/Cuemon.Threading/ActionForSynchronousLoop.cs @@ -0,0 +1,16 @@ +using System; + +namespace Cuemon.Threading +{ + internal sealed class ActionForSynchronousLoop : ForSynchronousLoop where TOperand : struct, IComparable, IEquatable, IConvertible + { + public ActionForSynchronousLoop(ForLoopRuleset rules, Action setup) : base(rules, setup) + { + } + + protected override void FillWorkQueueWorkerFactory(TemplateFactory worker) + { + if (worker is ActionFactory wf) { wf.ExecuteMethod(); } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ActionWhileSynchronousLoop.cs b/src/Cuemon.Threading/ActionWhileSynchronousLoop.cs new file mode 100644 index 000000000..dd831f0bd --- /dev/null +++ b/src/Cuemon.Threading/ActionWhileSynchronousLoop.cs @@ -0,0 +1,17 @@ +using System; + +namespace Cuemon.Threading +{ + internal sealed class ActionWhileSynchronousLoop : WhileSynchronousLoop + { + public ActionWhileSynchronousLoop(ForwardIterator iterator, Action setup) : base(iterator, setup) + { + } + + + protected override void FillWorkQueueWorkerFactory(TemplateFactory worker, long sorter) + { + if (worker is ActionFactory wf) { wf.ExecuteMethod(); } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/AdvancedParallelFactory.For.cs b/src/Cuemon.Threading/AdvancedParallelFactory.For.cs new file mode 100644 index 000000000..84fcefbb3 --- /dev/null +++ b/src/Cuemon.Threading/AdvancedParallelFactory.For.cs @@ -0,0 +1,158 @@ +using System; + +namespace Cuemon.Threading +{ + public static partial class AdvancedParallelFactory + { + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static void For(ForLoopRuleset rules, Action worker, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForCore(rules, ActionFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The parameter of the delegate . + /// The which may be configured. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static void For(ForLoopRuleset rules, Action worker, T arg, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForCore(rules, ActionFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The which may be configured. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static void For(ForLoopRuleset rules, Action worker, T1 arg1, T2 arg2, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForCore(rules, ActionFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The which may be configured. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static void For(ForLoopRuleset rules, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForCore(rules, ActionFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The which may be configured. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static void For(ForLoopRuleset rules, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForCore(rules, ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The fifth parameter of the delegate . + /// The which may be configured. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static void For(ForLoopRuleset rules, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(worker, nameof(worker)); + Validator.ThrowIfNull(rules, nameof(rules)); + ForCore(rules, ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static void ForCore(ForLoopRuleset rules, ActionFactory workerFactory, Action setup) + where TWorker : Template + where TOperand : struct, IComparable, IEquatable, IConvertible + { + new ActionForSynchronousLoop(rules, setup).PrepareExecution(workerFactory); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/AdvancedParallelFactory.ForAsync.cs b/src/Cuemon.Threading/AdvancedParallelFactory.ForAsync.cs new file mode 100644 index 000000000..8bf24902d --- /dev/null +++ b/src/Cuemon.Threading/AdvancedParallelFactory.ForAsync.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + public static partial class AdvancedParallelFactory + { + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The rules of a for-loop control flow statement. + /// The based function delegate that is invoked once per iteration. + /// The which may be configured. + /// A that represents the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task ForAsync(ForLoopRuleset rules, Func worker, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForCoreAsync(rules, TaskActionFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The based function delegate that is invoked once per iteration. + /// The parameter of the delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task ForAsync(ForLoopRuleset rules, Func worker, T arg, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForCoreAsync(rules, TaskActionFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The based function delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task ForAsync(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForCoreAsync(rules, TaskActionFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The based function delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task ForAsync(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForCoreAsync(rules, TaskActionFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The based function delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task ForAsync(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForCoreAsync(rules, TaskActionFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The rules of a for-loop control flow statement. + /// The based function delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The fifth parameter of the delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task ForAsync(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForCoreAsync(rules, TaskActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static async Task ForCoreAsync(ForLoopRuleset rules, TaskActionFactory workerFactory, Action setup) + where TWorker : Template + where TOperand : struct, IComparable, IEquatable, IConvertible + { + var from = rules.From; + var options = Patterns.Configure(setup); + TOperand processed = default; + + while (true) + { + var workChunks = options.PartitionSize; + var queue = new List(); + for (var i = from; rules.Condition(i, rules.Relation, rules.To); i = rules.Iterator(i, rules.Assignment, rules.Step)) + { + workerFactory.GenericArguments.Arg1 = i; + queue.Add(workerFactory.ExecuteMethodAsync(options.CancellationToken)); + + processed = i; + workChunks--; + + if (workChunks == 0) { break; } + } + from = Calculator.Calculate(processed, rules.Assignment, rules.Step); + if (queue.Count == 0) { break; } + await Task.WhenAll(queue).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/AdvancedParallelFactory.ForResult.cs b/src/Cuemon.Threading/AdvancedParallelFactory.ForResult.cs new file mode 100644 index 000000000..da115a413 --- /dev/null +++ b/src/Cuemon.Threading/AdvancedParallelFactory.ForResult.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + public static partial class AdvancedParallelFactory + { + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IReadOnlyCollection ForResult(ForLoopRuleset rules, Func worker, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCore(rules, FuncFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IReadOnlyCollection ForResult(ForLoopRuleset rules, Func worker, T arg, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCore(rules, FuncFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IReadOnlyCollection ForResult(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCore(rules, FuncFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IReadOnlyCollection ForResult(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCore(rules, FuncFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IReadOnlyCollection ForResult(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCore(rules, FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the fifth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The fifth parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IReadOnlyCollection ForResult(ForLoopRuleset rules, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCore(rules, FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static IReadOnlyCollection ForResultCore(ForLoopRuleset rules, FuncFactory workerFactory, Action setup) + where TWorker : Template + where TOperand : struct, IComparable, IEquatable, IConvertible + { + return new FuncForSynchronousLoop(rules, setup).GetResult(workerFactory); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/AdvancedParallelFactory.ForResultAsync.cs b/src/Cuemon.Threading/AdvancedParallelFactory.ForResultAsync.cs new file mode 100644 index 000000000..3a2daf6e0 --- /dev/null +++ b/src/Cuemon.Threading/AdvancedParallelFactory.ForResultAsync.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + public static partial class AdvancedParallelFactory + { + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task> ForResultAsync(ForLoopRuleset rules, Func> worker, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCoreAsync(rules, TaskFuncFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task> ForResultAsync(ForLoopRuleset rules, Func> worker, T arg, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCoreAsync(rules, TaskFuncFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task> ForResultAsync(ForLoopRuleset rules, Func> worker, T1 arg1, T2 arg2, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCoreAsync(rules, TaskFuncFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task> ForResultAsync(ForLoopRuleset rules, Func> worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCoreAsync(rules, TaskFuncFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task> ForResultAsync(ForLoopRuleset rules, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCoreAsync(rules, TaskFuncFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the number used with the loop control variable. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the fifth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The rules of a for-loop control flow statement. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The fifth parameter of the function delegate . + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static Task> ForResultAsync(ForLoopRuleset rules, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + where TOperand : struct, IComparable, IEquatable, IConvertible + { + Validator.ThrowIfNull(rules, nameof(rules)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForResultCoreAsync(rules, TaskFuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static async Task> ForResultCoreAsync(ForLoopRuleset rules, TaskFuncFactory workerFactory, Action setup) + where TWorker : Template + where TOperand : struct, IComparable, IEquatable, IConvertible + { + var from = rules.From; + var options = Patterns.Configure(setup); + var result = new ConcurrentDictionary(); + TOperand processed = default; + + while (true) + { + var workChunks = options.PartitionSize; + var queue = new Dictionary>(); + for (var i = from; rules.Condition(i, rules.Relation, rules.To); i = rules.Iterator(i, rules.Assignment, rules.Step)) + { + workerFactory.GenericArguments.Arg1 = i; + queue.Add(i, workerFactory.ExecuteMethodAsync(options.CancellationToken)); + + processed = i; + workChunks--; + + if (workChunks == 0) { break; } + } + from = Calculator.Calculate(processed, rules.Assignment, rules.Step); + if (queue.Count == 0) { break; } + await Task.WhenAll(queue.Values).ConfigureAwait(false); + foreach (var item in queue) { result.TryAdd(item.Key, item.Value.Result); } + } + return new ReadOnlyCollection(result.Values.ToList()); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/AdvancedParallelFactory.While.cs b/src/Cuemon.Threading/AdvancedParallelFactory.While.cs new file mode 100644 index 000000000..4d5ec1db8 --- /dev/null +++ b/src/Cuemon.Threading/AdvancedParallelFactory.While.cs @@ -0,0 +1,151 @@ +using System; + +namespace Cuemon.Threading +{ + public static partial class AdvancedParallelFactory + { + /// + /// Executes a parallel while loop. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The which may be configured. + public static void While(TReader reader, Func condition, Func provider, Action worker, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + WhileCore(new ForwardIterator(reader, condition, provider), ActionFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel while loop. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the parameter of the delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The parameter of the delegate . + /// The which may be configured. + public static void While(TReader reader, Func condition, Func provider, Action worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + WhileCore(new ForwardIterator(reader, condition, provider), ActionFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel while loop. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The which may be configured. + public static void While(TReader reader, Func condition, Func provider, Action worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + WhileCore(new ForwardIterator(reader, condition, provider), ActionFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel while loop. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The which may be configured. + public static void While(TReader reader, Func condition, Func provider, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + WhileCore(new ForwardIterator(reader, condition, provider), ActionFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel while loop. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The which may be configured. + public static void While(TReader reader, Func condition, Func provider, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + WhileCore(new ForwardIterator(reader, condition, provider), ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel while loop. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The fifth parameter of the delegate . + /// The which may be configured. + public static void While(TReader reader, Func condition, Func provider, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + WhileCore(new ForwardIterator(reader, condition, provider), ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static void WhileCore(ForwardIterator iterator, ActionFactory workerFactory, Action setup) + where TWorker : Template + { + new ActionWhileSynchronousLoop(iterator, setup).PrepareExecution(workerFactory); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.WhileAsync.cs b/src/Cuemon.Threading/AdvancedParallelFactory.WhileAsync.cs similarity index 75% rename from src/Cuemon.Threading/ParallelFactory.WhileAsync.cs rename to src/Cuemon.Threading/AdvancedParallelFactory.WhileAsync.cs index 032cd47cb..14c769ca6 100644 --- a/src/Cuemon.Threading/ParallelFactory.WhileAsync.cs +++ b/src/Cuemon.Threading/AdvancedParallelFactory.WhileAsync.cs @@ -1,15 +1,11 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Cuemon.Threading { - /// - /// Provides a factory based way to encapsulate and re-use existing code while adding support for typically long-running parallel loops and regions. - /// - public static partial class ParallelFactory + public static partial class AdvancedParallelFactory { /// /// Executes a parallel while loop. @@ -20,15 +16,14 @@ public static partial class ParallelFactory /// The function delegate that is responsible for the while loop condition. /// The function delegate that provides data from the specified . /// The delegate that will perform work while evaluates true. - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task WhileAsync(TReader reader, Func> condition, Func provider, Action worker, Action setup = null) + public static Task WhileAsync(TReader reader, Func> condition, Func provider, Func worker, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default); - return WhileCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskActionFactory.Create(worker, default), setup); } /// @@ -42,15 +37,14 @@ public static Task WhileAsync(TReader reader, Func /// The function delegate that provides data from the specified . /// The delegate that will perform work while evaluates true. /// The parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task WhileAsync(TReader reader, Func> condition, Func provider, Action worker, T arg, Action setup = null) + public static Task WhileAsync(TReader reader, Func> condition, Func provider, Func worker, T arg, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg); - return WhileCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskActionFactory.Create(worker, default, arg), setup); } /// @@ -66,15 +60,14 @@ public static Task WhileAsync(TReader reader, FuncThe delegate that will perform work while evaluates true. /// The first parameter of the delegate . /// The second parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task WhileAsync(TReader reader, Func> condition, Func provider, Action worker, T1 arg1, T2 arg2, Action setup = null) + public static Task WhileAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2); - return WhileCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskActionFactory.Create(worker, default, arg1, arg2), setup); } /// @@ -92,15 +85,14 @@ public static Task WhileAsync(TReader reader, FuncThe first parameter of the delegate . /// The second parameter of the delegate . /// The third parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task WhileAsync(TReader reader, Func> condition, Func provider, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + public static Task WhileAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3); - return WhileCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskActionFactory.Create(worker, default, arg1, arg2, arg3), setup); } /// @@ -120,15 +112,14 @@ public static Task WhileAsync(TReader reader, Fun /// The second parameter of the delegate . /// The third parameter of the delegate . /// The fourth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task WhileAsync(TReader reader, Func> condition, Func provider, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + public static Task WhileAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4); - return WhileCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskActionFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); } /// @@ -150,52 +141,37 @@ public static Task WhileAsync(TReader reader, /// The third parameter of the delegate . /// The fourth parameter of the delegate . /// The fifth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task WhileAsync(TReader reader, Func> condition, Func provider, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + public static Task WhileAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5); - return WhileCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); } - private static async Task WhileCoreAsync(ForwardIterator iterator, ActionFactory workerFactory, Action setup) + private static async Task WhileCoreAsync(AsyncForwardIterator iterator, TaskActionFactory workerFactory, Action setup) where TWorker : Template { var options = Patterns.Configure(setup); - var exceptions = new ConcurrentBag(); var readForward = true; - + while (true) { var workChunks = options.PartitionSize; var queue = new List(); - while (workChunks > 1 && readForward) + while (workChunks > 0 && readForward) { readForward = await iterator.ReadAsync().ConfigureAwait(false); if (!readForward) { break; } - var shallowWorkerFactory = workerFactory.Clone(); - queue.Add(Task.Factory.StartNew(element => - { - try - { - Interlocked.Decrement(ref workChunks); - shallowWorkerFactory.GenericArguments.Arg1 = (TElement)element; - shallowWorkerFactory.ExecuteMethod(); - } - catch (Exception e) - { - exceptions.Add(e); - } - }, iterator.Current, options.CancellationToken, options.CreationOptions, options.Scheduler)); + workerFactory.GenericArguments.Arg1 = iterator.Current; + queue.Add(workerFactory.ExecuteMethodAsync(options.CancellationToken)); + workChunks--; } if (queue.Count == 0) { break; } await Task.WhenAll(queue).ConfigureAwait(false); - if (workChunks > 1) { break; } } - if (exceptions.Count > 0) { throw new AggregateException(exceptions); } } } } \ No newline at end of file diff --git a/src/Cuemon.Threading/AdvancedParallelFactory.WhileResult.cs b/src/Cuemon.Threading/AdvancedParallelFactory.WhileResult.cs new file mode 100644 index 000000000..a5f4293a4 --- /dev/null +++ b/src/Cuemon.Threading/AdvancedParallelFactory.WhileResult.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; + +namespace Cuemon.Threading +{ + public static partial class AdvancedParallelFactory + { + /// + /// Executes a parallel while loop where the return value of the function delegate is stored in the same order as the while loop evaluates true. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the return value of the function delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same order as the while loop evaluates true. + public static IReadOnlyCollection WhileResult(TReader reader, Func condition, Func provider, Func worker, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + return WhileResultCore(new ForwardIterator(reader, condition, provider), FuncFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel while loop where the return value of the function delegate is stored in the same order as the while loop evaluates true. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the parameter of the delegate . + /// The type of the return value of the function delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The parameter of the delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same order as the while loop evaluates true. + public static IReadOnlyCollection WhileResult(TReader reader, Func condition, Func provider, Func worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + return WhileResultCore(new ForwardIterator(reader, condition, provider), FuncFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel while loop where the return value of the function delegate is stored in the same order as the while loop evaluates true. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the return value of the function delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same order as the while loop evaluates true. + public static IReadOnlyCollection WhileResult(TReader reader, Func condition, Func provider, Func worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + return WhileResultCore(new ForwardIterator(reader, condition, provider), FuncFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel while loop where the return value of the function delegate is stored in the same order as the while loop evaluates true. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the return value of the function delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same order as the while loop evaluates true. + public static IReadOnlyCollection WhileResult(TReader reader, Func condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + return WhileResultCore(new ForwardIterator(reader, condition, provider), FuncFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel while loop where the return value of the function delegate is stored in the same order as the while loop evaluates true. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the return value of the function delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same order as the while loop evaluates true. + public static IReadOnlyCollection WhileResult(TReader reader, Func condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + return WhileResultCore(new ForwardIterator(reader, condition, provider), FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel while loop where the return value of the function delegate is stored in the same order as the while loop evaluates true. + /// + /// The type of the that provides forward-only access to data. + /// The type of the result provided by . + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The type of the return value of the function delegate . + /// The reader that provides forward-only access to data. + /// The function delegate that is responsible for the while loop condition. + /// The function delegate that provides data from the specified . + /// The delegate that will perform work while evaluates true. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The fifth parameter of the delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same order as the while loop evaluates true. + public static IReadOnlyCollection WhileResult(TReader reader, Func condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(condition, nameof(condition)); + Validator.ThrowIfNull(provider, nameof(provider)); + Validator.ThrowIfNull(worker, nameof(worker)); + return WhileResultCore(new ForwardIterator(reader, condition, provider), FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static IReadOnlyCollection WhileResultCore(ForwardIterator iterator, FuncFactory workerFactory, Action setup) + where TWorker : Template + { + return new FuncWhileSynchronousLoop(iterator, setup).GetResult(workerFactory); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.WhileResultAsync.cs b/src/Cuemon.Threading/AdvancedParallelFactory.WhileResultAsync.cs similarity index 79% rename from src/Cuemon.Threading/ParallelFactory.WhileResultAsync.cs rename to src/Cuemon.Threading/AdvancedParallelFactory.WhileResultAsync.cs index 0fdc25ea0..f2cfdfa94 100644 --- a/src/Cuemon.Threading/ParallelFactory.WhileResultAsync.cs +++ b/src/Cuemon.Threading/AdvancedParallelFactory.WhileResultAsync.cs @@ -8,7 +8,7 @@ namespace Cuemon.Threading { - public static partial class ParallelFactory + public static partial class AdvancedParallelFactory { /// /// Executes a parallel while loop where the return value of the function delegate is stored in the same order as the while loop evaluates true. @@ -20,16 +20,15 @@ public static partial class ParallelFactory /// The function delegate that is responsible for the while loop condition. /// The function delegate that provides data from the specified . /// The delegate that will perform work while evaluates true. - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same order as the while loop evaluates true. - public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func worker, Action setup = null) + public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func> worker, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default); - return WhileResultCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileResultCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskFuncFactory.Create(worker, default), setup); } /// @@ -44,16 +43,15 @@ public static Task> WhileResultAsyncThe function delegate that provides data from the specified . /// The delegate that will perform work while evaluates true. /// The parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same order as the while loop evaluates true. - public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func worker, T arg, Action setup = null) + public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func> worker, T arg, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg); - return WhileResultCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileResultCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskFuncFactory.Create(worker, default, arg), setup); } /// @@ -70,16 +68,15 @@ public static Task> WhileResultAsyncThe delegate that will perform work while evaluates true. /// The first parameter of the delegate . /// The second parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same order as the while loop evaluates true. - public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, Action setup = null) + public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func> worker, T1 arg1, T2 arg2, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2); - return WhileResultCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileResultCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskFuncFactory.Create(worker, default, arg1, arg2), setup); } /// @@ -98,16 +95,15 @@ public static Task> WhileResultAsyncThe first parameter of the delegate . /// The second parameter of the delegate . /// The third parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same order as the while loop evaluates true. - public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func> worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3); - return WhileResultCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileResultCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskFuncFactory.Create(worker, default, arg1, arg2, arg3), setup); } /// @@ -128,16 +124,15 @@ public static Task> WhileResultAsyncThe second parameter of the delegate . /// The third parameter of the delegate . /// The fourth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same order as the while loop evaluates true. - public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4); - return WhileResultCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileResultCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskFuncFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); } /// @@ -160,61 +155,43 @@ public static Task> WhileResultAsyncThe third parameter of the delegate . /// The fourth parameter of the delegate . /// The fifth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same order as the while loop evaluates true. - public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + public static Task> WhileResultAsync(TReader reader, Func> condition, Func provider, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { Validator.ThrowIfNull(condition, nameof(condition)); Validator.ThrowIfNull(provider, nameof(provider)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5); - return WhileResultCoreAsync(new ForwardIterator(reader, condition, provider), wf, setup); + return WhileResultCoreAsync(new AsyncForwardIterator(reader, condition, provider), TaskFuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); } - private static async Task> WhileResultCoreAsync(ForwardIterator iterator, FuncFactory workerFactory, Action setup) + private static async Task> WhileResultCoreAsync(AsyncForwardIterator iterator, TaskFuncFactory workerFactory, Action setup) where TWorker : Template { var options = Patterns.Configure(setup); - var exceptions = new ConcurrentBag(); var result = new ConcurrentDictionary(); var readForward = true; - var sorter = long.MinValue; + long sorter = 0; while (true) { var workChunks = options.PartitionSize; - var queue = new List(); - while (workChunks > 1 && readForward) + var queue = new Dictionary>(); + while (workChunks > 0 && readForward) { readForward = await iterator.ReadAsync().ConfigureAwait(false); if (!readForward) { break; } - - var shallowWorkerFactory = workerFactory.Clone(); - var current = sorter; - queue.Add(Task.Factory.StartNew(element => - { - try - { - Interlocked.Decrement(ref workChunks); - shallowWorkerFactory.GenericArguments.Arg1 = (TElement)element; - var presult = shallowWorkerFactory.ExecuteMethod(); - result.TryAdd(current, presult); - } - catch (Exception e) - { - exceptions.Add(e); - } - }, iterator.Current, options.CancellationToken, options.CreationOptions, options.Scheduler)); - + workerFactory.GenericArguments.Arg1 = iterator.Current; + queue.Add(sorter, workerFactory.ExecuteMethodAsync(options.CancellationToken)); + workChunks--; sorter++; } if (queue.Count == 0) { break; } - await Task.WhenAll(queue).ConfigureAwait(false); - if (workChunks > 1) { break; } + await Task.WhenAll(queue.Values).ConfigureAwait(false); + foreach (var item in queue) { result.TryAdd(item.Key, item.Value.Result); } } - if (exceptions.Count > 0) { throw new AggregateException(exceptions); } return new ReadOnlyCollection(result.Values.ToList()); } } -} +} \ No newline at end of file diff --git a/src/Cuemon.Threading/AdvancedParallelFactory.cs b/src/Cuemon.Threading/AdvancedParallelFactory.cs new file mode 100644 index 000000000..bc804c8bf --- /dev/null +++ b/src/Cuemon.Threading/AdvancedParallelFactory.cs @@ -0,0 +1,55 @@ +using System; + +namespace Cuemon.Threading +{ + /// + /// Provides a factory based way to work with advanced scenarios that encapsulate and re-use existing code while adding support for typically long-running parallel loops and regions. + /// + /// + public static partial class AdvancedParallelFactory + { + /// + /// Provides a default implementation of a for-iterator callback method. + /// + /// The type of the counter in a for-loop. + /// The current value of the counter in a for-loop. + /// One of the enumeration values that specifies the rules to apply as the assignment operator for left-hand operand and right-hand operand . + /// The value to assign to according to the rule specified by . + /// The computed result of having the of . + public static T Iterator(T current, AssignmentOperator assignment, T step) where T : struct, IComparable, IEquatable, IConvertible + { + Calculator.ValidAsNumericOperand(); + return Calculator.Calculate(current, assignment, step); + } + + /// + /// Provides a default implementation of a for-condition callback method. + /// + /// The type of the counter in a for-loop. + /// The current value of the counter in a for-loop. + /// One of the enumeration values that specifies the rules to apply as the relational operator for left-hand operand and right-hand operand . + /// The amount of repeats to do according to the rules specified by . + /// true if does not meet the condition of and ; otherwise false. + public static bool Condition(T current, RelationalOperator relational, T repeats) where T : struct, IComparable, IEquatable, IConvertible + { + Calculator.ValidAsNumericOperand(); + switch (relational) + { + case RelationalOperator.Equal: + return current.Equals(repeats); + case RelationalOperator.GreaterThan: + return current.CompareTo(repeats) > 0; + case RelationalOperator.GreaterThanOrEqual: + return current.CompareTo(repeats) >= 0; + case RelationalOperator.LessThan: + return current.CompareTo(repeats) < 0; + case RelationalOperator.LessThanOrEqual: + return current.CompareTo(repeats) <= 0; + case RelationalOperator.NotEqual: + return !current.Equals(repeats); + default: + throw new ArgumentOutOfRangeException(nameof(relational)); + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/AsyncForwardIterator.cs b/src/Cuemon.Threading/AsyncForwardIterator.cs new file mode 100644 index 000000000..56ecca68a --- /dev/null +++ b/src/Cuemon.Threading/AsyncForwardIterator.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + internal class AsyncForwardIterator + { + internal AsyncForwardIterator(TReader reader, Func> condition, Func provider) + { + Reader = reader; + ConditionAsync = condition; + Provider = provider; + } + + private TReader Reader { get; } + + private Func> ConditionAsync { get; } + + private Func Provider { get; } + + public TElement Current { get; private set; } + + public async Task ReadAsync() + { + if (await ConditionAsync().ConfigureAwait(false)) + { + Current = Provider(Reader); + return true; + } + Current = default; + return false; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Threading/AsyncOptions.cs b/src/Cuemon.Threading/AsyncOptions.cs similarity index 100% rename from src/Cuemon.Core/Threading/AsyncOptions.cs rename to src/Cuemon.Threading/AsyncOptions.cs diff --git a/src/Cuemon.Threading/TaskFactoryOptions.cs b/src/Cuemon.Threading/AsyncTaskFactoryOptions.cs similarity index 72% rename from src/Cuemon.Threading/TaskFactoryOptions.cs rename to src/Cuemon.Threading/AsyncTaskFactoryOptions.cs index cf0cbc0cf..5c42402f8 100644 --- a/src/Cuemon.Threading/TaskFactoryOptions.cs +++ b/src/Cuemon.Threading/AsyncTaskFactoryOptions.cs @@ -4,22 +4,22 @@ namespace Cuemon.Threading { /// - /// Configuration options for . + /// Configuration options for . /// - public class TaskFactoryOptions : AsyncOptions + public class AsyncTaskFactoryOptions : AsyncWorkloadOptions { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The following table shows the initial property values for an instance of . + /// The following table shows the initial property values for an instance of . /// /// /// Property /// Initial Value /// /// - /// + /// /// 2 x /// /// @@ -32,19 +32,12 @@ public class TaskFactoryOptions : AsyncOptions /// /// /// - public TaskFactoryOptions() + public AsyncTaskFactoryOptions() { CreationOptions = TaskCreationOptions.LongRunning; Scheduler = TaskScheduler.Current; - PartitionSize = 2 * Environment.ProcessorCount; } - /// - /// Gets or sets the size of the partition to allocate work to a set of tasks. - /// - /// The size of the partition to allocate work to a set of tasks. - public int PartitionSize { get; set; } - /// /// Gets or sets the used to create the task. /// diff --git a/src/Cuemon.Threading/AsyncWorkloadOptions.cs b/src/Cuemon.Threading/AsyncWorkloadOptions.cs new file mode 100644 index 000000000..43781d81a --- /dev/null +++ b/src/Cuemon.Threading/AsyncWorkloadOptions.cs @@ -0,0 +1,37 @@ +using System; + +namespace Cuemon.Threading +{ + /// + /// Configuration options for . + /// + public class AsyncWorkloadOptions : AsyncOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// 2 x + /// + /// + /// + public AsyncWorkloadOptions() + { + PartitionSize = 2 * Environment.ProcessorCount; + } + + /// + /// Gets or sets the size of the partition to allocate work to a set of tasks. + /// + /// The size of the partition to allocate work to a set of tasks. + public int PartitionSize { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/Cuemon.Threading.csproj b/src/Cuemon.Threading/Cuemon.Threading.csproj index 7a5ab6279..ab18ddbf3 100644 --- a/src/Cuemon.Threading/Cuemon.Threading.csproj +++ b/src/Cuemon.Threading/Cuemon.Threading.csproj @@ -1,26 +1,15 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 150bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Threading Cuemon.Threading - The Cuemon.Threading assembly provides access to abstractions related to the System.Threading namespace. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Threading namespace contains types related to working with long-running concurrent loops and regions that utilizes both synchronous and asynchronous delegates. The namespace is an addition to the System.Threading namespace. + advanced-parallel-factory parallel-factory for-async for-each-async for-each-result-async for-result-async while-async while-result-async async-options timer-factory for-loop-ruleset diff --git a/src/Cuemon.Threading/ForEachSynchronousLoop.cs b/src/Cuemon.Threading/ForEachSynchronousLoop.cs new file mode 100644 index 000000000..695df0245 --- /dev/null +++ b/src/Cuemon.Threading/ForEachSynchronousLoop.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Cuemon.Collections.Generic; + +namespace Cuemon.Threading +{ + internal abstract class ForEachSynchronousLoop : SynchronousLoop + { + protected ForEachSynchronousLoop(IEnumerable source, Action setup) : base(setup) + { + Partitioner = new PartitionerEnumerable(source, Options.PartitionSize); + WhileCondition = () => Partitioner.HasPartitions; + Sorter = 0; + } + + private PartitionerEnumerable Partitioner { get; set; } + + private long Sorter { get; set; } + + protected sealed override void FillWorkQueue(TemplateFactory worker, IList> queue) + { + foreach (var item in Partitioner) + { + var shallowWorkerFactory = worker.Clone(); + shallowWorkerFactory.GenericArguments.Arg1 = item; + var current = Sorter; + queue.Add(() => Task.Factory.StartNew(swf => + { + try + { + FillWorkQueueWorkerFactory(swf as TemplateFactory, current); + } + catch (Exception e) + { + Exceptions.Add(e); + } + }, shallowWorkerFactory, Options.CancellationToken, Options.CreationOptions, Options.Scheduler)); + Sorter++; + } + } + + protected abstract void FillWorkQueueWorkerFactory(TemplateFactory worker, long sorter) where TWorker : Template; + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ForLoopRuleset.cs b/src/Cuemon.Threading/ForLoopRuleset.cs new file mode 100644 index 000000000..e4f40e180 --- /dev/null +++ b/src/Cuemon.Threading/ForLoopRuleset.cs @@ -0,0 +1,92 @@ +using System; + +namespace Cuemon.Threading +{ + + /// + /// Specifies the rules of a for-loop control flow statement. + /// + /// The type of the number used with the loop control variable. + public class ForLoopRuleset where TOperand : struct, IComparable, IEquatable, IConvertible + { + /// + /// Initializes a new instance of the class. + /// + /// + /// is outside the range of allowed types.
+ /// Allowed types are: , , , , , , , , , or . + ///
+ public ForLoopRuleset() + { + Calculator.ValidAsNumericOperand(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The rules of a for-loop control flow statement. + /// The conditional value of the loop control variable. + /// The value to assign the loop control variable. + /// The relation between the loop control variable and . + /// The assignment statement of the loop control variable using . + /// The function delegate that represents the condition section of the for loop. Default value is . + /// The function delegate that represents the iterator section of the for loop. Default value is . + /// + /// is outside the range of allowed types.
+ /// Allowed types are: , , , , , , , , , or . + ///
+ public ForLoopRuleset(TOperand from, TOperand to, TOperand step, RelationalOperator relation = RelationalOperator.LessThan, AssignmentOperator assignment = AssignmentOperator.Addition, Func condition = null, Func iterator = null) + { + Calculator.ValidAsNumericOperand(); + From = from; + To = to; + Step = step; + Relation = relation; + Assignment = assignment; + Condition = condition ?? AdvancedParallelFactory.Condition; + Iterator = iterator ?? AdvancedParallelFactory.Iterator; + } + + /// + /// Gets or sets the initial value of the loop control variable. + /// + /// The initial value of the loop control variable. + public TOperand From { get; set; } + + /// + /// Gets or sets the relation between the loop control variable and . + /// + /// The relation between the loop control variable. + public RelationalOperator Relation { get; set; } + + /// + /// Gets or sets the conditional value of the loop control variable. + /// + /// The conditional value of the loop control variable. + public TOperand To { get; set; } + + /// + /// Gets or sets the assignment statement of the loop control variable using . + /// + /// The assignment statement of the loop control variable. + public AssignmentOperator Assignment { get; set; } + + /// + /// Gets or sets the number to assign the loop control variable. + /// + /// The number to assign the loop control variable. + public TOperand Step { get; set; } + + /// + /// Gets or sets the function delegate of a for-condition. + /// + /// The function delegate of a for-condition. + public Func Condition { get; set; } + + /// + /// Gets or sets the function delegate of a for-iterator. + /// + /// The function delegate of a for-iterator. + public Func Iterator { get; set; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ForSynchronousLoop.cs b/src/Cuemon.Threading/ForSynchronousLoop.cs new file mode 100644 index 000000000..9b1dce175 --- /dev/null +++ b/src/Cuemon.Threading/ForSynchronousLoop.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + internal abstract class ForSynchronousLoop : SynchronousLoop where TOperand : struct, IComparable, IEquatable, IConvertible + { + protected ForSynchronousLoop(ForLoopRuleset rules, Action setup) : base(setup) + { + Rules = rules; + From = rules.From; + WhileCondition = () => true; + } + + protected TOperand From { get; set; } + + protected ForLoopRuleset Rules { get; } + + protected TOperand Processed { get; set; } + + protected int WorkChunks { get; set; } + + protected sealed override void FillWorkQueue(TemplateFactory worker, IList> queue) + { + for (var i = From; Rules.Condition(i, Rules.Relation, Rules.To); i = Rules.Iterator(i, Rules.Assignment, Rules.Step)) + { + var shallowWorkerFactory = worker.Clone(); + shallowWorkerFactory.GenericArguments.Arg1 = i; + queue.Add(() => Task.Factory.StartNew(swf => + { + try + { + FillWorkQueueWorkerFactory(swf as TemplateFactory); + } + catch (Exception e) + { + Exceptions.Add(e); + } + }, shallowWorkerFactory, Options.CancellationToken, Options.CreationOptions, Options.Scheduler)); + + Processed = i; + WorkChunks--; + + if (WorkChunks == 0) { break; } + } + From = Calculator.Calculate(Processed, Rules.Assignment, Rules.Step); + } + + protected abstract void FillWorkQueueWorkerFactory(TemplateFactory worker) where TWorker : Template; + + protected sealed override void OnWhileExecutingBeforeFillWorkQueue() + { + WorkChunks = Options.PartitionSize; + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ForwardIterator.cs b/src/Cuemon.Threading/ForwardIterator.cs index 838f08630..9539ee375 100644 --- a/src/Cuemon.Threading/ForwardIterator.cs +++ b/src/Cuemon.Threading/ForwardIterator.cs @@ -1,11 +1,10 @@ using System; -using System.Threading.Tasks; namespace Cuemon.Threading { internal class ForwardIterator { - internal ForwardIterator(TReader reader, Func> condition, Func provider) + internal ForwardIterator(TReader reader, Func condition, Func provider) { Reader = reader; Condition = condition; @@ -14,15 +13,15 @@ internal ForwardIterator(TReader reader, Func> condition, Func> Condition { get; } + private Func Condition { get; } private Func Provider { get; } public TElement Current { get; private set; } - public async Task ReadAsync() + public bool Read() { - if (await Condition().ConfigureAwait(false)) + if (Condition()) { Current = Provider(Reader); return true; diff --git a/src/Cuemon.Threading/FuncForEachSynchronousLoop.cs b/src/Cuemon.Threading/FuncForEachSynchronousLoop.cs new file mode 100644 index 000000000..f8e7fba97 --- /dev/null +++ b/src/Cuemon.Threading/FuncForEachSynchronousLoop.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Cuemon.Threading +{ + internal sealed class FuncForEachSynchronousLoop : ForEachSynchronousLoop + { + public FuncForEachSynchronousLoop(IEnumerable source, Action setup) : base(source, setup) + { + } + + protected override void FillWorkQueueWorkerFactory(TemplateFactory worker, long sorter) + { + if (worker is FuncFactory wf) + { + var presult = wf.ExecuteMethod(); + Result.TryAdd(sorter, presult); + } + } + + private ConcurrentDictionary Result { get; } = new ConcurrentDictionary(); + + public IReadOnlyCollection GetResult(TemplateFactory worker) where TWorker : Template + { + PrepareExecution(worker); + return new ReadOnlyCollection(Result.Values.ToList()); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/FuncForSynchronousLoop.cs b/src/Cuemon.Threading/FuncForSynchronousLoop.cs new file mode 100644 index 000000000..f3624575f --- /dev/null +++ b/src/Cuemon.Threading/FuncForSynchronousLoop.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Cuemon.Threading +{ + internal sealed class FuncForSynchronousLoop : ForSynchronousLoop where TOperand : struct, IComparable, IEquatable, IConvertible + { + public FuncForSynchronousLoop(ForLoopRuleset rules, Action setup) : base(rules, setup) + { + } + + private ConcurrentDictionary Result { get; } = new ConcurrentDictionary(); + + + protected override void FillWorkQueueWorkerFactory(TemplateFactory worker) + { + if (worker is FuncFactory wf) + { + var presult = wf.ExecuteMethod(); + Result.TryAdd(wf.GenericArguments.Arg1, presult); + } + } + + public IReadOnlyCollection GetResult(TemplateFactory worker) where TWorker : Template + { + PrepareExecution(worker); + return new ReadOnlyCollection(Result.Values.ToList()); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/FuncWhileSynchronousLoop.cs b/src/Cuemon.Threading/FuncWhileSynchronousLoop.cs new file mode 100644 index 000000000..8f9b2b29a --- /dev/null +++ b/src/Cuemon.Threading/FuncWhileSynchronousLoop.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Cuemon.Threading +{ + internal sealed class FuncWhileSynchronousLoop : WhileSynchronousLoop + { + public FuncWhileSynchronousLoop(ForwardIterator iterator, Action setup) : base(iterator, setup) + { + } + + private ConcurrentDictionary Result { get; } = new ConcurrentDictionary(); + + protected override void FillWorkQueueWorkerFactory(TemplateFactory worker, long sorter) + { + if (worker is FuncFactory wf) + { + var presult = wf.ExecuteMethod(); + Result.TryAdd(sorter, presult); + } + } + + public IReadOnlyCollection GetResult(TemplateFactory worker) where TWorker : Template + { + PrepareExecution(worker); + return new ReadOnlyCollection(Result.Values.ToList()); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/GlobalSuppressions.cs b/src/Cuemon.Threading/GlobalSuppressions.cs new file mode 100644 index 000000000..813a627fc --- /dev/null +++ b/src/Cuemon.Threading/GlobalSuppressions.cs @@ -0,0 +1,112 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.For``4(Cuemon.Threading.ForLoopRuleset{``0},System.Action{``0,``1,``2,``3},``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.For``5(Cuemon.Threading.ForLoopRuleset{``0},System.Action{``0,``1,``2,``3,``4},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.For``6(Cuemon.Threading.ForLoopRuleset{``0},System.Action{``0,``1,``2,``3,``4,``5},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.For``6(Cuemon.Threading.ForLoopRuleset{``0},System.Action{``0,``1,``2,``3,``4,``5},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForAsync``4(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForAsync``5(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForAsync``6(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForAsync``6(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResult``4(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3},``1,``2,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResult``5(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4},``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResult``6(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,``5},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResult``7(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,``5,``6},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResult``7(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,``5,``6},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResultAsync``4(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``1,``2,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResultAsync``5(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResultAsync``6(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResultAsync``7(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.ForResultAsync``7(Cuemon.Threading.ForLoopRuleset{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.While``4(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Action{``1,``2,``3},``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.While``5(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Action{``1,``2,``3,``4},``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.While``5(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Action{``1,``2,``3,``4},``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.While``6(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Action{``1,``2,``3,``4,``5},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.While``6(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Action{``1,``2,``3,``4,``5},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.While``7(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Action{``1,``2,``3,``4,``5,``6},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.While``7(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Action{``1,``2,``3,``4,``5,``6},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileAsync``4(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileAsync``5(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileAsync``5(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileAsync``6(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileAsync``6(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileAsync``7(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileAsync``7(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``4(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3},``2,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``5(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3,``4},``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``6(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5},``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``6(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5},``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``7(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``7(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``8(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6,``7},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``7}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResult``8(``0,System.Func{System.Boolean},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6,``7},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``7}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``4(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``2,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``5(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``6(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``6(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``7(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``7(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``8(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task{``7}},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultAsync``8(``0,System.Func{System.Threading.Tasks.Task{System.Boolean}},System.Func{``0,``1},System.Func{``1,``2,``3,``4,``5,``6,System.Threading.CancellationToken,System.Threading.Tasks.Task{``7}},``2,``3,``4,``5,``6,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``7}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultCore``4(Cuemon.Threading.ForwardIterator{``0,``1},Cuemon.FuncFactory{``2,``3},System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.AdvancedParallelFactory.WhileResultCoreAsync``4(Cuemon.Threading.AsyncForwardIterator{``0,``1},Cuemon.TaskFuncFactory{``2,``3},System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``4(System.Int32,System.Int32,System.Action{System.Int32,``0,``1,``2,``3},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``4(System.Int32,System.Int32,System.Action{System.Int32,``0,``1,``2,``3},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``4(System.Int64,System.Int64,System.Action{System.Int64,``0,``1,``2,``3},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``4(System.Int64,System.Int64,System.Action{System.Int64,``0,``1,``2,``3},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``5(System.Int32,System.Int32,System.Action{System.Int32,``0,``1,``2,``3,``4},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``5(System.Int32,System.Int32,System.Action{System.Int32,``0,``1,``2,``3,``4},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``5(System.Int64,System.Int64,System.Action{System.Int64,``0,``1,``2,``3,``4},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.For``5(System.Int64,System.Int64,System.Action{System.Int64,``0,``1,``2,``3,``4},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``4(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``4(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``4(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``4(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``5(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``5(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``5(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForAsync``5(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEach``4(System.Collections.Generic.IEnumerable{``0},System.Action{``0,``1,``2,``3},``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEach``5(System.Collections.Generic.IEnumerable{``0},System.Action{``0,``1,``2,``3,``4},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEach``6(System.Collections.Generic.IEnumerable{``0},System.Action{``0,``1,``2,``3,``4,``5},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEach``6(System.Collections.Generic.IEnumerable{``0},System.Action{``0,``1,``2,``3,``4,``5},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachAsync``4(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachAsync``5(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachAsync``6(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachAsync``6(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResult``4(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3},``1,``2,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResult``5(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4},``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResult``6(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,``5},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResult``7(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,``5,``6},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResult``7(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,``5,``6},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``6}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResultAsync``4(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``1,``2,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResultAsync``5(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResultAsync``6(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResultAsync``7(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForEachResultAsync``7(System.Collections.Generic.IEnumerable{``0},System.Func{``0,``1,``2,``3,``4,``5,System.Threading.CancellationToken,System.Threading.Tasks.Task{``6}},``1,``2,``3,``4,``5,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``6}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``4(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3},``0,``1,``2,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``4(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3},``0,``1,``2,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``3}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``5(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``5(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``5(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``5(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``4}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``6(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``6(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``6(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResult``6(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4,``5},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncTaskFactoryOptions})~System.Collections.Generic.IReadOnlyCollection{``5}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``4(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``0,``1,``2,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``4(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,System.Threading.CancellationToken,System.Threading.Tasks.Task{``3}},``0,``1,``2,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``3}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``5(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``5(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``5(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``5(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,System.Threading.CancellationToken,System.Threading.Tasks.Task{``4}},``0,``1,``2,``3,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``4}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``6(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``6(System.Int32,System.Int32,System.Func{System.Int32,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``6(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "member", Target = "~M:Cuemon.Threading.ParallelFactory.ForResultAsync``6(System.Int64,System.Int64,System.Func{System.Int64,``0,``1,``2,``3,``4,System.Threading.CancellationToken,System.Threading.Tasks.Task{``5}},``0,``1,``2,``3,``4,System.Action{Cuemon.Threading.AsyncWorkloadOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{``5}}")] +[assembly: SuppressMessage("Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "By design; allow up till 5 generic arguments.", Scope = "type", Target = "~T:Cuemon.Threading.FuncWhileSynchronousLoop`3")] diff --git a/src/Cuemon.Threading/Loop.cs b/src/Cuemon.Threading/Loop.cs new file mode 100644 index 000000000..3d1d00f2f --- /dev/null +++ b/src/Cuemon.Threading/Loop.cs @@ -0,0 +1,14 @@ +using System; + +namespace Cuemon.Threading +{ + internal abstract class Loop where TOptions : AsyncOptions, new() + { + protected Loop(Action setup) + { + Options = Patterns.Configure(setup); + } + + protected TOptions Options { get; } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.For.cs b/src/Cuemon.Threading/ParallelFactory.For.cs new file mode 100644 index 000000000..5016a51eb --- /dev/null +++ b/src/Cuemon.Threading/ParallelFactory.For.cs @@ -0,0 +1,223 @@ +using System; + +namespace Cuemon.Threading +{ + public static partial class ParallelFactory + { + /// + /// Executes a parallel for loop. + /// + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + public static void For(int fromInclusive, int toExclusive, Action worker, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The parameter of the delegate . + /// The which may be configured. + public static void For(int fromInclusive, int toExclusive, Action worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The which may be configured. + public static void For(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The which may be configured. + public static void For(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The which may be configured. + public static void For(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The fifth parameter of the delegate . + /// The which may be configured. + public static void For(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + public static void For(long fromInclusive, long toExclusive, Action worker, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup: setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The parameter of the delegate . + /// The which may be configured. + public static void For(long fromInclusive, long toExclusive, Action worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup: setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The which may be configured. + public static void For(long fromInclusive, long toExclusive, Action worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup: setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The which may be configured. + public static void For(long fromInclusive, long toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup: setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The which may be configured. + public static void For(long fromInclusive, long toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup: setup); + } + + /// + /// Executes a parallel for loop. + /// + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The fifth parameter of the delegate . + /// The which may be configured. + public static void For(long fromInclusive, long toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + AdvancedParallelFactory.For(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup: setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.ForAsync.cs b/src/Cuemon.Threading/ParallelFactory.ForAsync.cs index aef84e397..1757d24ec 100644 --- a/src/Cuemon.Threading/ParallelFactory.ForAsync.cs +++ b/src/Cuemon.Threading/ParallelFactory.ForAsync.cs @@ -1,68 +1,23 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Cuemon.Threading { public static partial class ParallelFactory { - /// - /// Provides a default implementation of a for-iterator callback method. - /// - /// The type of the counter in a for-loop. - /// The current value of the counter in a for-loop. - /// One of the enumeration values that specifies the rules to apply as the assignment operator for left-hand operand and right-hand operand . - /// The value to assign to according to the rule specified by . - /// The computed result of having the of . - public static T Iterator(T current, AssignmentOperator assignment, T step) where T : struct, IComparable, IEquatable, IConvertible - { - Calculator.ValidAsNumericOperand(); - return Calculator.Calculate(current, assignment, step); - } - - /// - /// Provides a default implementation of a for-condition callback method. - /// - /// The type of the counter in a for-loop. - /// The current value of the counter in a for-loop. - /// One of the enumeration values that specifies the rules to apply as the relational operator for left-hand operand and right-hand operand . - /// The amount of repeats to do according to the rules specified by . - /// true if does not meet the condition of and ; otherwise false. - public static bool Condition(T current, RelationalOperator relational, T repeats) where T : struct, IComparable, IEquatable, IConvertible - { - Calculator.ValidAsNumericOperand(); - switch (relational) - { - case RelationalOperator.Equal: - return current.Equals(repeats); - case RelationalOperator.GreaterThan: - return current.CompareTo(repeats) > 0; - case RelationalOperator.GreaterThanOrEqual: - return current.CompareTo(repeats) >= 0; - case RelationalOperator.LessThan: - return current.CompareTo(repeats) < 0; - case RelationalOperator.LessThanOrEqual: - return current.CompareTo(repeats) <= 0; - case RelationalOperator.NotEqual: - return !current.Equals(repeats); - default: - throw new ArgumentOutOfRangeException(nameof(relational)); - } - } - /// /// Executes a parallel for loop. /// /// The start index, inclusive. /// The end index, exclusive. /// The delegate that is invoked once per iteration. - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(int fromInclusive, int toExclusive, Action worker, Action setup = null) + public static Task ForAsync(int fromInclusive, int toExclusive, Func worker, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, setup: setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup); } /// @@ -73,12 +28,12 @@ public static Task ForAsync(int fromInclusive, int toExclusive, Action work /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(int fromInclusive, int toExclusive, Action worker, T arg, Action setup = null) + public static Task ForAsync(int fromInclusive, int toExclusive, Func worker, T arg, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg, setup: setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup); } /// @@ -91,12 +46,12 @@ public static Task ForAsync(int fromInclusive, int toExclusive, ActionThe delegate that is invoked once per iteration. /// The first parameter of the delegate . /// The second parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, Action setup = null) + public static Task ForAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, setup: setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup); } /// @@ -111,12 +66,12 @@ public static Task ForAsync(int fromInclusive, int toExclusive, ActionThe first parameter of the delegate . /// The second parameter of the delegate . /// The third parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + public static Task ForAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, arg3, setup: setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup); } /// @@ -133,12 +88,12 @@ public static Task ForAsync(int fromInclusive, int toExclusive, Acti /// The second parameter of the delegate . /// The third parameter of the delegate . /// The fourth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + public static Task ForAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, arg3, arg4, setup: setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup); } /// @@ -157,225 +112,126 @@ public static Task ForAsync(int fromInclusive, int toExclusive, /// The third parameter of the delegate . /// The fourth parameter of the delegate . /// The fifth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(int fromInclusive, int toExclusive, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + public static Task ForAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, arg3, arg4, arg5, setup: setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// Executes a parallel for loop. /// - /// The type of the number used with the loop control variable. - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Action worker, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task ForAsync(long fromInclusive, long toExclusive, Func worker, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default); - return ForCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// Executes a parallel for loop. /// - /// The type of the number used with the loop control variable. /// The type of the parameter of the delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The parameter of the delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Action worker, T arg, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task ForAsync(long fromInclusive, long toExclusive, Func worker, T arg, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg); - return ForCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// Executes a parallel for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the delegate . /// The type of the second parameter of the delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the delegate . /// The second parameter of the delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Action worker, T1 arg1, T2 arg2, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task ForAsync(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2); - return ForCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// Executes a parallel for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the delegate . /// The type of the second parameter of the delegate . /// The type of the third parameter of the delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the delegate . /// The second parameter of the delegate . /// The third parameter of the delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Action worker, T1 arg1, T2 arg2, T3 arg3, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task ForAsync(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3); - return ForCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// Executes a parallel for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the delegate . /// The type of the second parameter of the delegate . /// The type of the third parameter of the delegate . /// The type of the fourth parameter of the delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the delegate . /// The second parameter of the delegate . /// The third parameter of the delegate . /// The fourth parameter of the delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task ForAsync(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4); - return ForCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections. + /// Executes a parallel for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the delegate . /// The type of the second parameter of the delegate . /// The type of the third parameter of the delegate . /// The type of the fourth parameter of the delegate . /// The type of the fifth parameter of the delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the delegate . /// The second parameter of the delegate . /// The third parameter of the delegate . /// The fourth parameter of the delegate . /// The fifth parameter of the delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task ForAsync(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5); - return ForCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); - } - - private static async Task ForCoreAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, ActionFactory workerFactory, Func condition, Func iterator, Action setup) - where TWorker : Template - where TNumber : struct, IComparable, IEquatable, IConvertible - { - if (condition == null) { condition = Condition; } - if (iterator == null) { iterator = Iterator; } - - var options = Patterns.Configure(setup); - var exceptions = new ConcurrentBag(); - - while (true) - { - var workChunks = options.PartitionSize; - var queue = new List(); - for (var i = @from; condition(i, relation, to); i = iterator(i, assignment, step)) - { - var shallowWorkerFactory = workerFactory.Clone(); - queue.Add(Task.Factory.StartNew(j => - { - try - { - shallowWorkerFactory.GenericArguments.Arg1 = (TNumber)j; - shallowWorkerFactory.ExecuteMethod(); - } - catch (Exception e) - { - exceptions.Add(e); - } - }, i, options.CancellationToken, options.CreationOptions, options.Scheduler)); - - workChunks--; - - if (workChunks == 0) - { - @from = Calculator.Calculate(i, assignment, step); - break; - } - } - if (queue.Count == 0) { break; } - await Task.WhenAll(queue).ConfigureAwait(false); - if (workChunks > 1) { break; } - } - if (exceptions.Count > 0) { throw new AggregateException(exceptions); } + return AdvancedParallelFactory.ForAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup: setup); } } } \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.ForEach.cs b/src/Cuemon.Threading/ParallelFactory.ForEach.cs new file mode 100644 index 000000000..1b0bf8399 --- /dev/null +++ b/src/Cuemon.Threading/ParallelFactory.ForEach.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; + +namespace Cuemon.Threading +{ + public static partial class ParallelFactory + { + /// + /// Executes a parallel foreach loop. + /// + /// The type of the data in the source. + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + public static void ForEach(IEnumerable source, Action worker, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForEachCore(source, ActionFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel foreach loop. + /// + /// The type of the data in the source. + /// The type of the parameter of the delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The parameter of the delegate . + /// The which may be configured. + public static void ForEach(IEnumerable source, Action worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForEachCore(source, ActionFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel foreach loop. + /// + /// The type of the data in the source. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The which may be configured. + public static void ForEach(IEnumerable source, Action worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForEachCore(source, ActionFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel foreach loop. + /// + /// The type of the data in the source. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The which may be configured. + public static void ForEach(IEnumerable source, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForEachCore(source, ActionFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel foreach loop. + /// + /// The type of the data in the source. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The which may be configured. + public static void ForEach(IEnumerable source, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForEachCore(source, ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel foreach loop. + /// + /// The type of the data in the source. + /// The type of the first parameter of the delegate . + /// The type of the second parameter of the delegate . + /// The type of the third parameter of the delegate . + /// The type of the fourth parameter of the delegate . + /// The type of the fifth parameter of the delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the delegate . + /// The second parameter of the delegate . + /// The third parameter of the delegate . + /// The fourth parameter of the delegate . + /// The fifth parameter of the delegate . + /// The which may be configured. + public static void ForEach(IEnumerable source, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + ForEachCore(source, ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static void ForEachCore(IEnumerable source, ActionFactory workerFactory, Action setup) where TWorker : Template + { + new ActionForEachSynchronousLoop(source, setup).PrepareExecution(workerFactory); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.ForEachAsync.cs b/src/Cuemon.Threading/ParallelFactory.ForEachAsync.cs index 3e3775d3d..7061aaba9 100644 --- a/src/Cuemon.Threading/ParallelFactory.ForEachAsync.cs +++ b/src/Cuemon.Threading/ParallelFactory.ForEachAsync.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Cuemon.Collections.Generic; @@ -14,14 +14,13 @@ public static partial class ParallelFactory /// The type of the data in the source. /// The sequence to iterate over parallel. /// The delegate that is invoked once per iteration. - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForEachAsync(IEnumerable source, Action worker, Action setup = null) + public static Task ForEachAsync(IEnumerable source, Func worker, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default); - return ForEachCoreAsync(source, wf, setup); + return ForEachCoreAsync(source, TaskActionFactory.Create(worker, default), setup); } /// @@ -32,14 +31,13 @@ public static Task ForEachAsync(IEnumerable source, ActionThe sequence to iterate over parallel. /// The delegate that is invoked once per iteration. /// The parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForEachAsync(IEnumerable source, Action worker, T arg, Action setup = null) + public static Task ForEachAsync(IEnumerable source, Func worker, T arg, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg); - return ForEachCoreAsync(source, wf, setup); + return ForEachCoreAsync(source, TaskActionFactory.Create(worker, default, arg), setup); } /// @@ -52,14 +50,13 @@ public static Task ForEachAsync(IEnumerable source, Action< /// The delegate that is invoked once per iteration. /// The first parameter of the delegate . /// The second parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForEachAsync(IEnumerable source, Action worker, T1 arg1, T2 arg2, Action setup = null) + public static Task ForEachAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2); - return ForEachCoreAsync(source, wf, setup); + return ForEachCoreAsync(source, TaskActionFactory.Create(worker, default, arg1, arg2), setup); } /// @@ -74,14 +71,13 @@ public static Task ForEachAsync(IEnumerable source, Ac /// The first parameter of the delegate . /// The second parameter of the delegate . /// The third parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForEachAsync(IEnumerable source, Action worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + public static Task ForEachAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3); - return ForEachCoreAsync(source, wf, setup); + return ForEachCoreAsync(source, TaskActionFactory.Create(worker, default, arg1, arg2, arg3), setup); } /// @@ -98,14 +94,13 @@ public static Task ForEachAsync(IEnumerable source /// The second parameter of the delegate . /// The third parameter of the delegate . /// The fourth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForEachAsync(IEnumerable source, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + public static Task ForEachAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4); - return ForEachCoreAsync(source, wf, setup); + return ForEachCoreAsync(source, TaskActionFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); } /// @@ -124,47 +119,31 @@ public static Task ForEachAsync(IEnumerable so /// The third parameter of the delegate . /// The fourth parameter of the delegate . /// The fifth parameter of the delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. - public static Task ForEachAsync(IEnumerable source, Action worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + public static Task ForEachAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = ActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5); - return ForEachCoreAsync(source, wf, setup); + return ForEachCoreAsync(source, TaskActionFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); } - private static async Task ForEachCoreAsync(IEnumerable source, ActionFactory workerFactory, Action setup) - where TWorker : Template + private static async Task ForEachCoreAsync(IEnumerable source, TaskActionFactory workerFactory, Action setup) where TWorker : Template { var options = Patterns.Configure(setup); - var exceptions = new ConcurrentBag(); - var partitioner = new PartitionerEnumerable(source, options.PartitionSize); + while (partitioner.HasPartitions) { var queue = new List(); foreach (var item in partitioner) { - var shallowWorkerFactory = workerFactory.Clone(); - queue.Add(Task.Factory.StartNew(element => - { - try - { - shallowWorkerFactory.GenericArguments.Arg1 = (TSource)element; - shallowWorkerFactory.ExecuteMethod(); - } - catch (Exception e) - { - exceptions.Add(e); - } - }, item, options.CancellationToken, options.CreationOptions, options.Scheduler)); + workerFactory.GenericArguments.Arg1 = item; + queue.Add(workerFactory.ExecuteMethodAsync(options.CancellationToken)); } if (queue.Count == 0) { break; } await Task.WhenAll(queue).ConfigureAwait(false); } - - if (exceptions.Count > 0) { throw new AggregateException(exceptions); } } } } \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.ForEachResult.cs b/src/Cuemon.Threading/ParallelFactory.ForEachResult.cs new file mode 100644 index 000000000..6f04ac46b --- /dev/null +++ b/src/Cuemon.Threading/ParallelFactory.ForEachResult.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + public static partial class ParallelFactory + { + /// + /// Executes a parallel foreach loop where the return value of the function delegate is stored in the same sequential order as . + /// + /// The type of the data in the source. + /// The type of the return value of the function delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as . + public static IReadOnlyCollection ForEachResult(IEnumerable source, Func worker, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForEachResultCore(source, FuncFactory.Create(worker, default), setup); + } + + /// + /// Executes a parallel foreach loop where the return value of the function delegate is stored in the same sequential order as . + /// + /// The type of the data in the source. + /// The type of the parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as . + public static IReadOnlyCollection ForEachResult(IEnumerable source, Func worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForEachResultCore(source, FuncFactory.Create(worker, default, arg), setup); + } + + /// + /// Executes a parallel foreach loop where the return value of the function delegate is stored in the same sequential order as . + /// + /// The type of the data in the source. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as . + public static IReadOnlyCollection ForEachResult(IEnumerable source, Func worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForEachResultCore(source, FuncFactory.Create(worker, default, arg1, arg2), setup); + } + + /// + /// Executes a parallel foreach loop where the return value of the function delegate is stored in the same sequential order as . + /// + /// The type of the data in the source. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as . + public static IReadOnlyCollection ForEachResult(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForEachResultCore(source, FuncFactory.Create(worker, default, arg1, arg2, arg3), setup); + } + + /// + /// Executes a parallel foreach loop where the return value of the function delegate is stored in the same sequential order as . + /// + /// The type of the data in the source. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as . + public static IReadOnlyCollection ForEachResult(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForEachResultCore(source, FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); + } + + /// + /// Executes a parallel foreach loop + /// + /// The type of the data in the source. + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the fifth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The sequence to iterate over parallel. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The fifth parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as . + public static IReadOnlyCollection ForEachResult(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(worker, nameof(worker)); + return ForEachResultCore(source, FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); + } + + private static IReadOnlyCollection ForEachResultCore(IEnumerable source, FuncFactory workerFactory, Action setup) + where TWorker : Template + { + return new FuncForEachSynchronousLoop(source, setup).GetResult(workerFactory); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.ForEachResultAsync.cs b/src/Cuemon.Threading/ParallelFactory.ForEachResultAsync.cs index a0eec57d4..855020712 100644 --- a/src/Cuemon.Threading/ParallelFactory.ForEachResultAsync.cs +++ b/src/Cuemon.Threading/ParallelFactory.ForEachResultAsync.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Cuemon.Collections.Generic; @@ -17,15 +18,14 @@ public static partial class ParallelFactory /// The type of the return value of the function delegate . /// The sequence to iterate over parallel. /// The delegate that is invoked once per iteration. - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as . - public static Task> ForEachResultAsync(IEnumerable source, Func worker, Action setup = null) + public static Task> ForEachResultAsync(IEnumerable source, Func> worker, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default); - return ForEachResultCoreAsync(source, wf, setup); + return ForEachResultCoreAsync(source, TaskFuncFactory.Create(worker, default), setup); } /// @@ -37,15 +37,14 @@ public static Task> ForEachResultAsyncThe sequence to iterate over parallel. /// The delegate that is invoked once per iteration. /// The parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as . - public static Task> ForEachResultAsync(IEnumerable source, Func worker, T arg, Action setup = null) + public static Task> ForEachResultAsync(IEnumerable source, Func> worker, T arg, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg); - return ForEachResultCoreAsync(source, wf, setup); + return ForEachResultCoreAsync(source, TaskFuncFactory.Create(worker, default, arg), setup); } /// @@ -59,15 +58,14 @@ public static Task> ForEachResultAsyncThe delegate that is invoked once per iteration. /// The first parameter of the function delegate . /// The second parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as . - public static Task> ForEachResultAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, Action setup = null) + public static Task> ForEachResultAsync(IEnumerable source, Func> worker, T1 arg1, T2 arg2, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2); - return ForEachResultCoreAsync(source, wf, setup); + return ForEachResultCoreAsync(source, TaskFuncFactory.Create(worker, default, arg1, arg2), setup); } /// @@ -83,15 +81,14 @@ public static Task> ForEachResultAsyncThe first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as . - public static Task> ForEachResultAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + public static Task> ForEachResultAsync(IEnumerable source, Func> worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3); - return ForEachResultCoreAsync(source, wf, setup); + return ForEachResultCoreAsync(source, TaskFuncFactory.Create(worker, default, arg1, arg2, arg3), setup); } /// @@ -109,15 +106,14 @@ public static Task> ForEachResultAsyncThe second parameter of the function delegate . /// The third parameter of the function delegate . /// The fourth parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as . - public static Task> ForEachResultAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + public static Task> ForEachResultAsync(IEnumerable source, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4); - return ForEachResultCoreAsync(source, wf, setup); + return ForEachResultCoreAsync(source, TaskFuncFactory.Create(worker, default, arg1, arg2, arg3, arg4), setup); } /// @@ -137,55 +133,37 @@ public static Task> ForEachResultAsyncThe third parameter of the function delegate . /// The fourth parameter of the function delegate . /// The fifth parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as . - public static Task> ForEachResultAsync(IEnumerable source, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + public static Task> ForEachResultAsync(IEnumerable source, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { Validator.ThrowIfNull(source, nameof(source)); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5); - return ForEachResultCoreAsync(source, wf, setup); + return ForEachResultCoreAsync(source, TaskFuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5), setup); } - private static async Task> ForEachResultCoreAsync(IEnumerable source, FuncFactory workerFactory, Action setup) + private static async Task> ForEachResultCoreAsync(IEnumerable source, TaskFuncFactory workerFactory, Action setup) where TWorker : Template { var options = Patterns.Configure(setup); - var exceptions = new ConcurrentBag(); var result = new ConcurrentDictionary(); - var sorter = 0; + long sorter = 0; var partitioner = new PartitionerEnumerable(source, options.PartitionSize); while (partitioner.HasPartitions) { - var queue = new List(); + var queue = new Dictionary>(); foreach (var item in partitioner) { - var shallowWorkerFactory = workerFactory.Clone(); - var current = sorter; - queue.Add(Task.Factory.StartNew(element => - { - try - { - shallowWorkerFactory.GenericArguments.Arg1 = (TSource)element; - var presult = shallowWorkerFactory.ExecuteMethod(); - result.TryAdd(current, presult); - } - catch (Exception e) - { - exceptions.Add(e); - } - }, item, options.CancellationToken, options.CreationOptions, options.Scheduler)); - + workerFactory.GenericArguments.Arg1 = item; + queue.Add(sorter, workerFactory.ExecuteMethodAsync(options.CancellationToken)); sorter++; - } if (queue.Count == 0) { break; } - await Task.WhenAll(queue).ConfigureAwait(false); + await Task.WhenAll(queue.Values).ConfigureAwait(false); + foreach (var item in queue) { result.TryAdd(item.Key, item.Value.Result); } } - - if (exceptions.Count > 0) { throw new AggregateException(exceptions); } return new ReadOnlyCollection(result.Values.ToList()); } } diff --git a/src/Cuemon.Threading/ParallelFactory.ForResult.cs b/src/Cuemon.Threading/ParallelFactory.ForResult.cs new file mode 100644 index 000000000..521a1ae41 --- /dev/null +++ b/src/Cuemon.Threading/ParallelFactory.ForResult.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; + +namespace Cuemon.Threading +{ + public static partial class ParallelFactory + { + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(int fromInclusive, int toExclusive, Func worker, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(int fromInclusive, int toExclusive, Func worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the fifth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The fifth parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(long fromInclusive, long toExclusive, Func worker, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup: setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(long fromInclusive, long toExclusive, Func worker, T arg, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup: setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup: setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup: setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup: setup); + } + + /// + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. + /// + /// The type of the first parameter of the function delegate . + /// The type of the second parameter of the function delegate . + /// The type of the third parameter of the function delegate . + /// The type of the fourth parameter of the function delegate . + /// The type of the fifth parameter of the function delegate . + /// The type of the return value of the function delegate . + /// The start index, inclusive. + /// The end index, exclusive. + /// The delegate that is invoked once per iteration. + /// The first parameter of the function delegate . + /// The second parameter of the function delegate . + /// The third parameter of the function delegate . + /// The fourth parameter of the function delegate . + /// The fifth parameter of the function delegate . + /// The which may be configured. + /// An where the return value of the function delegate is stored in the same sequential order as the for loop. + public static IReadOnlyCollection ForResult(long fromInclusive, long toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + { + Validator.ThrowIfNull(worker, nameof(worker)); + return AdvancedParallelFactory.ForResult(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup: setup); + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.ForResultAsync.cs b/src/Cuemon.Threading/ParallelFactory.ForResultAsync.cs index 2afa3801d..892455e38 100644 --- a/src/Cuemon.Threading/ParallelFactory.ForResultAsync.cs +++ b/src/Cuemon.Threading/ParallelFactory.ForResultAsync.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Cuemon.Threading @@ -16,13 +14,13 @@ public static partial class ParallelFactory /// The start index, inclusive. /// The end index, exclusive. /// The delegate that is invoked once per iteration. - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func worker, Action setup = null) + public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func> worker, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForResultAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, setup: setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup); } /// @@ -34,13 +32,13 @@ public static Task> ForResultAsync(int fro /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func worker, T arg, Action setup = null) + public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func> worker, T arg, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForResultAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg, setup: setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup); } /// @@ -54,13 +52,13 @@ public static Task> ForResultAsync(int /// The delegate that is invoked once per iteration. /// The first parameter of the function delegate . /// The second parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, Action setup = null) + public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func> worker, T1 arg1, T2 arg2, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForResultAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, setup: setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup); } /// @@ -76,13 +74,13 @@ public static Task> ForResultAsync /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) + public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func> worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForResultAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, arg3, setup: setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup); } /// @@ -100,13 +98,13 @@ public static Task> ForResultAsyncThe second parameter of the function delegate . /// The third parameter of the function delegate . /// The fourth parameter of the function delegate . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) + public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForResultAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, arg3, arg4, setup: setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup); } /// @@ -126,241 +124,139 @@ public static Task> ForResultAsyncThe third parameter of the function delegate . /// The fourth parameter of the function delegate . /// The fifth parameter of the function delegate . - /// The which may be configured. - /// A that represents the asynchronous operation. - public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) + /// The which may be configured. + /// A that represents the asynchronous operation. + /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. + public static Task> ForResultAsync(int fromInclusive, int toExclusive, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { Validator.ThrowIfNull(worker, nameof(worker)); - return ForResultAsync(fromInclusive, RelationalOperator.LessThan, toExclusive, AssignmentOperator.Addition, 1, worker, arg1, arg2, arg3, arg4, arg5, setup: setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. /// - /// The type of the number used with the loop control variable. /// The type of the return value of the function delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Func worker, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task> ForResultAsync(long fromInclusive, long toExclusive, Func> worker, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default); - return ForResultCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. /// - /// The type of the number used with the loop control variable. /// The type of the parameter of the function delegate . /// The type of the return value of the function delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The parameter of the function delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Func worker, T arg, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task> ForResultAsync(long fromInclusive, long toExclusive, Func> worker, T arg, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg); - return ForResultCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the function delegate . /// The type of the second parameter of the function delegate . /// The type of the return value of the function delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the function delegate . /// The second parameter of the function delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Func worker, T1 arg1, T2 arg2, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task> ForResultAsync(long fromInclusive, long toExclusive, Func> worker, T1 arg1, T2 arg2, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2); - return ForResultCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the function delegate . /// The type of the second parameter of the function delegate . /// The type of the third parameter of the function delegate . /// The type of the return value of the function delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Func worker, T1 arg1, T2 arg2, T3 arg3, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task> ForResultAsync(long fromInclusive, long toExclusive, Func> worker, T1 arg1, T2 arg2, T3 arg3, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3); - return ForResultCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the function delegate . /// The type of the second parameter of the function delegate . /// The type of the third parameter of the function delegate . /// The type of the fourth parameter of the function delegate . /// The type of the return value of the function delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . /// The fourth parameter of the function delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task> ForResultAsync(long fromInclusive, long toExclusive, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4); - return ForResultCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, setup: setup); } /// - /// Executes a parallel for loop that offers control of the loop control variable and loop sections where the return value of the function delegate is stored in the same sequential order as the for loop. + /// Executes a parallel for loop where the return value of the function delegate is stored in the same sequential order as the for loop. /// - /// The type of the number used with the loop control variable. /// The type of the first parameter of the function delegate . /// The type of the second parameter of the function delegate . /// The type of the third parameter of the function delegate . /// The type of the fourth parameter of the function delegate . /// The type of the fifth parameter of the function delegate . /// The type of the return value of the function delegate . - /// The initial value of the loop control variable. - /// The relation between the loop control variable and . - /// The conditional value of the loop control variable. - /// The assignment statement of the loop control variable using . - /// The value to assign the loop control variable. + /// The start index, inclusive. + /// The end index, exclusive. /// The delegate that is invoked once per iteration. /// The first parameter of the function delegate . /// The second parameter of the function delegate . /// The third parameter of the function delegate . /// The fourth parameter of the function delegate . /// The fifth parameter of the function delegate . - /// The function delegate that represents the condition section of the for loop. Default value is . - /// The function delegate that represents the iterator section of the for loop. Default value is . - /// The which may be configured. + /// The which may be configured. /// A that represents the asynchronous operation. /// The task result contains an where the return value of the function delegate is stored in the same sequential order as the for loop. - public static Task> ForResultAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, Func worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func condition = null, Func iterator = null, Action setup = null) - where TNumber : struct, IComparable, IEquatable, IConvertible + public static Task> ForResultAsync(long fromInclusive, long toExclusive, Func> worker, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Action setup = null) { - Calculator.ValidAsNumericOperand(); Validator.ThrowIfNull(worker, nameof(worker)); - var wf = FuncFactory.Create(worker, default, arg1, arg2, arg3, arg4, arg5); - return ForResultCoreAsync(@from, relation, to, assignment, step, wf, condition, iterator, setup); - } - - private static async Task> ForResultCoreAsync(TNumber from, RelationalOperator relation, TNumber to, AssignmentOperator assignment, TNumber step, FuncFactory workerFactory, Func condition, Func iterator, Action setup) - where TWorker : Template - where TNumber : struct, IComparable, IEquatable, IConvertible - { - if (condition == null) { condition = Condition; } - if (iterator == null) { iterator = Iterator; } - - var options = Patterns.Configure(setup); - var exceptions = new ConcurrentBag(); - var result = new ConcurrentDictionary(); - - while (true) - { - var workChunks = options.PartitionSize; - var queue = new List(); - for (var i = @from; condition(i, relation, to); i = iterator(i, assignment, step)) - { - var shallowWorkerFactory = workerFactory.Clone(); - queue.Add(Task.Factory.StartNew(j => - { - try - { - var number = (TNumber)j; - shallowWorkerFactory.GenericArguments.Arg1 = number; - var presult = shallowWorkerFactory.ExecuteMethod(); - result.TryAdd(number, presult); - } - catch (Exception e) - { - exceptions.Add(e); - } - }, i, options.CancellationToken, options.CreationOptions, options.Scheduler)); - - workChunks--; - - if (workChunks == 0) - { - @from = Calculator.Calculate(i, assignment, step); - break; - } - } - if (queue.Count == 0) { break; } - await Task.WhenAll(queue).ConfigureAwait(false); - if (workChunks > 1) { break; } - } - if (exceptions.Count > 0) { throw new AggregateException(exceptions); } - return new ReadOnlyCollection(result.Values.ToList()); + return AdvancedParallelFactory.ForResultAsync(new ForLoopRuleset(fromInclusive, toExclusive, 1), worker, arg1, arg2, arg3, arg4, arg5, setup: setup); } } } \ No newline at end of file diff --git a/src/Cuemon.Threading/ParallelFactory.cs b/src/Cuemon.Threading/ParallelFactory.cs new file mode 100644 index 000000000..e4ab515eb --- /dev/null +++ b/src/Cuemon.Threading/ParallelFactory.cs @@ -0,0 +1,9 @@ +namespace Cuemon.Threading +{ + /// + /// Provides a factory based way to encapsulate and re-use existing code while adding support for typically long-running parallel loops and regions. + /// + public static partial class ParallelFactory + { + } +} \ No newline at end of file diff --git a/src/Cuemon.Threading/Properties/PackageReleaseNotes.txt b/src/Cuemon.Threading/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..6cb177e43 --- /dev/null +++ b/src/Cuemon.Threading/Properties/PackageReleaseNotes.txt @@ -0,0 +1,29 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- The extent of refactoring applied to this project resulted in so many breaking changes that a git diff is advisable +  +# New Features +- ADDED AsyncOptions class in the Cuemon.Threading namespace that specifies options that is related to asynchronous operations +- ADDED AsyncTaskFactoryOptions class in the Cuemon.Threading namespace that specifies options that is related to both ParallelFactory and AdvancedParallelFactory +- ADDED AsyncWorkloadOptions class in the Cuemon.Threading namespace that specifies options that is related to both ParallelFactory and AdvancedParallelFactory +- ADDED AdvancedParallelFactory static class in the Cuemon.Threading namespace that provides a factory based way to work with advanced scenarios that encapsulate and re-use existing code while adding support for typically long-running parallel loops and regions +- ADDED TimerFactory class in the Cuemon.Threading namespace that provides access to factory methods for creating and configuring Timer instances +- ADDED ForLoopRuleset class in the Cuemon.Threading namespace that specifies the rules of a for-loop control flow statement +  +# Breaking Changes +- REMOVED ThreadPoolUtility class from the Cuemon.Threading namespace +  +# Bug Fixes +- APPLIED ConfigureAwait(false) to all async methods +- FIXED a bug that would lead to endless loop if workload was 1 (PartitionSize) +  +# Improvements +- All members now support true async functionality ForAsync, ForResultAsync, WhileAsync, WhileResultAsync, ForEachAsync and ForEachResultAsync +- Advanced members was moved to the AdvancedParallelFactory class to conform to Framework Design Guidelines +  +# Quality Analysis Actions +- APPLIED while loop over for loop https://rules.sonarsource.com/csharp/RSPEC-1264 +- JUSTIFIED that types are allowed to have many generic parameters https://rules.sonarsource.com/csharp/RSPEC-2436 +- JUSTIFIED that methods are allowed to have many generic parameters https://rules.sonarsource.com/csharp/RSPEC-107 \ No newline at end of file diff --git a/src/Cuemon.Threading/SynchronousLoop.cs b/src/Cuemon.Threading/SynchronousLoop.cs new file mode 100644 index 000000000..d03245475 --- /dev/null +++ b/src/Cuemon.Threading/SynchronousLoop.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + internal abstract class SynchronousLoop : Loop + { + protected SynchronousLoop(Action setup) : base(setup) + { + } + + protected ConcurrentBag Exceptions { get; } = new ConcurrentBag(); + + protected Func WhileCondition { get; set; } + + public void PrepareExecution(TemplateFactory worker) where TWorker : Template + { + WhileExecuting(worker); + } + + protected void WhileExecuting(TemplateFactory worker) where TWorker : Template + { + if (WhileCondition == null) { throw new InvalidOperationException($"{nameof(WhileCondition)} cannot be null."); } + while (WhileCondition()) + { + OnWhileExecutingBeforeFillWorkQueue(); + var queue = new List>(); + FillWorkQueue(worker, queue); + if (queue.Count == 0) { break; } + Process(queue); + } + if (Exceptions.Count > 0) { throw new AggregateException(Exceptions); } + } + + protected virtual void OnWhileExecutingBeforeFillWorkQueue() + { + } + + protected abstract void FillWorkQueue(TemplateFactory worker, IList> queue) where TWorker : Template; + + protected void Process(IList> queue) + { + try + { + Task.WaitAll(queue.Select(func => func()).ToArray(), Options.CancellationToken); + } + catch (Exception e) + { + Exceptions.Add(e); + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Threading/TimerFactory.cs b/src/Cuemon.Threading/TimerFactory.cs similarity index 100% rename from src/Cuemon.Core/Threading/TimerFactory.cs rename to src/Cuemon.Threading/TimerFactory.cs diff --git a/src/Cuemon.Threading/WhileSynchronousLoop.cs b/src/Cuemon.Threading/WhileSynchronousLoop.cs new file mode 100644 index 000000000..55269da6a --- /dev/null +++ b/src/Cuemon.Threading/WhileSynchronousLoop.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Cuemon.Threading +{ + internal abstract class WhileSynchronousLoop : SynchronousLoop + { + private int _workChunks; + + protected WhileSynchronousLoop(ForwardIterator iterator, Action setup) : base(setup) + { + Iterator = iterator; + ReadForward = true; + WhileCondition = () => true; + Sorter = 0; + } + + private ForwardIterator Iterator { get; } + + private bool ReadForward { get; set; } + + private long Sorter { get; set; } + + protected override void OnWhileExecutingBeforeFillWorkQueue() + { + _workChunks = Options.PartitionSize; + } + + protected sealed override void FillWorkQueue(TemplateFactory worker, IList> queue) + { + while (_workChunks > 0 && ReadForward) + { + ReadForward = Iterator.Read(); + if (!ReadForward) { return; } + var shallowWorkerFactory = worker.Clone(); + shallowWorkerFactory.GenericArguments.Arg1 = Iterator.Current; + var current = Sorter; + queue.Add(() => Task.Factory.StartNew(swf => + { + try + { + Interlocked.Decrement(ref _workChunks); + FillWorkQueueWorkerFactory(swf as TemplateFactory, current); + } + catch (Exception e) + { + Exceptions.Add(e); + } + }, shallowWorkerFactory, Options.CancellationToken, Options.CreationOptions, Options.Scheduler)); + Sorter++; + } + } + + protected abstract void FillWorkQueueWorkerFactory(TemplateFactory worker, long sorter) where TWorker : Template; + } +} \ No newline at end of file diff --git a/src/Cuemon.Xml/Cuemon.Xml.csproj b/src/Cuemon.Xml/Cuemon.Xml.csproj index a906e3618..fd66a4877 100644 --- a/src/Cuemon.Xml/Cuemon.Xml.csproj +++ b/src/Cuemon.Xml/Cuemon.Xml.csproj @@ -1,26 +1,15 @@ - netstandard2.0 - true - ..\cuemon.snk - true + net5.0;netstandard2.0 120bdf91-e7c7-4cb4-a39d-e1a5374c5602 - Cuemon Cuemon.Xml Cuemon.Xml - The Cuemon.Xml assembly provides access to features that extends both the System.Xml- and System.Xml.Serialization namespaces. Included is a lightweight XML serializer framework that offers the same flexibility provided by the JSON equivalent from Newtonsoft. - Geekle - Michael Mortensen - Copyright © Geekle 2009-2020. All rights reserved. - 6.0.2020.25 - 6.0.2020.25 - 6.0.2020.0 - Michael Mortensen - en-US + The Cuemon.Xml namespace contains types related to encoding, converting and serialization. The included lightweight XML serializer framework offers same flexibility as the one provided by the JSON equivalent from Newtonsoft. The namespace is an addition to both System.Xml- and System.Xml.Serialization namespaces. + xml-formatter xml-converter xml-serializer xml-factories diff --git a/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs b/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs index 04fe1596c..b9f996d06 100644 --- a/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs +++ b/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs @@ -185,7 +185,7 @@ public static IDecorator> AddExceptionDescriptorConverter(th foreach (var evidence in descriptor.Evidence) { if (evidence.Value == null) { continue; } - Decorator.Enclose(writer).WriteObject(evidence.Value, evidence.Value.GetType(), o => o.RootName = new XmlQualifiedEntity(evidence.Key)); + Decorator.Enclose(writer).WriteObject(evidence.Value, evidence.Value.GetType(), o => o.Settings.RootName = new XmlQualifiedEntity(evidence.Key)); } writer.WriteEndElement(); } @@ -343,7 +343,7 @@ private static void WriteExceptionCore(XmlWriter writer, Exception exception, bo { var value = property.GetValue(exception); if (value == null) { continue; } - Decorator.Enclose(writer).WriteObject(value, value.GetType(), settings => settings.RootName = new XmlQualifiedEntity(property.Name)); + Decorator.Enclose(writer).WriteObject(value, value.GetType(), o => o.Settings.RootName = new XmlQualifiedEntity(property.Name)); } WriteInnerExceptions(writer, exception, includeStackTrace); diff --git a/src/Cuemon.Xml/Extensions/XmlWriterDecoratorExtensions.cs b/src/Cuemon.Xml/Extensions/XmlWriterDecoratorExtensions.cs index 27470ec99..f4d4b6df7 100644 --- a/src/Cuemon.Xml/Extensions/XmlWriterDecoratorExtensions.cs +++ b/src/Cuemon.Xml/Extensions/XmlWriterDecoratorExtensions.cs @@ -5,6 +5,7 @@ using Cuemon.Reflection; using Cuemon.Runtime.Serialization; using Cuemon.Xml.Serialization; +using Cuemon.Xml.Serialization.Formatters; namespace Cuemon.Xml { @@ -21,11 +22,11 @@ public static class XmlWriterDecoratorExtensions /// The type of the object to serialize. /// The to extend. /// The object to serialize. - /// The which need to be configured. + /// The which may be configured. /// /// cannot be null. /// - public static void WriteObject(this IDecorator decorator, T value, Action setup = null) + public static void WriteObject(this IDecorator decorator, T value, Action setup = null) { WriteObject(decorator, value, typeof(T), setup); } @@ -36,15 +37,15 @@ public static void WriteObject(this IDecorator decorator, T value, /// The to extend. /// The object to serialize. /// The type of the object to serialize. - /// The which need to be configured. + /// The which may be configured. /// /// cannot be null. /// - public static void WriteObject(this IDecorator decorator, object value, Type objectType, Action setup = null) + public static void WriteObject(this IDecorator decorator, object value, Type objectType, Action setup = null) { Validator.ThrowIfNull(decorator, nameof(decorator)); - var serializer = XmlSerializer.Create(setup == null ? null : Patterns.Configure(setup)); - serializer.Serialize(decorator.Inner, value, objectType); + var formatter = new XmlFormatter(setup); + formatter.SerializeToWriter(decorator.Inner, value, objectType); } /// diff --git a/src/Cuemon.Xml/GlobalSuppressions.cs b/src/Cuemon.Xml/GlobalSuppressions.cs index db0c78032..291aaa197 100644 --- a/src/Cuemon.Xml/GlobalSuppressions.cs +++ b/src/Cuemon.Xml/GlobalSuppressions.cs @@ -6,6 +6,5 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Major Code Smell", "S907:\"goto\" statement should not be used", Justification = "Legacy implementation.", Scope = "member", Target = "~M:Cuemon.Xml.Serialization.Converters.DefaultXmlConverter.ParseReadXmlDefault(System.Xml.XmlReader,System.Type)~System.Object")] -[assembly: SuppressMessage("Major Code Smell", "S907:\"goto\" statement should not be used", Justification = "Legacy implementation.", Scope = "member", Target = "~M:Cuemon.Xml.XmlDataReader.ReadNext(System.Boolean)~System.Boolean")] [assembly: SuppressMessage("Major Code Smell", "S907:\"goto\" statement should not be used", Justification = "Legacy implementation.", Scope = "member", Target = "~M:Cuemon.Xml.XmlReaderDecoratorExtensions.ToHierarchy(Cuemon.IDecorator{System.Xml.XmlReader})~Cuemon.IHierarchy{Cuemon.DataPair}")] [assembly: SuppressMessage("Minor Code Smell", "S3626:Jump statements should not be redundant", Justification = "False-positive.", Scope = "member", Target = "~M:Cuemon.Xml.XmlStreamFactory.CreateStream(System.Action{System.Xml.XmlWriter},System.Action{System.Xml.XmlWriterSettings})~System.IO.Stream")] diff --git a/src/Cuemon.Xml/Properties/PackageReleaseNotes.txt b/src/Cuemon.Xml/Properties/PackageReleaseNotes.txt new file mode 100644 index 000000000..47d6780b6 --- /dev/null +++ b/src/Cuemon.Xml/Properties/PackageReleaseNotes.txt @@ -0,0 +1,50 @@ +Version: 6.0.0 +Availability: NET Standard 2.0, NET 5.0 +  +# Upgrade Steps +- The Cuemon.Serialization.Xml assembly and namespace was removed with this version +- Any XML serialization found in the Cuemon.Serialization.Xml namespace was merged into the Cuemon.Xml.Serialization namespace +- Any former extension methods of the Cuemon.Xml namespace was merged into the Cuemon.Extensions.Xml namespace +- The extent of refactoring applied to this project resulted in so many breaking changes that a git diff is advisable +  +# Breaking Changes +- REMOVED XElementExtensions class from the Cuemon.Xml.Serialization.Linq namespace +- REMOVED SerializableOrder class from the Cuemon.Xml.Serialization namespace +- REMOVED XmlJsonInstance class from the Cuemon.Xml.Serialization namespace +- RENAMED XPathNavigableConverter class in the Cuemon.Xml.XPath namespace to XPathDocumentFactory (including rename of remaining members) +- REMOVED JsonStreamConverter class from the Cuemon.Xml namespace +- REMOVED SecureXmlObfuscator class from the Cuemon.Xml namespace +- REMOVED XmlConvertExtensions class from the Cuemon.Xml namespace +- REMOVED XmlCopyOptions class from the Cuemon.Xml namespace +- RENAMED XmlDocumentConverter class in the Cuemon.Xml namespace to XmlDocumentFactory (including rename of remaining members) +- REMOVED XmlEncodingUtility class from the Cuemon.Xml namespace +- REMOVED XmlObfuscator class from the Cuemon.Xml namespace +- REMOVED XmlReaderConverter class from the Cuemon.Xml namespace +- REMOVED XmlReaderUtility class from the Cuemon.Xml namespace +- REMOVED XmlReaderUtilityExtensions class from the Cuemon.Xml namespace +- RENAMED XmlStreamConverter class in the Cuemon.Xml namespace to XmlStreamFactory (including rename of remaining members) +- REMOVED XmlUtility class from the Cuemon.Xml namespace +- REMOVED XmlUtilityExtensions class from the Cuemon.Xml namespace +- REMOVED XmlWriterUtility class from Cuemon.Xml namespace +- REMOVED XmlWriterUtilityExtensions class from the Cuemon.Xml namespace +  +# New Features +- MERGED DefaultXmlConverter class into Cuemon.Xml.Serialization.Converters namespace that provides a default way to convert objects to and from XML +- MERGED XmlConverter class into Cuemon.Xml.Serialization.Converters namespace that converts an object to and from XML +- MERGED XmlFormatter class into Cuemon.Xml.Serialization.Formatters namespace that serializes and deserializes an object in XML format +- MERGED XmlFormatterOptions class into Cuemon.Xml.Serialization.Formatters namespace that specifies configuration options for XmlFormatter +- MERGED DynamicXmlConverter class into Cuemon.Xml.Serialization namespace that provides a factory based way to create and wrap an XmlConverter implementation +- MERGED DynamicXmlSerializable class into Cuemon.Xml.Serialization namespace that provides a factory based way to create and wrap an IXmlSerializable implementation +- MERGED XmlConvert class into Cuemon.Xml.Serialization namespace that provides methods for converting between .NET types and XML types +- MERGED XmlSerializer class into Cuemon.Xml.Serialization namespace that serializes and deserializes objects into and from the XML format +- MERGED XmlSerializerSettings class into Cuemon.Xml.Serialization namespace that specifies configuration options for XmlSerializer +  +# Improvements +- EXTENDED XmlSerializer class in the Cuemon.Xml.Serialization namespace with a new overloaded method: Serialize +- RENAMED XmlSerializerSettings class in the Cuemon.Xml.Serialization namespace to XmlSerializerOptions +  +# Bug Fixes +- FIXED bug that in some cases could throw a SerializationException when serializable type consisted of only a default contructor (Cuemon.Xml.Serialization.Converters.DefaultXmlConverter.ParseReadXmlDefault) +- FIXED bug that would trigger a redundant entry of InnerException on AggregateException when serializing exceptions (Cuemon.Xml.Serialization.Converters.XmlConverterDecoratorExtensions.WriteInnerExceptions) +- FIXED bug that could trigger a NullReferenceException when serializing namespace information on an Exception class (Cuemon.Xml.Serialization.Converters.XmlConverterDecoratorExtensions.WriteException) +- FIXED assignment of default converters with multicast delegate (Cuemon.Xml.Serialization.Formatters.XmlFormatterOptions.ctor) \ No newline at end of file diff --git a/src/Cuemon.Xml/Serialization/Converters/DefaultXmlConverter.cs b/src/Cuemon.Xml/Serialization/Converters/DefaultXmlConverter.cs index 8d51a81f5..1f92d15b5 100644 --- a/src/Cuemon.Xml/Serialization/Converters/DefaultXmlConverter.cs +++ b/src/Cuemon.Xml/Serialization/Converters/DefaultXmlConverter.cs @@ -184,11 +184,12 @@ private object ParseReadXmlDefault(XmlReader reader, Type valueType) break; case XmlNodeType.CDATA: case XmlNodeType.Text: - Decorator.Enclose(values).TryAddOrUpdate(key, reader.Value); + Decorator.Enclose(values).AddOrUpdate(key, reader.Value); break; } } + var hasDefaultCtor = false; var constructors = valueType.GetConstructors(new MemberReflection(excludeStatic: true)).ToList(); var properties = valueType.GetProperties(new MemberReflection(excludeStatic: true)).Where(info => info.CanWrite).ToDictionary(info => info.Name); var propertyNames = properties.Select(info => info.Key).Intersect(values.Select(pair => pair.Key), StringComparer.OrdinalIgnoreCase).ToList(); @@ -200,6 +201,7 @@ private object ParseReadXmlDefault(XmlReader reader, Type valueType) var argumentsLength = arguments.Select(info => info.Name).Intersect(values.Select(pair => pair.Key), StringComparer.OrdinalIgnoreCase).Count(); if (arguments.Length == argumentsLength) { + if (!hasDefaultCtor && argumentsLength == 0) { hasDefaultCtor = true; } foreach (var arg in arguments) { args.Add(Decorator.Enclose(values.First(pair => pair.Key.Equals(arg.Name, StringComparison.OrdinalIgnoreCase)).Value).ChangeType(arg.ParameterType)); @@ -224,7 +226,7 @@ private object ParseReadXmlDefault(XmlReader reader, Type valueType) return method.Invoke(null, args.ToArray()); } } - throw new SerializationException("Unable to find a suitable constructor or static method for deserialization."); + if (!hasDefaultCtor) { throw new SerializationException("Unable to find a suitable constructor or static method for deserialization."); } } var instance = Activator.CreateInstance(valueType, args.ToArray()); diff --git a/src/Cuemon.Xml/Serialization/Formatters/XmlFormatter.cs b/src/Cuemon.Xml/Serialization/Formatters/XmlFormatter.cs index 10c10baaf..7d7b35adc 100644 --- a/src/Cuemon.Xml/Serialization/Formatters/XmlFormatter.cs +++ b/src/Cuemon.Xml/Serialization/Formatters/XmlFormatter.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Xml; using Cuemon.Runtime.Serialization.Formatters; using Cuemon.Xml.Serialization.Converters; @@ -50,6 +51,10 @@ public XmlFormatter(XmlFormatterOptions options) /// The object to serialize to XML format. /// The type of the object to serialize. /// A stream of the serialized . + /// + /// cannot be null -or- + /// cannot be null. + /// public override Stream Serialize(object source, Type objectType) { Validator.ThrowIfNull(source, nameof(source)); @@ -58,12 +63,36 @@ public override Stream Serialize(object source, Type objectType) return serializer.Serialize(source, objectType); } + /// + /// Serializes the specified into an XML format. + /// + /// The writer used in the serialization process. + /// The object to serialize to XML format. + /// The type of the object to serialize. + /// + /// cannot be null -or- + /// cannot be null -or- + /// cannot be null. + /// + public void SerializeToWriter(XmlWriter writer, object source, Type objectType) + { + Validator.ThrowIfNull(writer, nameof(writer)); + Validator.ThrowIfNull(source, nameof(source)); + Validator.ThrowIfNull(objectType, nameof(objectType)); + var serializer = XmlSerializer.Create(Options.Settings); + serializer.Serialize(writer, source, objectType); + } + /// /// Deserializes the specified into an object of . /// /// The stream from which to deserialize the object graph. /// The type of the deserialized object. /// An object of . + /// + /// cannot be null -or- + /// cannot be null. + /// public override object Deserialize(Stream value, Type objectType) { Validator.ThrowIfNull(value, nameof(value)); diff --git a/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs b/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs index c78abf5c2..31f5538e4 100644 --- a/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs +++ b/src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs @@ -1,14 +1,29 @@ using System; +using System.Collections.Generic; using Cuemon.Diagnostics; using Cuemon.Xml.Serialization.Converters; namespace Cuemon.Xml.Serialization.Formatters { /// - /// Specifies options that is related to operations. + /// Configuration options for . /// public class XmlFormatterOptions { + static XmlFormatterOptions() + { + DefaultConverters = list => + { + Decorator.Enclose(list) + .AddExceptionDescriptorConverter() + .AddEnumerableConverter() + .AddUriConverter() + .AddDateTimeConverter() + .AddTimeSpanConverter() + .AddStringConverter(); + }; + } + /// /// Initializes a new instance of the class. /// @@ -46,20 +61,17 @@ public XmlFormatterOptions() IncludeExceptionDescriptorFailure = true; IncludeExceptionDescriptorEvidence = true; IncludeExceptionStackTrace = false; - XmlSerializerOptions.DefaultConverters = list => - { - Decorator.Enclose(list) - .AddExceptionDescriptorConverter() - .AddExceptionConverter(() => IncludeExceptionStackTrace) - .AddEnumerableConverter() - .AddUriConverter() - .AddDateTimeConverter() - .AddTimeSpanConverter() - .AddStringConverter(); - }; Settings = new XmlSerializerOptions(); + Decorator.Enclose(Settings.Converters).AddExceptionConverter(() => IncludeExceptionStackTrace); + DefaultConverters?.Invoke(Settings.Converters); } + /// + /// Gets or sets a delegate that is invoked when is initialized and propagates registered implementations. + /// + /// The delegate which propagates registered implementations when is initialized. + public static Action> DefaultConverters { get; set; } + /// /// Gets or sets a value indicating whether the stack of an is included in the converter that handles exceptions. /// diff --git a/src/Cuemon.Xml/Serialization/XmlSerializer.cs b/src/Cuemon.Xml/Serialization/XmlSerializer.cs index 008c93c05..dab23296b 100644 --- a/src/Cuemon.Xml/Serialization/XmlSerializer.cs +++ b/src/Cuemon.Xml/Serialization/XmlSerializer.cs @@ -53,18 +53,9 @@ public Stream Serialize(object value, Type objectType) }); } - /// - /// Serializes the specified into an XML format. - /// - /// The writer used in the serialization process. - /// The object to serialize to XML format. - /// The type of the object to serialize. - /// - /// cannot be null. - /// - public void Serialize(XmlWriter writer, object value, Type objectType) + internal void Serialize(XmlWriter writer, object value, Type objectType) { - Validator.ThrowIfNull(writer, nameof(writer)); + GetWriterConverter(objectType).WriteXml(writer, value); } diff --git a/src/Cuemon.Xml/Serialization/XmlSerializerOptions.cs b/src/Cuemon.Xml/Serialization/XmlSerializerOptions.cs index 9c432a415..ef00a2eac 100644 --- a/src/Cuemon.Xml/Serialization/XmlSerializerOptions.cs +++ b/src/Cuemon.Xml/Serialization/XmlSerializerOptions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Xml; using Cuemon.Xml.Serialization.Converters; @@ -43,15 +42,8 @@ public XmlSerializerOptions() Writer = new XmlWriterSettings() { IndentChars = Alphanumeric.Tab }; Reader = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; Converters = new List(); - DefaultConverters?.Invoke(Converters); } - - /// - /// Gets or sets a delegate that is invoked when is initialized and propagates registered implementations. - /// - /// The delegate which propagates registered implementations when is initialized. - public static Action> DefaultConverters { get; set; } - + /// /// Gets or sets a collection that will be used during serialization. /// diff --git a/src/Cuemon.Xml/XmlStreamFactory.cs b/src/Cuemon.Xml/XmlStreamFactory.cs index bff5f0822..dc7e599d6 100644 --- a/src/Cuemon.Xml/XmlStreamFactory.cs +++ b/src/Cuemon.Xml/XmlStreamFactory.cs @@ -18,7 +18,7 @@ public static class XmlStreamFactory public static Stream CreateStream(Action writer, Action setup = null) { var options = Patterns.Configure(setup); - return Disposable.SafeInvoke(() => new MemoryStream(), ms => + return Patterns.SafeInvoke(() => new MemoryStream(), ms => { using (var w = XmlWriter.Create(ms, options)) { diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/Assets/ExceptionMiddleware.cs b/test/Cuemon.AspNetCore.Authentication.Tests/Assets/ExceptionMiddleware.cs new file mode 100644 index 000000000..09ecd546b --- /dev/null +++ b/test/Cuemon.AspNetCore.Authentication.Tests/Assets/ExceptionMiddleware.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace Cuemon.AspNetCore.Authentication.Assets +{ + public class ExceptionMiddleware : Middleware + { + public ExceptionMiddleware(RequestDelegate next) : base(next) + { + } + + public override async Task InvokeAsync(HttpContext context) + { + try + { + await Next(context); + } + catch (Exception exception) + { + if (exception is ArgumentException) + { + context.Response.StatusCode = StatusCodes.Status400BadRequest; + } + throw; + } + } + } + + public static class ApplicationBuilderExtensions + { + public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder) + { + return MiddlewareBuilderFactory.UseMiddleware(builder); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/BasicAuthenticationMiddlewareTest.cs b/test/Cuemon.AspNetCore.Authentication.Tests/BasicAuthenticationMiddlewareTest.cs new file mode 100644 index 000000000..a49f659dc --- /dev/null +++ b/test/Cuemon.AspNetCore.Authentication.Tests/BasicAuthenticationMiddlewareTest.cs @@ -0,0 +1,169 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Authentication.Basic; +using Cuemon.Collections.Generic; +using Cuemon.Extensions.AspNetCore.Authentication; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; +using Cuemon.Extensions; + +namespace Cuemon.AspNetCore.Authentication +{ + public class BasicAuthenticationMiddlewareTest : Test + { + public BasicAuthenticationMiddlewareTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task InvokeAsync_ShouldNotBeAuthenticated() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseBasicAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (username, password) => + { + return null; + }; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + TestOutput.WriteLine(wwwAuthenticate); + + var bb = new BasicAuthorizationHeaderBuilder() + .AddUserName("Agent") + .AddPassword("Test"); + + context.Request.Headers.Add(HeaderNames.Authorization, bb.Build().ToString()); + + ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + } + } + + [Fact] + public async Task InvokeAsync_ShouldFailBecauseOfColonInUserName() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseBasicAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (username, password) => + { + return null; + }; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + TestOutput.WriteLine(wwwAuthenticate); + + var bb = new BasicAuthorizationHeaderBuilder() + .AddUserName("Ag:ent") + .AddPassword("Test"); + + var ae = Assert.Throws(() => context.Request.Headers.Add(HeaderNames.Authorization, bb.Build().ToString())); + Assert.Contains("Colon is not allowed as part of the", ae.Message); + + ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + } + } + + [Fact] + public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeader() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseBasicAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (username, password) => + { + if (username == "Agent" && password == "Test") + { + var cp = new ClaimsPrincipal(); + cp.AddIdentity(new ClaimsIdentity(Arguments.Yield(new Claim("Name", "Test Agent")))); + return cp; + } + return null; + }; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + TestOutput.WriteLine(wwwAuthenticate); + + var bb = new BasicAuthorizationHeaderBuilder() + .AddUserName("Agent") + .AddPassword("Test"); + + context.Request.Headers.Add(HeaderNames.Authorization, bb.Build().ToString()); + + await pipeline(context); + + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/Cuemon.AspNetCore.Authentication.Tests.csproj b/test/Cuemon.AspNetCore.Authentication.Tests/Cuemon.AspNetCore.Authentication.Tests.csproj new file mode 100644 index 000000000..b377df6c5 --- /dev/null +++ b/test/Cuemon.AspNetCore.Authentication.Tests/Cuemon.AspNetCore.Authentication.Tests.csproj @@ -0,0 +1,15 @@ + + + + Cuemon.AspNetCore.Authentication + + + + + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/DigestAccessAuthenticationMiddlewareTest.cs b/test/Cuemon.AspNetCore.Authentication.Tests/DigestAccessAuthenticationMiddlewareTest.cs new file mode 100644 index 000000000..001e418d7 --- /dev/null +++ b/test/Cuemon.AspNetCore.Authentication.Tests/DigestAccessAuthenticationMiddlewareTest.cs @@ -0,0 +1,297 @@ +using System; +using System.IO; +using System.Security.Claims; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Authentication.Assets; +using Cuemon.AspNetCore.Authentication.Digest; +using Cuemon.Collections.Generic; +using Cuemon.Extensions; +using Cuemon.Extensions.AspNetCore.Authentication; +using Cuemon.Extensions.IO; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Authentication +{ + public class DigestAccessAuthenticationMiddlewareTest : Test + { + public DigestAccessAuthenticationMiddlewareTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task InvokeAsync_ShouldNotBeAuthenticated() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseExceptionMiddleware(); + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseDigestAccessAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (string username, out string password) => + { + if (username == "Agent") + { + password = "Test"; + var cp = new ClaimsPrincipal(); + cp.AddIdentity(new ClaimsIdentity(Arguments.Yield(new Claim("Name", "Test Agent")))); + return cp; + } + password = null; + return null; + }; + o.Realm = "unittest"; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + services.AddInMemoryDigestAuthenticationNonceTracker(); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + TestOutput.WriteLine(wwwAuthenticate); + } + } + + [Fact] + public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeader() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseDigestAccessAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (string username, out string password) => + { + if (username == "Agent") + { + password = "Test"; + var cp = new ClaimsPrincipal(); + cp.AddIdentity(new ClaimsIdentity(Arguments.Yield(new Claim("Name", "Test Agent")))); + return cp; + } + password = null; + return null; + }; + o.Realm = "unittest"; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + services.AddInMemoryDigestAuthenticationNonceTracker(); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + + TestOutput.WriteLine(wwwAuthenticate); + + var db = new DigestAuthorizationHeaderBuilder(options.Value.Algorithm) + .AddRealm(options.Value.Realm) + .AddUserName("Agent") + .AddUri("/") + .AddNc(1) + .AddCnonce() + .AddQopAuthentication() + .AddFromWwwAuthenticateHeader(context.Response.Headers); + + + var ha1 = db.ComputeHash1("Test"); + + TestOutput.WriteLine(ha1); + + var ha2 = db.ComputeHash2("GET", context.Response.Body.ToEncodedString(o => o.LeaveOpen = true)); + var response = db.ComputeResponse(ha1, ha2); + + db.AddResponse("Test", "GET", context.Response.Body.ToEncodedString()); + + context.Response.Body = new MemoryStream(); + context.Request.Headers.Add(HeaderNames.Authorization, db.Build().ToString()); + + await pipeline(context); + + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + + [Fact] + public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeaderNoPlainTextPassword() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseDigestAccessAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (string username, out string password) => + { + if (username == "Agent") + { + password = "a69d6da3eea4fa832dc1c0534863988e550e523f1f786c238951b7ec7abf4d57"; + var cp = new ClaimsPrincipal(); + cp.AddIdentity(new ClaimsIdentity(Arguments.Yield(new Claim("Name", "Test Agent")))); + return cp; + } + password = null; + return null; + }; + o.Realm = "unittest"; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + services.AddInMemoryDigestAuthenticationNonceTracker(); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + TestOutput.WriteLine(wwwAuthenticate); + + var db = new DigestAuthorizationHeaderBuilder(options.Value.Algorithm) + .AddRealm(options.Value.Realm) + .AddUserName("Agent") + .AddUri("/") + .AddNc(1) + .AddCnonce() + .AddQopAuthentication() + .AddFromWwwAuthenticateHeader(context.Response.Headers); + + + var ha1 = db.ComputeHash1("a69d6da3eea4fa832dc1c0534863988e550e523f1f786c238951b7ec7abf4d57"); + + TestOutput.WriteLine(ha1); + + var ha2 = db.ComputeHash2("GET", context.Response.Body.ToEncodedString(o => o.LeaveOpen = true)); + var response = db.ComputeResponse(ha1, ha2); + + db.AddResponse("a69d6da3eea4fa832dc1c0534863988e550e523f1f786c238951b7ec7abf4d57", "GET", context.Response.Body.ToEncodedString()); + + context.Response.Body = new MemoryStream(); + context.Request.Headers.Add(HeaderNames.Authorization, db.Build().ToString()); + + await pipeline(context); + + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + + [Fact] + public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeaderWithQopIntegrity() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseDigestAccessAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (string username, out string password) => + { + if (username == "Agent") + { + password = "Test"; + var cp = new ClaimsPrincipal(); + cp.AddIdentity(new ClaimsIdentity(Arguments.Yield(new Claim("Name", "Test Agent")))); + return cp; + } + password = null; + return null; + }; + o.Realm = "unittest"; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + services.AddInMemoryDigestAuthenticationNonceTracker(); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + TestOutput.WriteLine(wwwAuthenticate); + + var db = new DigestAuthorizationHeaderBuilder(options.Value.Algorithm) + .AddRealm(options.Value.Realm) + .AddUserName("Agent") + .AddUri("/") + .AddNc(1) + .AddCnonce() + .AddQopAuthenticationIntegrity() + .AddFromWwwAuthenticateHeader(context.Response.Headers); + + TestOutput.WriteLine("Body:"); + + var entityBody = "test of entityBody in request"; + + var ha1 = db.ComputeHash1("Test"); + + TestOutput.WriteLine("HA1:"); + TestOutput.WriteLine(ha1); + + var ha2 = db.ComputeHash2("POST", entityBody); + var response = db.ComputeResponse(ha1, ha2); + + TestOutput.WriteLine("HA2:"); + TestOutput.WriteLine(ha2); + + db.AddResponse("Test", "POST", entityBody); + + context.Request.Method = "POST"; + context.Request.Body = new MemoryStream(entityBody.ToByteArray()); + context.Request.Headers.Add(HeaderNames.Authorization, db.Build().ToString()); + + await pipeline(context); + + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/HmacAuthenticationMiddlewareTest.cs b/test/Cuemon.AspNetCore.Authentication.Tests/HmacAuthenticationMiddlewareTest.cs new file mode 100644 index 000000000..c696318b8 --- /dev/null +++ b/test/Cuemon.AspNetCore.Authentication.Tests/HmacAuthenticationMiddlewareTest.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Security.Claims; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Authentication.Hmac; +using Cuemon.Collections.Generic; +using Cuemon.Extensions; +using Cuemon.Extensions.AspNetCore.Authentication; +using Cuemon.Extensions.IO; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json.Serialization; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Authentication +{ + public class HmacAuthenticationMiddlewareTest : Test + { + public HmacAuthenticationMiddlewareTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeader() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseFakeHttpResponseTrigger(o => o.ShortCircuitOnStarting = true); + app.UseHmacAuthentication(); + }, services => + { + services.Configure(o => + { + o.Authenticator = (string clientId, out string clientSecret) => + { + clientSecret = null; + if (clientId == "Agent-Api") + { + clientSecret = "Test"; + var cp = new ClaimsPrincipal(); + cp.AddIdentity(new ClaimsIdentity(Arguments.Yield(new Claim("Name", "Test Agent")))); + return cp; + } + return null; + }; + o.RequireSecureConnection = false; + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var ue = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(ue.Message, options.Value.UnauthorizedMessage); + Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode); + + var wwwAuthenticate = context.Response.Headers[HeaderNames.WWWAuthenticate]; + + + TestOutput.WriteLine(wwwAuthenticate); + + context.Request.Host = new HostString("www.cuemon.net"); + context.Request.Headers.Add(HeaderNames.Date, context.Response.Headers[HeaderNames.Date]); + + var hb = new HmacAuthorizationHeaderBuilder() + .AddFromRequest(context.Request) + .AddClientId("Agent-Api") + .AddClientSecret("Test") + .AddCredentialScope("20150830/us-east-1/iam/aws4_request"); + + var hmacHeader = hb.Build(); + + TestOutput.WriteLine(hmacHeader.ToString()); + TestOutput.WriteLine(""); + TestOutput.WriteLine("--- HmacAuthorizationHeaderBuilder ---"); + TestOutput.WriteLine(hb.ToString()); + + context.Request.Headers.Add(HeaderNames.Authorization, hmacHeader.ToString()); + + await pipeline(context); + + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Assets/BearerThrottlingSentinelAttribute.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Assets/BearerThrottlingSentinelAttribute.cs new file mode 100644 index 000000000..91c6cb9f0 --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Assets/BearerThrottlingSentinelAttribute.cs @@ -0,0 +1,23 @@ +using System.Linq; +using Cuemon.AspNetCore.Mvc.Filters.Throttling; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +namespace Cuemon.AspNetCore.Mvc.Assets +{ + public class BearerThrottlingSentinelAttribute : ThrottlingSentinelAttribute + { + public BearerThrottlingSentinelAttribute(int rateLimit, double window, TimeUnit windowUnit) : base(rateLimit, window, windowUnit) + { + } + + public override string UniqueContextResolver(HttpContext context) + { + if (context.Request.Headers.TryGetValue(HeaderNames.Authorization, out var authorization)) + { + return authorization.ToString().Split(' ').Last(); + } + return null; + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Assets/ExceptionFilter.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Assets/ExceptionFilter.cs new file mode 100644 index 000000000..a8d81931c --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Assets/ExceptionFilter.cs @@ -0,0 +1,22 @@ +using Cuemon.AspNetCore.Http.Throttling; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Cuemon.AspNetCore.Mvc.Assets +{ + public class ExceptionFilter : ExceptionFilterAttribute + { + public override void OnException(ExceptionContext context) + { + if (context.ActionDescriptor is ControllerActionDescriptor) + { + var exception = context.Exception; + if (exception is ThrottlingException) + { + context.ExceptionHandled = true; + context.Result = new TooManyRequestsObjectResult(exception.Message); + } + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Assets/FakeController.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Assets/FakeController.cs new file mode 100644 index 000000000..fa93c98ab --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Assets/FakeController.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Mvc.Filters.Diagnostics; +using Cuemon.Extensions.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; + +namespace Cuemon.AspNetCore.Mvc.Assets +{ + [ApiController] + [Route("[controller]")] + public class FakeController : ControllerBase + { + [HttpGet] + [BearerThrottlingSentinel(10, 5, TimeUnit.Seconds)] + public IActionResult Get() + { + return Ok("Unit Test"); + } + + [HttpGet("it")] + public IActionResult GetIt() + { + return Ok("Unit Test"); + } + + [HttpGet("oneSecond")] + public async Task GetAfter1Second() + { + await Task.Delay(TimeSpan.FromSeconds(1)); + return Ok("Unit Test"); + } + + [ServerTiming(Name = "action-result")] + [HttpGet("oneSecondAttribute")] + public async Task GetAfter1SecondDecorated() + { + await Task.Delay(TimeSpan.FromSeconds(1)); + return Ok("Unit Test"); + } + + [HttpGet("getResponse400")] + public IActionResult GetBadRequest() + { + throw new ValidationException("Unit Test"); + } + + [HttpGet("getCacheByEtag")] + public IActionResult GetEtag() + { + return Ok("Unit Test".MakeCacheable(s => Convertible.GetBytes(Generate.HashCode32(s)))); + } + + [HttpGet("getCacheByLastModified")] + public IActionResult GetLastModified() + { + return Ok("Unit Test".MakeCacheable(s => DateTime.UnixEpoch)); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/CacheableObjectTest.cs b/test/Cuemon.AspNetCore.Mvc.Tests/CacheableObjectTest.cs index 34d862ffb..c6360a3fc 100644 --- a/test/Cuemon.AspNetCore.Mvc.Tests/CacheableObjectTest.cs +++ b/test/Cuemon.AspNetCore.Mvc.Tests/CacheableObjectTest.cs @@ -35,7 +35,7 @@ public void CreateCacheableObject_ShouldHaveICacheableIntegrityImplementation_Wh Assert.IsAssignableFrom(cor); if (cor.Value is IEntityDataIntegrity integrity) { - Assert.True(integrity.Validation == EntityDataIntegrityStrength.Strong); + Assert.True(integrity.Validation == EntityDataIntegrityValidation.Strong); Assert.True(integrity.Checksum.GetBytes() == orBytes); } Assert.Equal(cor.Value, or); @@ -47,12 +47,12 @@ public void CreateCacheableObject_ShouldHaveICacheableEntityImplementation_WhenC var or = Generate.RandomString(2048); var orBytes = Convertible.GetBytes(or); var cor = CacheableObjectFactory.CreateCacheableObjectResult(or, () => DateTime.MinValue, () => orBytes, () => DateTime.MaxValue, () => false); - Assert.IsAssignableFrom(cor); - if (cor.Value is IEntityData entity) + Assert.IsAssignableFrom(cor); + if (cor.Value is IEntityInfo entity) { Assert.True(entity.Created == DateTime.MinValue); Assert.True(entity.Modified == DateTime.MinValue); - Assert.True(entity.Validation == EntityDataIntegrityStrength.Strong); + Assert.True(entity.Validation == EntityDataIntegrityValidation.Strong); Assert.True(entity.Checksum.GetBytes() == orBytes); } Assert.Equal(cor.Value, or); diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj b/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj index abd7de891..6fa916af4 100644 --- a/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Cuemon.AspNetCore.Mvc.Tests.csproj @@ -1,29 +1,20 @@ - - + - netcoreapp3.0 - - false - Cuemon.AspNetCore.Mvc - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + - - + + Always + \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Cacheable/HttpCacheableFilterTest.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Cacheable/HttpCacheableFilterTest.cs new file mode 100644 index 000000000..ab5f6d737 --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Cacheable/HttpCacheableFilterTest.cs @@ -0,0 +1,81 @@ +using System.Threading.Tasks; +using Cuemon.AspNetCore.Mvc.Assets; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Mvc.Filters.Cacheable +{ + public class HttpCacheableFilterTest : Test + { + public HttpCacheableFilterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task GetEtag_ShouldReturnOkWithEtagAndSubsequentlyNotModified() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.Configure(o => + { + o.Filters.Add(new HttpEntityTagHeaderFilter()); + }); + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var client = filter.Host.GetTestClient(); + var result = await client.GetAsync("/fake/getCacheByEtag"); + var etag = result.Headers.ETag.ToString(); + + Assert.Equal(StatusCodes.Status200OK, (int) result.StatusCode); + TestOutput.WriteLine(etag); + + client.DefaultRequestHeaders.Add(HeaderNames.IfNoneMatch, etag); + + result = await client.GetAsync("/fake/getCacheByEtag"); + Assert.Equal(StatusCodes.Status304NotModified, (int) result.StatusCode); + } + } + + [Fact] + public async Task GetLastModified_ShouldReturnOkWithLastModifiedAndSubsequentlyNotModified() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.Configure(o => + { + o.Filters.Add(new HttpLastModifiedHeaderFilter()); + }); + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var client = filter.Host.GetTestClient(); + var result = await client.GetAsync("/fake/getCacheByLastModified"); + var lastModified = result.Content.Headers.LastModified.Value; + + Assert.Equal(StatusCodes.Status200OK, (int) result.StatusCode); + TestOutput.WriteLine(lastModified.ToString("O")); + + client.DefaultRequestHeaders.Add(HeaderNames.IfModifiedSince, lastModified.ToString("R")); + + result = await client.GetAsync("/fake/getCacheByLastModified"); + Assert.Equal(StatusCodes.Status304NotModified, (int) result.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Diagnostics/FaultDescriptorFilterTest.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Diagnostics/FaultDescriptorFilterTest.cs new file mode 100644 index 000000000..3c9dd0b96 --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Diagnostics/FaultDescriptorFilterTest.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using Cuemon.AspNetCore.Mvc.Assets; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics +{ + public class FaultDescriptorFilterTest : Test + { + public FaultDescriptorFilterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task GetBadRequest_ShouldReturnBadRequest() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly).AddJsonOptions(o => + { + o.JsonSerializerOptions.MaxDepth = 0; + o.JsonSerializerOptions.IgnoreNullValues = true; + o.JsonSerializerOptions.IgnoreReadOnlyProperties = true; + }); + })) + { + var client = filter.Host.GetTestClient(); + var result = await client.GetAsync("/fake/getResponse400"); + + Assert.Equal(StatusCodes.Status400BadRequest, (int) result.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Diagnostics/ServerTimingFilterTest.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Diagnostics/ServerTimingFilterTest.cs new file mode 100644 index 000000000..3aa9bbd85 --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Diagnostics/ServerTimingFilterTest.cs @@ -0,0 +1,139 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Diagnostics; +using Cuemon.AspNetCore.Mvc.Assets; +using Cuemon.Diagnostics; +using Cuemon.Extensions.AspNetCore.Diagnostics; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Mvc.Filters.Diagnostics +{ + public class ServerTimingFilterTest : Test + { + public ServerTimingFilterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task OnActionExecutionAsync_ShouldTimeMeasureFakeControllerAndFictiveMeasurements() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.Use(async (context, next) => + { + var serverTiming = context.RequestServices.GetRequiredService(); + + await Task.Delay(22); + serverTiming.AddServerTiming("redis", TimeSpan.FromMilliseconds(22), "Redis Cache"); + + await Task.Delay(1700); + serverTiming.AddServerTiming("restApi", TimeSpan.FromSeconds(1.7), "Some REST API integration"); + + await next(); + }); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.AddServerTiming(); + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var client = filter.Host.GetTestClient(); + var profiler = await TimeMeasure.WithFuncAsync(client.GetAsync, "/fake/oneSecond"); + var serverTimings = profiler.Result.Headers.GetValues(ServerTiming.HeaderName).ToArray(); + + Assert.InRange(profiler.Elapsed, TimeSpan.Zero, TimeSpan.FromSeconds(5)); + Assert.True(profiler.Result.Headers.Contains("Server-Timing")); + Assert.Equal("redis", serverTimings[0].Split(';').First()); + Assert.Equal("restApi", serverTimings[1].Split(';').First()); + Assert.Equal("mvc", serverTimings[2].Split(';').First()); + + TestOutput.WriteLine(profiler.Elapsed.ToString()); + } + } + + [Fact] + public async Task OnActionExecutionAsync_ShouldTimeMeasureFakeController() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.AddServerTiming(); + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var client = filter.Host.GetTestClient(); + var profiler = await TimeMeasure.WithFuncAsync(client.GetAsync, "/fake/oneSecond"); + + Assert.InRange(profiler.Elapsed, TimeSpan.Zero, TimeSpan.FromSeconds(5)); + Assert.True(profiler.Result.Headers.Contains("Server-Timing")); + Assert.StartsWith("mvc", profiler.Result.Headers.GetValues(ServerTiming.HeaderName).Single()); + + TestOutput.WriteLine(profiler.Elapsed.ToString()); + } + } + + [Fact] + public async Task ServerTimingAttribute_ShouldTimeMeasureFakeController_GetAfter1SecondDecorated() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.AddServerTiming(); + services.AddControllers().AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var client = filter.Host.GetTestClient(); + var profiler = await TimeMeasure.WithFuncAsync(client.GetAsync, "/fake/oneSecondAttribute"); + + Assert.InRange(profiler.Elapsed, TimeSpan.Zero, TimeSpan.FromSeconds(5)); + Assert.True(profiler.Result.Headers.Contains("Server-Timing")); + Assert.StartsWith("action-result", profiler.Result.Headers.GetValues(ServerTiming.HeaderName).Single()); + + TestOutput.WriteLine(profiler.Elapsed.ToString()); + } + } + + [Fact] + public async Task ServerTimingAttribute_ShouldSuppressTimeMeasureFakeController_GetAfter1SecondDecorated() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.AddServerTiming(); + services.AddControllers().AddApplicationPart(typeof(FakeController).Assembly); + services.Configure(o => o.SuppressHeaderPredicate = _ => true); + })) + { + var options = filter.ServiceProvider.GetRequiredService>(); + var client = filter.Host.GetTestClient(); + + var profiler = await TimeMeasure.WithFuncAsync(client.GetAsync, "/fake/oneSecondAttribute"); + + Assert.InRange(profiler.Elapsed, TimeSpan.Zero, TimeSpan.FromSeconds(5)); + Assert.True(options.Value.SuppressHeaderPredicate(filter.HostingEnvironment)); + Assert.False(profiler.Result.Headers.Contains("Server-Timing")); + + TestOutput.WriteLine(profiler.Elapsed.ToString()); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Headers/UserAgentSentinelFilterTest.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Headers/UserAgentSentinelFilterTest.cs new file mode 100644 index 000000000..04d0f0b5a --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Headers/UserAgentSentinelFilterTest.cs @@ -0,0 +1,177 @@ +using System.Linq; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Http.Headers; +using Cuemon.AspNetCore.Mvc.Assets; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Mvc.Filters.Headers +{ + public class UserAgentSentinelFilterTest : Test + { + public UserAgentSentinelFilterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task OnActionExecutionAsync_ShouldThrowUserAgentException_BadRequest() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + services.Configure(o => { o.RequireUserAgentHeader = true; }); + })) + { + var options = filter.ServiceProvider.GetRequiredService>(); + var client = filter.Host.GetTestClient(); + + var uae = await Assert.ThrowsAsync(async () => + { + var result = await client.GetAsync("/fake/it"); + }); + + + Assert.Equal(uae.Message, options.Value.BadRequestMessage); + Assert.Equal(uae.StatusCode, StatusCodes.Status400BadRequest); + + Assert.True(options.Value.RequireUserAgentHeader); + } + } + + [Fact] + public async Task OnActionExecutionAsync_ShouldThrowUserAgentException_Forbidden() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.Configure(o => + { + o.RequireUserAgentHeader = true; + o.ValidateUserAgentHeader = true; + o.AllowedUserAgents.Add("Cuemon-Agent"); + }); + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var options = filter.ServiceProvider.GetRequiredService>(); + var client = filter.Host.GetTestClient(); + client.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "Invalid-Agent"); + + var uae = await Assert.ThrowsAsync(async () => + { + var result = await client.GetAsync("/fake/it"); + }); + + + Assert.Equal(uae.Message, options.Value.ForbiddenMessage); + Assert.Equal(uae.StatusCode, StatusCodes.Status403Forbidden); + + Assert.True(options.Value.RequireUserAgentHeader); + Assert.True(options.Value.ValidateUserAgentHeader); + Assert.True(options.Value.AllowedUserAgents.Any()); + } + } + + [Fact] + public async Task OnActionExecutionAsync_ShouldThrowUserAgentException_BadRequest_BecauseOfUseGenericResponse() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.Configure(o => + { + o.RequireUserAgentHeader = true; + o.ValidateUserAgentHeader = true; + o.UseGenericResponse = true; + o.AllowedUserAgents.Add("Cuemon-Agent"); + }); + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var options = filter.ServiceProvider.GetRequiredService>(); + var client = filter.Host.GetTestClient(); + client.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "Invalid-Agent"); + + var uae = await Assert.ThrowsAsync(async () => + { + var result = await client.GetAsync("/fake/it"); + }); + + Assert.Equal(uae.Message, options.Value.BadRequestMessage); + Assert.Equal(uae.StatusCode, StatusCodes.Status400BadRequest); + + Assert.True(options.Value.RequireUserAgentHeader); + Assert.True(options.Value.ValidateUserAgentHeader); + Assert.True(options.Value.UseGenericResponse); + Assert.True(options.Value.AllowedUserAgents.Any()); + } + } + + [Fact] + public async Task OnActionExecutionAsync_ShouldAllowRequestUnconditional() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly))) + { + var options = filter.ServiceProvider.GetRequiredService>(); + + var client = filter.Host.GetTestClient(); + var result = await client.GetAsync("/fake/it"); + + Assert.Equal(StatusCodes.Status200OK, (int) result.StatusCode); + Assert.False(options.Value.RequireUserAgentHeader); + } + } + + [Fact] + public async Task OnActionExecutionAsync_ShouldAllowRequestAfterBeingValidated() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.Configure(o => + { + o.RequireUserAgentHeader = true; + o.ValidateUserAgentHeader = true; + o.AllowedUserAgents.Add("Cuemon-Agent"); + }); + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + })) + { + var options = filter.ServiceProvider.GetRequiredService>(); + var client = filter.Host.GetTestClient(); + client.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "Cuemon-Agent"); + + var result = await client.GetAsync("/fake/it"); + + Assert.True(options.Value.RequireUserAgentHeader); + Assert.True(options.Value.ValidateUserAgentHeader); + Assert.Equal(StatusCodes.Status200OK, (int) result.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Throttling/ThrottlingSentinelAttributeTest.cs b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Throttling/ThrottlingSentinelAttributeTest.cs new file mode 100644 index 000000000..a812573aa --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Filters/Throttling/ThrottlingSentinelAttributeTest.cs @@ -0,0 +1,150 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Http.Throttling; +using Cuemon.AspNetCore.Mvc.Assets; +using Cuemon.Extensions; +using Cuemon.Extensions.AspNetCore.Http.Throttling; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Mvc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Mvc.Filters.Throttling +{ + public class ThrottlingSentinelAttributeTest : AspNetCoreHostTest + { + private readonly IServiceProvider _provider; + + public ThrottlingSentinelAttributeTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _provider = hostFixture.ServiceProvider; + } + + [Fact] + public async Task Bearer_ShouldThrottleWhenQuotaIsExceeded() + { + var cache = _provider.GetRequiredService(); + var client = Host.GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", nameof(Bearer_ShouldThrottleWhenQuotaIsExceeded)); + + var te = await Assert.ThrowsAsync(async () => + { + for (var i = 0; i < 15; i++) + { + var result = await client.GetAsync("/fake"); + Assert.Equal(StatusCodes.Status200OK, (int)result.StatusCode); + Assert.Equal("Unit Test", await result.Content.ReadAsStringAsync()); + } + }); + + + var ce = cache[nameof(Bearer_ShouldThrottleWhenQuotaIsExceeded)]; + + Assert.InRange(ce.Total, te.RateLimit, 15); + Assert.Equal(ce.Quota.RateLimit, te.RateLimit); + Assert.Equal(ce.Quota.Window, TimeSpan.FromSeconds(5)); + Assert.Equal(StatusCodes.Status429TooManyRequests, te.StatusCode); + } + + [Fact] + public async Task Bearer_ShouldThrottleAndThenRehydrateAfterWindowHasPassed() + { + var cache = _provider.GetRequiredService(); + var client = Host.GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", nameof(Bearer_ShouldThrottleAndThenRehydrateAfterWindowHasPassed)); + + var te = await Assert.ThrowsAsync(async () => + { + for (var i = 0; i < 15; i++) + { + var result = await client.GetAsync("/fake"); + Assert.Equal(StatusCodes.Status200OK, (int)result.StatusCode); + Assert.Equal("Unit Test", await result.Content.ReadAsStringAsync()); + } + }); + + + var ce = cache[nameof(Bearer_ShouldThrottleAndThenRehydrateAfterWindowHasPassed)]; + + Assert.InRange(ce.Total, te.RateLimit, 15); + Assert.Equal(ce.Quota.RateLimit, te.RateLimit); + Assert.Equal(ce.Quota.Window, TimeSpan.FromSeconds(5)); + Assert.Equal(StatusCodes.Status429TooManyRequests, te.StatusCode); + + await Task.Delay(TimeSpan.FromSeconds(5)); + + for (var i = 0; i < 5; i++) + { + var result = await client.GetAsync("/fake"); + Assert.Equal(StatusCodes.Status200OK, (int)result.StatusCode); + Assert.Equal("Unit Test", await result.Content.ReadAsStringAsync()); + } + + Assert.Equal(5, ce.Total); + } + + [Fact] + public async Task Bearer_VerifyHeadersAreSetCorrectly() + { + using (var filter = MvcFilterTestFactory.CreateMvcFilterTest(app => + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + }, services => + { + services.AddControllers(o => { o.Filters.Add(); }).AddApplicationPart(typeof(FakeController).Assembly); + services.AddMemoryThrottlingCache(); + })) + { + var client = filter.Host.GetTestClient(); + + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", nameof(Bearer_VerifyHeadersAreSetCorrectly)); + + HttpResponseMessage result = null; + for (var i = 0; i < 10; i++) + { + result = await client.GetAsync("/fake"); + Assert.Equal(StatusCodes.Status200OK, (int)result.StatusCode); + Assert.Equal("Unit Test", await result.Content.ReadAsStringAsync()); + } + + result = await client.GetAsync("/fake"); + + var retryAfter = result.Headers.RetryAfter.Delta.Value.TotalSeconds; + var ratelimitReset = result.Headers.GetValues("RateLimit-Reset").Single().As(); + + Assert.Equal(StatusCodes.Status429TooManyRequests, (int)result.StatusCode); + Assert.Equal("Throttling rate limit quota violation. Quota limit exceeded.", await result.Content.ReadAsStringAsync()); + Assert.Contains("Retry-After", result.Headers.Select(pair => pair.Key)); + Assert.Contains("RateLimit-Limit", result.Headers.Select(pair => pair.Key)); + Assert.Contains("RateLimit-Remaining", result.Headers.Select(pair => pair.Key)); + Assert.Contains("RateLimit-Reset", result.Headers.Select(pair => pair.Key)); + + Assert.Equal(retryAfter, ratelimitReset); + } + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddApplicationPart(typeof(FakeController).Assembly); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + services.AddMemoryThrottlingCache(); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseRouting(); + app.UseEndpoints(routes => + { + routes.MapControllers(); + }); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/Properties/launchSettings.json b/test/Cuemon.AspNetCore.Mvc.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..505d8e923 --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:61097/", + "sslPort": 44328 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Cuemon.AspNetCore.Mvc.Tests": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/SeeOtherResultTest.cs b/test/Cuemon.AspNetCore.Mvc.Tests/SeeOtherResultTest.cs new file mode 100644 index 000000000..bc9a9162f --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/SeeOtherResultTest.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using Cuemon.Extensions.Xunit.Hosting; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Mvc +{ + public class SeeOtherResultTest : HostTest + { + private readonly IServiceProvider _provider; + + public SeeOtherResultTest(HostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _provider = hostFixture.ServiceProvider; + } + + [Fact] + public async Task ExecuteResultAsync_ShouldReturnStatusCode303AndLocationUri() + { + var uri = new Uri("https://www.cuemon.net/"); + var context = _provider.GetRequiredService().HttpContext; + var sor = new SeeOtherResult(uri); + var ac = new ActionContext(context, new RouteData(), new ActionDescriptor()); + + Assert.Equal(StatusCodes.Status303SeeOther, sor.StatusCode); + Assert.Equal(uri, sor.Location); + + await sor.ExecuteResultAsync(ac); + + Assert.Equal(sor.StatusCode, context.Response.StatusCode); + Assert.Equal(sor.Location.OriginalString, context.Response.Headers[HeaderNames.Location]); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddFakeHttpContextAccessor(ServiceLifetime.Transient); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Mvc.Tests/xunit.runner.json b/test/Cuemon.AspNetCore.Mvc.Tests/xunit.runner.json new file mode 100644 index 000000000..34b2fe2cd --- /dev/null +++ b/test/Cuemon.AspNetCore.Mvc.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "shadowCopy": false +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj b/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj index 2b2ab47c5..7df076545 100644 --- a/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj +++ b/test/Cuemon.AspNetCore.Tests/Cuemon.AspNetCore.Tests.csproj @@ -1,29 +1,14 @@ - + - netcoreapp3.1 - - false - Cuemon.AspNetCore - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + - + + - + \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Tests/Hosting/HostingEnvironmentMiddlewareTest.cs b/test/Cuemon.AspNetCore.Tests/Hosting/HostingEnvironmentMiddlewareTest.cs new file mode 100644 index 000000000..e80357aff --- /dev/null +++ b/test/Cuemon.AspNetCore.Tests/Hosting/HostingEnvironmentMiddlewareTest.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Linq; +using System.Threading.Tasks; +using Cuemon.Extensions.AspNetCore.Hosting; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Hosting +{ + public class HostingEnvironmentMiddlewareTest : AspNetCoreHostTest + { + private readonly IServiceProvider _provider; + private readonly IApplicationBuilder _pipeline; + + public HostingEnvironmentMiddlewareTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _pipeline = hostFixture.Application; + _provider = hostFixture.ServiceProvider; + } + + [Fact] + public async Task InvokeAsync_ShouldHaveHostingEnvironmentHeader_ConfiguredByIOptions() + { + var context = _provider.GetRequiredService().HttpContext; + var options = _provider.GetRequiredService>(); + var pipeline = _pipeline.Build(); + + await pipeline(context); + + Assert.True(context.Response.Headers.TryGetValue(options.Value.HeaderName, out var xHostingEnvironmentHeader)); + Assert.Equal(HostingEnvironment.EnvironmentName, xHostingEnvironmentHeader.Single()); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddFakeHttpContextAccessor(ServiceLifetime.Transient); + services.Configure(o => o.HeaderName = "X-Environment"); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseHostingEnvironment(); + app.UseFakeHttpResponseTrigger(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Tests/Http/Headers/CorrelationIdentifierMiddlewareTest.cs b/test/Cuemon.AspNetCore.Tests/Http/Headers/CorrelationIdentifierMiddlewareTest.cs new file mode 100644 index 000000000..43e68c743 --- /dev/null +++ b/test/Cuemon.AspNetCore.Tests/Http/Headers/CorrelationIdentifierMiddlewareTest.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Cuemon.Extensions.AspNetCore.Http.Headers; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Cuemon.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Http.Headers +{ + public class CorrelationIdentifierMiddlewareTest : AspNetCoreHostTest + { + private readonly IServiceProvider _provider; + private readonly IApplicationBuilder _pipeline; + + public CorrelationIdentifierMiddlewareTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _pipeline = hostFixture.Application; + _provider = hostFixture.ServiceProvider; + } + + [Fact] + public async Task InvokeAsync_ShouldCreateNewCorrelationIdHeader_ConfiguredByDefault() + { + var context = _provider.GetRequiredService().HttpContext; + var options = _provider.GetRequiredService>(); + var pipeline = _pipeline.Build(); + + await pipeline(context); + + Assert.True(context.Response.Headers.TryGetValue(options.Value.HeaderName, out var xCorrelationIdHeader)); + Assert.True(ParserFactory.FromGuid().TryParse(xCorrelationIdHeader.Single(), out var correlationId, o => o.Formats = GuidFormats.N)); + + TestOutput.WriteLine(correlationId.ToString("N")); + } + + [Fact] + public async Task InvokeAsync_ShouldRelayCorrelationIdHeader_ConfiguredByDefault() + { + var expected = "072a8d5aa1cc4a16bf04132482748243"; + var context = _provider.GetRequiredService().HttpContext; + var options = _provider.GetRequiredService>(); + var pipeline = _pipeline.Build(); + + context.Request.Headers.Add(options.Value.HeaderName, expected); + + await pipeline(context); + + Assert.True(context.Response.Headers.TryGetValue(options.Value.HeaderName, out var xCorrelationIdHeader)); + Assert.True(ParserFactory.FromGuid().TryParse(xCorrelationIdHeader.Single(), out var correlationId, o => o.Formats = GuidFormats.N)); + Assert.Equal(expected, correlationId.ToString("N")); + + TestOutput.WriteLine(expected); + } + + /// + /// Adds services to the container. + /// + /// The collection of service descriptors. + public override void ConfigureServices(IServiceCollection services) + { + services.AddFakeHttpContextAccessor(ServiceLifetime.Transient); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseCorrelationIdentifier(); + app.UseFakeHttpResponseTrigger(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Tests/Http/Headers/ExceptionTest.cs b/test/Cuemon.AspNetCore.Tests/Http/Headers/ExceptionTest.cs index 792254818..555bc440c 100644 --- a/test/Cuemon.AspNetCore.Tests/Http/Headers/ExceptionTest.cs +++ b/test/Cuemon.AspNetCore.Tests/Http/Headers/ExceptionTest.cs @@ -8,7 +8,7 @@ namespace Cuemon.AspNetCore.Http.Headers { public class ExceptionTest : Test { - public ExceptionTest(ITestOutputHelper output = null) : base(output) + public ExceptionTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.AspNetCore.Tests/Http/Headers/RequestIdentifierMiddlewareTest.cs b/test/Cuemon.AspNetCore.Tests/Http/Headers/RequestIdentifierMiddlewareTest.cs new file mode 100644 index 000000000..4430bb558 --- /dev/null +++ b/test/Cuemon.AspNetCore.Tests/Http/Headers/RequestIdentifierMiddlewareTest.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Cuemon.Extensions.AspNetCore.Http.Headers; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Cuemon.Messaging; +using Cuemon.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Http.Headers +{ + public class RequestIdentifierMiddlewareTest : AspNetCoreHostTest + { + private readonly IServiceProvider _provider; + private readonly IApplicationBuilder _pipeline; + + public RequestIdentifierMiddlewareTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _pipeline = hostFixture.Application; + _provider = hostFixture.ServiceProvider; + } + + [Fact] + public async Task InvokeAsync_ShouldCreateNewRequestIdHeader_ConfiguredByDelegate() + { + var context = _provider.GetRequiredService().HttpContext; + var options = _provider.GetRequiredService>(); + var pipeline = _pipeline.Build(); + + await pipeline(context); + + Assert.True(context.Response.Headers.TryGetValue(options.Value.HeaderName, out var xRequestIdHeader)); + + var requestId = xRequestIdHeader.Single(); + + Assert.False(ParserFactory.FromGuid().TryParse(requestId, out _, o => o.Formats = GuidFormats.N)); + Assert.Equal(32, requestId.Length); + + TestOutput.WriteLine(requestId); + } + + [Fact] + public async Task InvokeAsync_ShouldIgnoreExistingRequestIdHeader_ConfiguredByDefault() + { + var expected = "072a8d5aa1cc4a16bf04132482748243"; + var context = _provider.GetRequiredService().HttpContext; + var options = _provider.GetRequiredService>(); + var pipeline = _pipeline.Build(); + + context.Request.Headers.Add(options.Value.HeaderName, expected); + + await pipeline(context); + + Assert.True(context.Response.Headers.TryGetValue(options.Value.HeaderName, out var xRequestIdHeader)); + + var requestId = xRequestIdHeader.Single(); + + Assert.False(ParserFactory.FromGuid().TryParse(requestId, out _, o => o.Formats = GuidFormats.N)); + Assert.NotEqual(expected, requestId); + Assert.Equal(32, requestId.Length); + + TestOutput.WriteLine(requestId); + } + + /// + /// Adds services to the container. + /// + /// The collection of service descriptors. + public override void ConfigureServices(IServiceCollection services) + { + services.AddFakeHttpContextAccessor(ServiceLifetime.Transient); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseRequestIdentifier(o => o.RequestProvider = () => DynamicRequest.Create(Generate.RandomString(32, Alphanumeric.PunctuationMarks, Alphanumeric.Numbers))); + app.UseFakeHttpResponseTrigger(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Tests/Http/Headers/UserAgentSentinelMiddlewareTest.cs b/test/Cuemon.AspNetCore.Tests/Http/Headers/UserAgentSentinelMiddlewareTest.cs new file mode 100644 index 000000000..fb7bd481c --- /dev/null +++ b/test/Cuemon.AspNetCore.Tests/Http/Headers/UserAgentSentinelMiddlewareTest.cs @@ -0,0 +1,178 @@ +using System.Linq; +using System.Threading.Tasks; +using Cuemon.Extensions.AspNetCore.Http.Headers; +using Cuemon.Extensions.IO; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Http.Headers +{ + public class UserAgentSentinelMiddlewareTest : Test + { + public UserAgentSentinelMiddlewareTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task InvokeAsync_ShouldThrowUserAgentException_BadRequest() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseUserAgentSentinel(); + app.UseFakeHttpResponseTrigger(); + }, services => + { + services.Configure(o => { o.RequireUserAgentHeader = true; }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Scoped); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + var uae = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(uae.Message, options.Value.BadRequestMessage); + Assert.Equal(uae.StatusCode, StatusCodes.Status400BadRequest); + + Assert.True(options.Value.RequireUserAgentHeader); + Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode); + Assert.Equal(options.Value.BadRequestMessage, context.Response.Body.ToEncodedString()); + } + } + + [Fact] + public async Task InvokeAsync_ShouldThrowUserAgentException_Forbidden() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseUserAgentSentinel(); + app.UseFakeHttpResponseTrigger(); + }, services => + { + services.Configure(o => + { + o.RequireUserAgentHeader = true; + o.ValidateUserAgentHeader = true; + o.AllowedUserAgents.Add("Cuemon-Agent"); + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Scoped); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + context.Request.Headers.Add(HeaderNames.UserAgent, "Invalid-Agent"); + + var uae = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(uae.Message, options.Value.ForbiddenMessage); + Assert.Equal(uae.StatusCode, StatusCodes.Status403Forbidden); + + Assert.True(options.Value.RequireUserAgentHeader); + Assert.True(options.Value.ValidateUserAgentHeader); + Assert.True(options.Value.AllowedUserAgents.Any()); + Assert.Equal(StatusCodes.Status403Forbidden, context.Response.StatusCode); + Assert.Equal(options.Value.ForbiddenMessage, context.Response.Body.ToEncodedString()); + } + } + + [Fact] + public async Task InvokeAsync_ShouldThrowUserAgentException_BadRequest_BecauseOfUseGenericResponse() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseUserAgentSentinel(); + app.UseFakeHttpResponseTrigger(); + }, services => + { + services.Configure(o => + { + o.RequireUserAgentHeader = true; + o.ValidateUserAgentHeader = true; + o.UseGenericResponse = true; + o.AllowedUserAgents.Add("Cuemon-Agent"); + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Scoped); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + context.Request.Headers.Add(HeaderNames.UserAgent, "Invalid-Agent"); + + var uae = await Assert.ThrowsAsync(async () => await pipeline(context)); + + Assert.Equal(uae.Message, options.Value.BadRequestMessage); + Assert.Equal(uae.StatusCode, StatusCodes.Status400BadRequest); + + Assert.True(options.Value.RequireUserAgentHeader); + Assert.True(options.Value.ValidateUserAgentHeader); + Assert.True(options.Value.UseGenericResponse); + Assert.True(options.Value.AllowedUserAgents.Any()); + Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode); + Assert.Equal(options.Value.BadRequestMessage, context.Response.Body.ToEncodedString()); + } + } + + [Fact] + public async Task InvokeAsync_ShouldAllowRequestUnconditional() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseUserAgentSentinel(); + app.UseFakeHttpResponseTrigger(); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + await pipeline(context); + + Assert.False(options.Value.RequireUserAgentHeader); + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + + [Fact] + public async Task InvokeAsync_ShouldAllowRequestAfterBeingValidated() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseUserAgentSentinel(); + app.UseFakeHttpResponseTrigger(); + }, services => + { + services.Configure(o => + { + o.RequireUserAgentHeader = true; + o.ValidateUserAgentHeader = true; + o.AllowedUserAgents.Add("Cuemon-Agent"); + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Scoped); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + context.Request.Headers.Add(HeaderNames.UserAgent, "Cuemon-Agent"); + + await pipeline(context); + + Assert.True(options.Value.RequireUserAgentHeader); + Assert.True(options.Value.ValidateUserAgentHeader); + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.AspNetCore.Tests/Http/Throttling/ExceptionTest.cs b/test/Cuemon.AspNetCore.Tests/Http/Throttling/ExceptionTest.cs index 842d9092d..2b9d13ee7 100644 --- a/test/Cuemon.AspNetCore.Tests/Http/Throttling/ExceptionTest.cs +++ b/test/Cuemon.AspNetCore.Tests/Http/Throttling/ExceptionTest.cs @@ -9,7 +9,7 @@ namespace Cuemon.AspNetCore.Http.Throttling { public class ExceptionTest : Test { - public ExceptionTest(ITestOutputHelper output = null) : base(output) + public ExceptionTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.AspNetCore.Tests/Http/Throttling/ThrottlingSentinelMiddlewareTest.cs b/test/Cuemon.AspNetCore.Tests/Http/Throttling/ThrottlingSentinelMiddlewareTest.cs new file mode 100644 index 000000000..b8439e02f --- /dev/null +++ b/test/Cuemon.AspNetCore.Tests/Http/Throttling/ThrottlingSentinelMiddlewareTest.cs @@ -0,0 +1,107 @@ +using System; +using System.Threading.Tasks; +using Cuemon.Extensions.AspNetCore.Http.Throttling; +using Cuemon.Extensions.IO; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.AspNetCore.Http.Throttling +{ + public class ThrottlingSentinelMiddlewareTest : Test + { + public ThrottlingSentinelMiddlewareTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task InvokeAsync_ShouldThrowThrottlingException_TooManyRequests() + { + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseThrottlingSentinel(); + app.UseFakeHttpResponseTrigger(); + }, services => + { + services.Configure(o => + { + o.Quota = new ThrottleQuota(10, TimeSpan.FromMinutes(5)); + o.ContextResolver = cr => nameof(ThrottlingSentinelMiddlewareTest); + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + services.AddMemoryThrottlingCache(); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var cache = middleware.ServiceProvider.GetRequiredService(); + var pipeline = middleware.Application.Build(); + + var te = await Assert.ThrowsAsync(async () => + { + for (var i = 0; i < 15; i++) + { + await pipeline(context); + } + }); + + var ce = cache[nameof(ThrottlingSentinelMiddlewareTest)]; + Assert.InRange(ce.Total, te.RateLimit, 15); + + Assert.Equal(te.RateLimit, options.Value.Quota.RateLimit); + Assert.Equal(te.Message, options.Value.TooManyRequestsMessage); + Assert.Equal(te.StatusCode, StatusCodes.Status429TooManyRequests); + + Assert.True(options.Value.UseRetryAfterHeader); + Assert.Equal(StatusCodes.Status429TooManyRequests, context.Response.StatusCode); + Assert.Equal(options.Value.TooManyRequestsMessage, context.Response.Body.ToEncodedString()); + } + } + + [Fact] + public async Task InvokeAsync_ShouldRehydrate() + { + var window = TimeSpan.FromSeconds(5); + using (var middleware = MiddlewareTestFactory.CreateMiddlewareTest(app => + { + app.UseThrottlingSentinel(); + app.UseFakeHttpResponseTrigger(); + }, services => + { + services.Configure(o => + { + o.Quota = new ThrottleQuota(10, window); + o.ContextResolver = cr => nameof(ThrottlingSentinelMiddlewareTest); + }); + services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton); + services.AddMemoryThrottlingCache(); + })) + { + var context = middleware.ServiceProvider.GetRequiredService().HttpContext; + var options = middleware.ServiceProvider.GetRequiredService>(); + var pipeline = middleware.Application.Build(); + + for (var i = 0; i < 10; i++) + { + await pipeline(context); + } + + var te = await Assert.ThrowsAsync(async () => await pipeline(context)); + + TestOutput.WriteLine(te.Delta.ToString()); + + await Task.Delay(te.Delta.Add(TimeSpan.FromSeconds(1))); + + await pipeline(context); + + Assert.True(window >= te.Delta, "window >= te.Delta"); + Assert.True(options.Value.UseRetryAfterHeader); + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + } + } + } +} \ No newline at end of file diff --git a/src/Cuemon.Core/Globalization/CultureInfo.SpecificCultures.dsv b/test/Cuemon.Core.Tests/Assets/CultureInfo.SpecificCultures.dsv similarity index 100% rename from src/Cuemon.Core/Globalization/CultureInfo.SpecificCultures.dsv rename to test/Cuemon.Core.Tests/Assets/CultureInfo.SpecificCultures.dsv diff --git a/test/Cuemon.Core.Tests/Assets/UnmanagedDisposable.cs b/test/Cuemon.Core.Tests/Assets/UnmanagedDisposable.cs index 163e53893..443f6a119 100644 --- a/test/Cuemon.Core.Tests/Assets/UnmanagedDisposable.cs +++ b/test/Cuemon.Core.Tests/Assets/UnmanagedDisposable.cs @@ -5,33 +5,47 @@ namespace Cuemon.Assets { public class UnmanagedDisposable : FinalizeDisposable { - [DllImport("kernel32.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public static extern IntPtr CreateFile( - string lpFileName, + internal IntPtr _handle = IntPtr.Zero; + internal IntPtr _libHandle = IntPtr.Zero; + + public delegate bool CloseHandle(IntPtr hObject); + + public delegate IntPtr CreateFileDelegate(string lpFileName, uint dwDesiredAccess, uint dwShareMode, - IntPtr SecurityAttributes, + IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, - IntPtr hTemplateFile - ); - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - static extern bool CloseHandle(IntPtr hObject); + IntPtr hTemplateFile); - public IntPtr _handle = IntPtr.Zero; + public delegate IntPtr PtSname(int fd); public UnmanagedDisposable() { - _handle = CreateFile(@"C:\TestFile.txt", - 0x80000000, //access read-only - 1, //share-read - IntPtr.Zero, - 3, //open existing - 0, - IntPtr.Zero); + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + if (NativeLibrary.TryLoad("kernel32.dll", GetType().Assembly, DllImportSearchPath.System32, out _libHandle)) + { + if (NativeLibrary.TryGetExport(_libHandle, "CreateFileW", out var functionHandle)) + { + var createFileFunc = Marshal.GetDelegateForFunctionPointer(functionHandle); + _handle = createFileFunc(@"C:\TestFile.txt", + 0x80000000, //access read-only + 1, //share-read + IntPtr.Zero, + 3, //open existing + 0, + IntPtr.Zero); + } + } + } + else if (Environment.OSVersion.Platform == PlatformID.Unix) + { + if (NativeLibrary.TryLoad("libc.so.6", GetType().Assembly, DllImportSearchPath.SafeDirectories, out _libHandle)) + { + _handle = _libHandle; // i don't know of any native methods on unix + } + } } protected override void OnDisposeManagedResources() @@ -41,9 +55,21 @@ protected override void OnDisposeManagedResources() protected override void OnDisposeUnmanagedResources() { - if (_handle != IntPtr.Zero) + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + if (_handle != IntPtr.Zero) + { + if (NativeLibrary.TryGetExport(_libHandle, "CloseHandle", out var closeHandle)) + { + var closeHandleAction = Marshal.GetDelegateForFunctionPointer(closeHandle); + closeHandleAction(_handle); + } + } + NativeLibrary.Free(_libHandle); + } + else if (Environment.OSVersion.Platform == PlatformID.Unix) { - CloseHandle(_handle); + NativeLibrary.Free(_libHandle); } } } diff --git a/test/Cuemon.Core.Tests/BinaryPrefixTest.cs b/test/Cuemon.Core.Tests/BinaryPrefixTest.cs new file mode 100644 index 000000000..302d0ed72 --- /dev/null +++ b/test/Cuemon.Core.Tests/BinaryPrefixTest.cs @@ -0,0 +1,26 @@ +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon +{ + public class BinaryPrefixTest : Test + { + public BinaryPrefixTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void BinaryPrefix_ShouldVerifyMultiplePrefixConstants() + { + Assert.Equal(BinaryPrefix.Kibi.Multiplier, 1024d); + Assert.Equal(BinaryPrefix.Mebi.Multiplier, 1048576d); + Assert.Equal(BinaryPrefix.Gibi.Multiplier, 1073741824d); + Assert.Equal(BinaryPrefix.Tebi.Multiplier, 1099511627776d); + Assert.Equal(BinaryPrefix.Pebi.Multiplier, 1125899906842624d); + Assert.Equal(BinaryPrefix.Exbi.Multiplier, 1152921504606846976d); + Assert.Equal(BinaryPrefix.Zebi.Multiplier, 1180591620717411303424d); + Assert.Equal(BinaryPrefix.Yobi.Multiplier, 1208925819614629174706176d); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Core.Tests/BitMultipleTableTest.cs b/test/Cuemon.Core.Tests/BitMultipleTableTest.cs new file mode 100644 index 000000000..4c0f3f87f --- /dev/null +++ b/test/Cuemon.Core.Tests/BitMultipleTableTest.cs @@ -0,0 +1,133 @@ +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon +{ + public class BitMultipleTableTest : Test + { + public BitMultipleTableTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void BitMultipleTable_ShouldBeEqualWithAFactorOfEight() + { + var x = BitMultipleTable.FromBytes(1000000000); + var y = BitMultipleTable.FromBits(8000000000); + Assert.Equal(x, y); + } + + [Fact] + public void BitMultipleTable_UseSymbol_ShouldConvertOneBillionBitsToBinaryAndMetricPrefixToStringRepresentation() + { + var x = BitMultipleTable.FromBits(1000000000); + + Assert.Equal("0 Pib", x.Pebi.ToString()); + Assert.Equal("0 Tib", x.Tebi.ToString()); + Assert.Equal("0.93 Gib", x.Gibi.ToString()); + Assert.Equal("953.67 Mib", x.Mebi.ToString()); + Assert.Equal("976,562.5 Kib", x.Kibi.ToString()); + + Assert.Equal("0 Pb", x.Peta.ToString()); + Assert.Equal("0 Tb", x.Tera.ToString()); + Assert.Equal("1 Gb", x.Giga.ToString()); + Assert.Equal("1,000 Mb", x.Mega.ToString()); + Assert.Equal("1,000,000 kb", x.Kilo.ToString()); + + Assert.Equal("1,000,000,000 b", x.Unit.ToString()); + + TestOutput.WriteLine(x.ToAggregateString()); + } + + [Fact] + public void BitMultipleTable_UseCompound_ShouldConvertOneBillionBitsToBinaryAndMetricPrefixToStringRepresentation() + { + var x = BitMultipleTable.FromBits(1000000000, o => o.Style = NamingStyle.Compound); + + Assert.Equal("0 pebibit", x.Pebi.ToString()); + Assert.Equal("0 tebibit", x.Tebi.ToString()); + Assert.Equal("0.93 gibibit", x.Gibi.ToString()); + Assert.Equal("953.67 mebibit", x.Mebi.ToString()); + Assert.Equal("976,562.5 kibibit", x.Kibi.ToString()); + + Assert.Equal("0 petabit", x.Peta.ToString()); + Assert.Equal("0 terabit", x.Tera.ToString()); + Assert.Equal("1 gigabit", x.Giga.ToString()); + Assert.Equal("1,000 megabit", x.Mega.ToString()); + Assert.Equal("1,000,000 kilobit", x.Kilo.ToString()); + + Assert.Equal("1,000,000,000 bit", x.Unit.ToString()); + + TestOutput.WriteLine(x.ToAggregateString()); + } + + public void BitMultipleTable_UseCompound_ShouldConvertOneBillionBitsToBinaryAndMetricPrefixDoubleRepresentation() + { + var bs = BitMultipleTable.FromBits(1000000000, o => + { + o.Style = NamingStyle.Compound; + o.Prefix = UnitPrefix.Decimal; + }); + + Assert.Equal(1000000000, (double)bs); + Assert.Equal(1000000, bs.Kilo.PrefixValue); + Assert.Equal(1000, bs.Mega.PrefixValue); + Assert.Equal(1, bs.Giga.PrefixValue); + Assert.Equal(0.001, bs.Tera.PrefixValue); + Assert.Equal(1E-06, bs.Peta.PrefixValue); + Assert.Equal(976562.5, bs.Kibi.PrefixValue); + Assert.Equal(953.67431640625, bs.Mebi.PrefixValue); + Assert.Equal(0.93132257461547852, bs.Gibi.PrefixValue); + Assert.Equal(0.00090949470177292824, bs.Tebi.PrefixValue); + Assert.Equal(8.8817841970012523E-07, bs.Pebi.PrefixValue); + Assert.Equal("1 gigabit", bs.ToString()); + + TestOutput.WriteLine(bs.ToString()); + } + + [Fact] + public void BitMultipleTable_UseSymbol_ShouldConvertOneBillionBytesToBinaryAndMetricPrefixToStringRepresentation() + { + var x = ByteMultipleTable.FromBytes(1000000000); + + Assert.Equal("0 PiB", x.Pebi.ToString()); + Assert.Equal("0 TiB", x.Tebi.ToString()); + Assert.Equal("0.93 GiB", x.Gibi.ToString()); + Assert.Equal("953.67 MiB", x.Mebi.ToString()); + Assert.Equal("976,562.5 KiB", x.Kibi.ToString()); + + Assert.Equal("0 PB", x.Peta.ToString()); + Assert.Equal("0 TB", x.Tera.ToString()); + Assert.Equal("1 GB", x.Giga.ToString()); + Assert.Equal("1,000 MB", x.Mega.ToString()); + Assert.Equal("1,000,000 kB", x.Kilo.ToString()); + + Assert.Equal("1,000,000,000 B", x.Unit.ToString()); + + TestOutput.WriteLine(x.ToAggregateString()); + } + + [Fact] + public void BitMultipleTable_UseCompound_ShouldConvertOneBillionBytesToBinaryAndMetricPrefixToStringRepresentation() + { + var x = ByteMultipleTable.FromBytes(1000000000, o => o.Style = NamingStyle.Compound); + + Assert.Equal("0 pebibyte", x.Pebi.ToString()); + Assert.Equal("0 tebibyte", x.Tebi.ToString()); + Assert.Equal("0.93 gibibyte", x.Gibi.ToString()); + Assert.Equal("953.67 mebibyte", x.Mebi.ToString()); + Assert.Equal("976,562.5 kibibyte", x.Kibi.ToString()); + + Assert.Equal("0 petabyte", x.Peta.ToString()); + Assert.Equal("0 terabyte", x.Tera.ToString()); + Assert.Equal("1 gigabyte", x.Giga.ToString()); + Assert.Equal("1,000 megabyte", x.Mega.ToString()); + Assert.Equal("1,000,000 kilobyte", x.Kilo.ToString()); + + Assert.Equal("1,000,000,000 byte", x.Unit.ToString()); + + TestOutput.WriteLine(x.ToAggregateString()); + } + } +} diff --git a/test/Cuemon.Core.Tests/CalculatorTest.cs b/test/Cuemon.Core.Tests/CalculatorTest.cs index 5d167c728..4878f71a9 100644 --- a/test/Cuemon.Core.Tests/CalculatorTest.cs +++ b/test/Cuemon.Core.Tests/CalculatorTest.cs @@ -6,7 +6,7 @@ namespace Cuemon { public class CalculatorTest : Test { - public CalculatorTest(ITestOutputHelper output = null) : base(output) + public CalculatorTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Core.Tests/Collections/Generic/DictionaryDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/Collections/Generic/DictionaryDecoratorExtensionsTest.cs index 0db4ab300..fb95c59fd 100644 --- a/test/Cuemon.Core.Tests/Collections/Generic/DictionaryDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/Collections/Generic/DictionaryDecoratorExtensionsTest.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using AutoFixture; using Xunit; namespace Cuemon.Collections.Generic @@ -10,8 +9,7 @@ public class DictionaryDecoratorExtensionsTest [Fact] public void Extend_Dictionary_With_GetValueOrDefault_Expect_Actual_Value_At_Key_42() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var result = Decorator.Enclose(dic).GetValueOrDefault(42); Assert.Equal(dic[42], result); } @@ -19,8 +17,7 @@ public void Extend_Dictionary_With_GetValueOrDefault_Expect_Actual_Value_At_Key_ [Fact] public void Extend_Dictionary_With_GetValueOrDefault_Expect_Default_Provided_Value() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var expected = Generate.RandomString(24); var result = Decorator.Enclose(dic).GetValueOrDefault(700, () => expected); Assert.Equal(expected, result); @@ -29,8 +26,7 @@ public void Extend_Dictionary_With_GetValueOrDefault_Expect_Default_Provided_Val [Fact] public void Extend_Dictionary_With_TryGetValueOrFallback_Expect_Actual_Value_At_Key_42() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var found = Decorator.Enclose(dic).TryGetValueOrFallback(42, keys => keys.Max(), out var result); Assert.True(found); Assert.Equal(dic[42], result); @@ -39,8 +35,7 @@ public void Extend_Dictionary_With_TryGetValueOrFallback_Expect_Actual_Value_At_ [Fact] public void Extend_Dictionary_With_TryGetValueOrFallback_Expect_Fallback_Value_At_Key_42() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var found = Decorator.Enclose(dic).TryGetValueOrFallback(-1, keys => 42, out var result); Assert.True(found); Assert.Equal(dic[42], result); @@ -49,8 +44,7 @@ public void Extend_Dictionary_With_TryGetValueOrFallback_Expect_Fallback_Value_A [Fact] public void Extend_Dictionary_With_TryGetValueOrFallback_Expect_Not_Found() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var found = Decorator.Enclose(dic).TryGetValueOrFallback(-1, keys => -42, out var result); Assert.False(found); Assert.Equal(default, result); @@ -59,8 +53,7 @@ public void Extend_Dictionary_With_TryGetValueOrFallback_Expect_Not_Found() [Fact] public void Extend_Dictionary_With_ToEnumerable_Expect_Sequence() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var result = Decorator.Enclose(dic).ToEnumerable(); Assert.Equal(dic, result); Assert.IsAssignableFrom>>(result); @@ -69,8 +62,7 @@ public void Extend_Dictionary_With_ToEnumerable_Expect_Sequence() [Fact] public void Extend_Dictionary_With_TryAdd_Expect_True() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); Assert.Equal(500, dic.Count); var added = Decorator.Enclose(dic).TryAdd(501, "First Legion"); Assert.True(added); @@ -80,8 +72,7 @@ public void Extend_Dictionary_With_TryAdd_Expect_True() [Fact] public void Extend_Dictionary_With_TryAdd_Expect_False() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var key = dic.Keys.Last(); Assert.Equal(500, dic.Count); var added = Decorator.Enclose(dic).TryAdd(key, "First Legion"); @@ -92,31 +83,26 @@ public void Extend_Dictionary_With_TryAdd_Expect_False() [Fact] public void Extend_Dictionary_With_TryAddOrUpdate_Add_And_Update_Expect_True() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var key = 501; var txt = "First Legion"; var subtxt = " Rules the Galaxy"; Assert.Equal(500, dic.Count); - var added = Decorator.Enclose(dic).TryAddOrUpdate(key, txt); - Assert.True(added); + Decorator.Enclose(dic).AddOrUpdate(key, txt); Assert.Equal(txt, dic[key]); - var updated = Decorator.Enclose(dic).TryAddOrUpdate(key, txt + subtxt); - Assert.True(updated); + Decorator.Enclose(dic).AddOrUpdate(key, txt + subtxt); Assert.Equal(string.Concat(txt, subtxt), dic[key]); } [Fact] public void Extend_Dictionary_With_TryAddOrUpdate_Update_Expect_True() { - var fixture = new Fixture(); - var dic = fixture.CreateMany>(500).ToDictionary(x => x.Key, x => x.Value); + var dic = Generate.RangeOf(500, x => new KeyValuePair(x, Generate.RandomString(24))).ToDictionary(x => x.Key, x => x.Value); var elm = dic.Last(); var txt = "First Legion"; Assert.Equal(500, dic.Count); Assert.Equal(elm.Value, dic.Last().Value); - var updated = Decorator.Enclose(dic).TryAddOrUpdate(elm.Key, txt); - Assert.True(updated); + Decorator.Enclose(dic).AddOrUpdate(elm.Key, txt); Assert.Equal(txt, dic.Last().Value); Assert.Equal(500, dic.Count); } diff --git a/test/Cuemon.Core.Tests/Cuemon.Core.Tests.csproj b/test/Cuemon.Core.Tests/Cuemon.Core.Tests.csproj index 6d485cb89..957fa6fb4 100644 --- a/test/Cuemon.Core.Tests/Cuemon.Core.Tests.csproj +++ b/test/Cuemon.Core.Tests/Cuemon.Core.Tests.csproj @@ -1,38 +1,24 @@ - netcoreapp3.0 - - false - Cuemon - - x64 - + + + - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + - + + - + \ No newline at end of file diff --git a/test/Cuemon.Core.Tests/DecimalPrefixTest.cs b/test/Cuemon.Core.Tests/DecimalPrefixTest.cs new file mode 100644 index 000000000..588a87ce4 --- /dev/null +++ b/test/Cuemon.Core.Tests/DecimalPrefixTest.cs @@ -0,0 +1,28 @@ +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon +{ + public class DecimalPrefixTest : Test + { + public DecimalPrefixTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void DecimalPrefix_ShouldVerifyMultiplePrefixConstants() + { + Assert.Equal(DecimalPrefix.Deca.Multiplier, 10d); + Assert.Equal(DecimalPrefix.Hecto.Multiplier, 100d); + Assert.Equal(DecimalPrefix.Kilo.Multiplier, 1000d); + Assert.Equal(DecimalPrefix.Mega.Multiplier, 1000000d); + Assert.Equal(DecimalPrefix.Giga.Multiplier, 1000000000d); + Assert.Equal(DecimalPrefix.Tera.Multiplier, 1000000000000d); + Assert.Equal(DecimalPrefix.Peta.Multiplier, 1000000000000000d); + Assert.Equal(DecimalPrefix.Exa.Multiplier, 1000000000000000000d); + Assert.Equal(DecimalPrefix.Zetta.Multiplier, 1000000000000000000000d); + Assert.Equal(DecimalPrefix.Yotta.Multiplier, 1000000000000000000000000d); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Core.Tests/DelimitedStringTest.cs b/test/Cuemon.Core.Tests/DelimitedStringTest.cs new file mode 100644 index 000000000..0e88f4d06 --- /dev/null +++ b/test/Cuemon.Core.Tests/DelimitedStringTest.cs @@ -0,0 +1,59 @@ +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon +{ + public class DelimitedStringTest : Test + { + public DelimitedStringTest(ITestOutputHelper output) : base(output) + { + } + + + [Fact] + public void Split_ShouldSplitPreservingQualifierOfTypeDoubleQuote() + { + var s1 = "1999,Chevy,\"Venture \"\"Extended Edition, Very Large\"\"\",,5000.00"; + var s2 = "realm=\"unittest\", qop=\"auth, auth-int\", nonce=\"MjAyMC0xMC0yMCAyMzoxMzo0MFo6OWY1NmRjZTY0NWI3YjY5YjhlM2NlOTFhNDM2ZWI2ZGFiNDIxYzY5MjU4YzI1YTBkNDg1M2RkYTQ2NmRkOWJkNg==\""; + + var ds1 = DelimitedString.Split(s1); + var ds2 = DelimitedString.Split(s2); + + TestOutput.WriteLine("---- ds1 -----"); + + TestOutput.WriteLine(s1); + + TestOutput.WriteLine("---- ds2 -----"); + + TestOutput.WriteLine(s2); + + TestOutput.WriteLine("---- foreach sc in ds1 -----"); + + foreach (var sc in ds1) + { + TestOutput.WriteLine(sc); + } + + TestOutput.WriteLine("---- foreach sc in ds2 -----"); + + foreach (var sc in ds2) + { + TestOutput.WriteLine(sc); + } + + TestOutput.WriteLine(new string('-', 5)); + + var j1 = DelimitedString.Create(ds1); + var j2 = DelimitedString.Create(ds2); + + TestOutput.WriteLine(j1); + + Assert.Equal(s1, j1); + Assert.Equal(s2, j2); + + Assert.True(ds1.Length == 5); + Assert.True(ds2.Length == 3); + } + } +} diff --git a/test/Cuemon.Core.Tests/DisposableTest.cs b/test/Cuemon.Core.Tests/DisposableTest.cs index 5b2289883..c15335821 100644 --- a/test/Cuemon.Core.Tests/DisposableTest.cs +++ b/test/Cuemon.Core.Tests/DisposableTest.cs @@ -12,7 +12,7 @@ namespace Cuemon { public class DisposableTest : Test { - public DisposableTest(ITestOutputHelper output = null) : base(output) + public DisposableTest(ITestOutputHelper output) : base(output) { } @@ -22,7 +22,7 @@ public void SafeInvoke_ShouldAbideRuleCA2000() { var guid = Guid.NewGuid(); var called = 0; - var stream = Disposable.SafeInvoke(() => new MemoryStream(), ms => + var stream = Patterns.SafeInvoke(() => new MemoryStream(), ms => { called++; ms.WriteByte(1); @@ -35,7 +35,7 @@ public void SafeInvoke_ShouldAbideRuleCA2000() MemoryStream msRef = null; called = 0; - stream = Disposable.SafeInvoke(() => new MemoryStream(), (ms, g) => + stream = Patterns.SafeInvoke(() => new MemoryStream(), (ms, g) => { msRef = ms; Assert.Equal(guid, g); @@ -49,7 +49,7 @@ public void SafeInvoke_ShouldAbideRuleCA2000() Assert.Null(stream); Assert.Throws(() => msRef.Length); - stream = Disposable.SafeInvoke(() => new MemoryStream(), (ms, n1, n2, n3, n4, n5) => + stream = Patterns.SafeInvoke(() => new MemoryStream(), (ms, n1, n2, n3, n4, n5) => { called++; ms.Write(Decorator.Enclose($"{n1}{n2}{n3}{n4}{n5}").ToByteArray()); @@ -67,7 +67,7 @@ public async Task SafeInvokeAsync_ShouldAbideRuleCA2000() { var guid = Guid.NewGuid(); var called = 0; - var stream = await Disposable.SafeInvokeAsync(() => new MemoryStream(), async (ms, ct) => + var stream = await Patterns.SafeInvokeAsync(() => new MemoryStream(), async (ms, ct) => { called++; await ms.WriteAsync(new byte[] { 1 }, ct); @@ -80,7 +80,7 @@ public async Task SafeInvokeAsync_ShouldAbideRuleCA2000() MemoryStream msRef = null; called = 0; - stream = await Disposable.SafeInvokeAsync(() => new MemoryStream(), (ms, g, ct) => + stream = await Patterns.SafeInvokeAsync(() => new MemoryStream(), (ms, g, ct) => { msRef = ms; Assert.Equal(guid, g); @@ -95,28 +95,31 @@ public async Task SafeInvokeAsync_ShouldAbideRuleCA2000() Assert.Null(stream); Assert.Throws(() => msRef.Length); - var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(5)); - msRef = null; - called = 0; - stream = await Disposable.SafeInvokeAsync(() => new MemoryStream(), async (ms, g, ct) => - { - msRef = ms; - Assert.Equal(guid, g); - await Task.Delay(TimeSpan.FromSeconds(1)); - await ms.WriteAsync(new byte[] { 1 }, ct); - ms.Position = 0; - return ms; - }, guid, ctsShouldFail.Token, (exception, g, ct) => + await Assert.ThrowsAsync(async () => { - Assert.Equal(guid, g); - Assert.True(exception is TaskCanceledException); - return Task.CompletedTask; + var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(5)); + msRef = null; + called = 0; + stream = await Patterns.SafeInvokeAsync(() => new MemoryStream(), async (ms, g, ct) => + { + msRef = ms; + Assert.Equal(guid, g); + await Task.Delay(TimeSpan.FromSeconds(1)); + await ms.WriteAsync(new byte[] {1}, ct); + ms.Position = 0; + return ms; + }, guid, ctsShouldFail.Token, (exception, g, ct) => + { + Assert.Equal(guid, g); + Assert.True(exception is TaskCanceledException); + return Task.CompletedTask; + }); + Assert.Equal(0, called); + Assert.Null(stream); + Assert.Throws(() => msRef.Length); }); - Assert.Equal(0, called); - Assert.Null(stream); - Assert.Throws(() => msRef.Length); - stream = await Disposable.SafeInvokeAsync(() => new MemoryStream(), async (ms, n1, n2, n3, n4, n5, ct) => + stream = await Patterns.SafeInvokeAsync(() => new MemoryStream(), async (ms, n1, n2, n3, n4, n5, ct) => { called++; var bytes = Decorator.Enclose($"{n1}{n2}{n3}{n4}{n5}").ToByteArray(); @@ -154,6 +157,7 @@ public void UnmanagedDisposable_VerifyThatAssetIsBeingDisposedOnFinalize() Action body = () => { var o = new UnmanagedDisposable(); + Assert.NotEqual(IntPtr.Zero, o._libHandle); Assert.NotEqual(IntPtr.Zero, o._handle); unmanaged = new WeakReference(o, true); }; @@ -168,7 +172,7 @@ public void UnmanagedDisposable_VerifyThatAssetIsBeingDisposedOnFinalize() GC.WaitForPendingFinalizers(); } - Thread.Sleep(500); + Thread.Sleep(3500); // await GC if (unmanaged.TryGetTarget(out var ud2)) { diff --git a/test/Cuemon.Core.Tests/ExceptionTest.cs b/test/Cuemon.Core.Tests/ExceptionTest.cs index 94f3bb306..e3216d11c 100644 --- a/test/Cuemon.Core.Tests/ExceptionTest.cs +++ b/test/Cuemon.Core.Tests/ExceptionTest.cs @@ -8,7 +8,7 @@ namespace Cuemon { public class ExceptionTest : Test { - public ExceptionTest(ITestOutputHelper output = null) : base(output) + public ExceptionTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Core.Tests/GenerateTest.cs b/test/Cuemon.Core.Tests/GenerateTest.cs index 0bfb84597..94608390a 100644 --- a/test/Cuemon.Core.Tests/GenerateTest.cs +++ b/test/Cuemon.Core.Tests/GenerateTest.cs @@ -8,7 +8,7 @@ namespace Cuemon { public class GenerateTest : Test { - public GenerateTest(ITestOutputHelper output = null) : base(output) + public GenerateTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Core.Tests/GlobalSuppressions.cs b/test/Cuemon.Core.Tests/GlobalSuppressions.cs deleted file mode 100644 index 4fbc51c9c..000000000 --- a/test/Cuemon.Core.Tests/GlobalSuppressions.cs +++ /dev/null @@ -1,10 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.DisposableTest.SafeInvokeAsync_ShouldAbideRuleCA2000~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("Minor Code Smell", "S3604:Member initializer values should not be redundant", Justification = "", Scope = "member", Target = "~F:Cuemon.Assets.UnmanagedDisposable._handle")] -[assembly: SuppressMessage("Critical Code Smell", "S1215:\"GC.Collect\" should not be called", Justification = "", Scope = "member", Target = "~M:Cuemon.DisposableTest.UnmanagedDisposable_VerifyThatAssetIsBeingDisposedOnFinalize")] diff --git a/test/Cuemon.Core.Tests/ObjectDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/ObjectDecoratorExtensionsTest.cs index d1fc91e1b..fef123421 100644 --- a/test/Cuemon.Core.Tests/ObjectDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/ObjectDecoratorExtensionsTest.cs @@ -10,7 +10,7 @@ public class ObjectDecoratorExtensionsTest : Test { private readonly string _number = $"{Generate.RandomString(5, Alphanumeric.Numbers)},{Generate.RandomNumber(0, 99):D2}"; - public ObjectDecoratorExtensionsTest(ITestOutputHelper output = null) : base(output) + public ObjectDecoratorExtensionsTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs index 1170067d7..381ea6900 100644 --- a/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.Linq; +using Cuemon.Assets; using Cuemon.Extensions.Xunit; using Xunit; using Xunit.Abstractions; @@ -9,7 +10,7 @@ namespace Cuemon.Reflection { public class AssemblyDecoratorExtensionsTest : Test { - public AssemblyDecoratorExtensionsTest(ITestOutputHelper output = null) : base(output) + public AssemblyDecoratorExtensionsTest(ITestOutputHelper output) : base(output) { } @@ -36,8 +37,8 @@ public void GetTypes_ShouldReturnAllTypesFromCuemonCore() var disposableTypesCount = Decorator.Enclose(disposableTypes).Inner.Count(); var configurationTypesCount = Decorator.Enclose(configurationTypes).Inner.Count(); - Assert.Equal(532, allTypesCount); - Assert.Equal(7, disposableTypesCount); + Assert.InRange(allTypesCount, 455, 475); // range because of tooling on CI adding dynamic types and high range of refactoring + Assert.Equal(4, disposableTypesCount); Assert.Equal(2, configurationTypesCount); } @@ -46,7 +47,9 @@ public void GetAssemblyVersion_ShouldReturnAssemblyVersion() { var a = typeof(Disposable).Assembly; var v = Decorator.Enclose(a).GetAssemblyVersion(); - Assert.Equal("6.0.2020.0", v.ToString()); + Assert.Equal("6.0.0.0", v.ToString()); + Assert.True(v.HasAlphanumericVersion); + Assert.False(v.IsSemanticVersion()); } [Fact] @@ -54,7 +57,9 @@ public void GetFileVersion_ShouldReturnFileVersion() { var a = typeof(Disposable).Assembly; var v = Decorator.Enclose(a).GetFileVersion(); - Assert.Equal("6.0.2020.25", v.ToString()); + Assert.False(v.IsSemanticVersion()); + Assert.True(v.HasAlphanumericVersion); + Assert.Equal("6.0.0", v.ToString()); } [Fact] @@ -62,14 +67,17 @@ public void GetProductVersion_ShouldReturnProductVersion() { var a = typeof(Disposable).Assembly; var v = Decorator.Enclose(a).GetProductVersion(); - Assert.Equal("6.0.2020.25", v.ToString()); + Assert.True(v.IsSemanticVersion()); + Assert.True(v.HasAlphanumericVersion); + Assert.Equal("6.0", v.ToVersion().ToString()); + Assert.Contains("-preview", v.ToString()); } [Fact] public void GetManifestResources_ShouldRetrieveCultureInfoSpecificCultures() { - var a = typeof(Disposable).Assembly; - var erbn = Decorator.Enclose(a).GetManifestResources($"{nameof(Cuemon)}.{nameof(Globalization)}.CultureInfo.SpecificCultures.dsv"); + var a = typeof(ClassBase).Assembly; + var erbn = Decorator.Enclose(a).GetManifestResources($"{nameof(Cuemon)}.{nameof(Assets)}.CultureInfo.SpecificCultures.dsv"); var erbnv = erbn.Single().Value; var erbce = Decorator.Enclose(a).GetManifestResources(".d", ManifestResourceMatch.ContainsExtension); var erbcev = erbn.Single().Value; diff --git a/test/Cuemon.Core.Tests/Reflection/MethodInfoDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/Reflection/MethodInfoDecoratorExtensionsTest.cs index 7ee729a97..3c307b210 100644 --- a/test/Cuemon.Core.Tests/Reflection/MethodInfoDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/Reflection/MethodInfoDecoratorExtensionsTest.cs @@ -7,7 +7,7 @@ namespace Cuemon.Reflection { public class MethodInfoDecoratorExtensionsTest : Test { - public MethodInfoDecoratorExtensionsTest(ITestOutputHelper output = null) : base(output) + public MethodInfoDecoratorExtensionsTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Core.Tests/Reflection/PropertyInfoDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/Reflection/PropertyInfoDecoratorExtensionsTest.cs index bfc9a00ab..342ee1555 100644 --- a/test/Cuemon.Core.Tests/Reflection/PropertyInfoDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/Reflection/PropertyInfoDecoratorExtensionsTest.cs @@ -8,7 +8,7 @@ namespace Cuemon.Reflection { public class PropertyInfoDecoratorExtensionsTest : Test { - public PropertyInfoDecoratorExtensionsTest(ITestOutputHelper output = null) : base(output) + public PropertyInfoDecoratorExtensionsTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Core.Tests/Security/Cryptography/HashFactoryTest.cs b/test/Cuemon.Core.Tests/Security/HashFactoryTest.cs similarity index 60% rename from test/Cuemon.Core.Tests/Security/Cryptography/HashFactoryTest.cs rename to test/Cuemon.Core.Tests/Security/HashFactoryTest.cs index b642372ce..5a4eb52f2 100644 --- a/test/Cuemon.Core.Tests/Security/Cryptography/HashFactoryTest.cs +++ b/test/Cuemon.Core.Tests/Security/HashFactoryTest.cs @@ -1,17 +1,27 @@ -using System; -using System.Text; +using System.Text; using Cuemon.Extensions.Xunit; using Xunit; using Xunit.Abstractions; -namespace Cuemon.Security.Cryptography +namespace Cuemon.Security { public class HashFactoryTest : Test { - public HashFactoryTest(ITestOutputHelper output = null) : base(output) + public HashFactoryTest(ITestOutputHelper output) : base(output) { } + [Fact] + public void CreateFnv64_Fnv1_ShouldHaveSizeOf64Bits() + { + var s1 = "957-KEY"; + var s2 = "958-KEY"; + var hf = HashFactory.CreateFnv64(o => o.Algorithm = FowlerNollVoAlgorithm.Fnv1a); + + TestOutput.WriteLine(hf.ComputeHash(s1).ToHexadecimalString()); + TestOutput.WriteLine(hf.ComputeHash(s2).ToHexadecimalString()); + } + [Fact] public void CreateCrc_Crc64_ShouldBeValidHashResult() { @@ -139,113 +149,6 @@ public void CreateCrc_Crc32CdRomEdc_ShouldBeValidHashResult() Assert.Equal("D9B8B5E9", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString().ToUpper()); } - [Fact] - public void CreateHmacCryptoMd5_ShouldBeValidHashResult() - { - var h = HashFactory.CreateHmacCryptoMd5(Decorator.Enclose("unittest").ToByteArray()); - Assert.Equal("d0ee1decd115feac4608976f28e19e10", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("34bdd10e5c71eb6860294d19f2db8233", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("70adffcc026ac757db66de1bbb005e04", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); - } - - [Fact] - public void CreateHmacCryptoSha1_ShouldBeValidHashResult() - { - var h = HashFactory.CreateHmacCryptoSha1(Decorator.Enclose("unittest").ToByteArray()); - Assert.Equal("0c7d9d4461c66d2d6eafef7211689abd20b28b39", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("62ea258e7e4d0522fe5e2b06e1ab600051c542c4", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("bae9d2e09c9b5f65f9a7a3c5a55748b180ee65c1", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); - } - - [Fact] - public void CreateHmacCryptoSha256_ShouldBeValidHashResult() - { - var h = HashFactory.CreateHmacCryptoSha256(Decorator.Enclose("unittest").ToByteArray()); - Assert.Equal("c06272f221bcbfc6783c059e85d7ecdb412e80f2c2b704c27885d64d984d95e3", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("1fd4ff25f71fa49435bc6996bbbaeeb9abe2f47ed3256b8a9f44b8eea49a23b5", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("38367544d02183414967e4669cd298ca6d644db6c6bfa903afae497c6ab300fa", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); - } - - [Fact] - public void CreateHmacCryptoSha384_ShouldBeValidHashResult() - { - var h = HashFactory.CreateHmacCryptoSha384(Decorator.Enclose("unittest").ToByteArray()); - Assert.Equal("87d90ce62441fd39f81d327fb4d3a9902d80a0d8312a961fdfc9b1bcf756afd77f0f0596d30a868a3cda1c49abf1a3ee", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("7a90151a628de76431d11cf01200a7213f0dc491f7677aafbcd232e512521ca1ac3771d2b6204fa1ab6b54cf085ba2d3", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("3b00f1f207819422761a22b918c69a3e16ae0d5268c01ad331311184b6a809b927b324f75d8cb8f1cee0f849382558bd", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); - } - - [Fact] - public void CreateHmacCryptoSha512_ShouldBeValidHashResult() - { - var h = HashFactory.CreateHmacCryptoSha512(Decorator.Enclose("unittest").ToByteArray()); - Assert.Equal("db0087dde1ad907c11037243bf700a9f08430899edfc19fe1fadb7d4e6846182fa0100762cf42c64828f1bfe41493275b98f5c0c1a80300656e0d97f9f1d892e", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("7b674e1138c2ee91828fcf5318d356e72a9c9e4e533234181640b0d1728f305db656bb7e57c95e101c0efbed9290454ef0c37f514c8dfb85f003c483bf0f236a", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("33520bc89652184cf5a17600a78a513db44a0b7eb1fb001525ab0e77c94afcb45b7878b259cd37896ddbbc5dff76d2cbb8f57eaf44c4f4aac77695fe8fe28992", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); - } - - [Fact] - public void CreateCryptoSha512_ShouldBeValidHashResult() - { - var h = HashFactory.CreateCryptoSha512(); - Assert.Equal("1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("bb96c2fc40d2d54617d6f276febe571f623a8dadf0b734855299b0e107fda32cf6b69f2da32b36445d73690b93cbd0f7bfc20e0f7f28553d2a4428f23b716e90", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("0b6cbac838dfe7f47ea1bd0df00ec282fdf45510c92161072ccfb84035390c4da743d9c3b954eaa1b0f86fc9861b23cc6c8667ab232c11c686432ebb5c8c3f27", h.ComputeHash(Guid.Empty.ToByteArray()).ToHexadecimalString()); - } - - [Fact] - public void CreateCryptoSha384_ShouldBeValidHashResult() - { - var h = HashFactory.CreateCryptoSha384(); - Assert.Equal("1761336e3f7cbfe51deb137f026f89e01a448e3b1fafa64039c1464ee8732f11a5341a6f41e0c202294736ed64db1a84", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("90ae531f24e48697904a4d0286f354c50a350ebb6c2b9efcb22f71c96ceaeffc11c6095e9ca0df0ec30bf685dcf2e5e5", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("7210af19145ec2a8e250a7fe8e9eeeac1301e524daab82366c36be614dc35402a289101e48cad61c45337f2f32c14fdc", h.ComputeHash(VerticalDirection.Up).ToHexadecimalString()); - } - - [Fact] - public void CreateCryptoSha256_ShouldBeValidHashResult() - { - var h = HashFactory.CreateCryptoSha256(); - Assert.Equal("db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); - Assert.Equal("84d89877f0d4041efb6bf91a16f0248f2fd573e6af05c19f96bedb9f882f7882", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("53ab3a50f51855beeae9721ab68656312c7f105b9b34bbfa97875dbfda72dbc6", h.ComputeHash(DateTime.UnixEpoch).ToHexadecimalString()); - Assert.Equal("1f1a24c833be74a0f4f99007aa70a51e2456e41f745a5628721ea2b8e1c07641", h.ComputeHash(213, "fdfsfsf", 9999).ToHexadecimalString()); - } - - [Fact] - public void CreateCryptoSha1_ShouldBeValidHashResult() - { - var h = HashFactory.CreateCryptoSha1(); - Assert.Equal("761c457bf73b14d27e9e9265c46f4b4dda11f940", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("761c457bf73b14d27e9e9265c46f4b4dda11f940", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); - Assert.Equal("87acec17cd9dcd20a716cc2cf67417b71c8a7016", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("485bad9954c874513b8ff7b6d5ea459ac65dd075", h.ComputeHash(decimal.MinValue).ToHexadecimalString()); - Assert.Equal("ec9a4348d9ffcb19403f5b90e9eefe4cedcd6ee1", h.ComputeHash(43402934324).ToHexadecimalString()); - } - - [Fact] - public void CreateCryptoMd5_LittleEndian_ShouldBeValidHashResult() - { - var h = HashFactory.CreateCryptoMd5(o => o.ByteOrder = Endianness.LittleEndian); - Assert.Equal("193112cee1d1a35660f02e95a28bfea2", h.ComputeHash(32131535).ToHexadecimalString()); - Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); - Assert.Equal("781e5e245d69b566979b86e28d23f2c7", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("612f309087565745ca61c53fcaf6fa7d", h.ComputeHash(212).ToHexadecimalString()); - } - - [Fact] - public void CreateCryptoMd5_BigEndian_ShouldBeValidHashResult() - { - var h = HashFactory.CreateCryptoMd5(o => o.ByteOrder = Endianness.BigEndian); - Assert.Equal("d52654efa276b2ab12ca067dccaf953f", h.ComputeHash(32131535).ToHexadecimalString()); - Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); - Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); - Assert.Equal("781e5e245d69b566979b86e28d23f2c7", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("fb7b8d4f62e2be708ceca1114b439d5d", h.ComputeHash(212).ToHexadecimalString()); - } - [Fact] public void CreateFnv32_Fnv1_ShouldBeValidHashResult() { @@ -296,7 +199,7 @@ public void CreateFnv1024_Fnv1_ShouldBeValidHashResult() var h = HashFactory.CreateFnv1024(o => o.Algorithm = FowlerNollVoAlgorithm.Fnv1); Assert.Equal("70e427242b62d481df8f97b5a7c389f5f6df3457fda072841eb0ac24759648a39784a0ab922c4730b68efa7d0980de290e79de582d88c97e17c953592b9b70ce6dca5ccd19cd93182254abfe9ed6face84979b6793e44e46621ad88c76744b1296ed3934a03e443ce593f1d3dd137dcba2ac2c5edb2cc9c7353111c2327224ca", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); Assert.Equal("c801f8e08ae91b180b98dd7d9f65ceb687ca86358c6905f60a7d1014c182b04f441590d012afb5871d0f57000000000000000000000000000000000000000000000000000000000000000000000000000000018045149ade1c79abe3b709a406f7d9205169bec59b126140bcb96f9d5d3e2ea91e0b2b52fa8d2d0d70ecdaeab2", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("98d7c19fbce653df221b9f717d3490ff95ca87fdaef30d1b823372f85b24a372f50e380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007685cd81a491dbccc21ad06648d09a5c8cf5a78482054e91470b33dde77252caef66597", h.ComputeHash(byte.MinValue).ToHexadecimalString()); + Assert.Equal("000000000000000098d7c19fbce653df221b9f717d3490ff95ca87fdaef30d1b823372f85b24a372f50e380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007685cd81a491dbccc21ad06648d09a5c8cf5a78482054e91470b33dde77252caef66597", h.ComputeHash(byte.MinValue).ToHexadecimalString()); } [Fact] @@ -350,7 +253,8 @@ public void CreateFnv1024_Fnv1a_ShouldBeValidHashResult() var h = HashFactory.CreateFnv1024(o => o.Algorithm = FowlerNollVoAlgorithm.Fnv1a); Assert.Equal("199c0ace56c5c33d8bce6f7cf4bc4b555e0fc3ae8d37c4b7384678a34d96ae8192825ae6bcda63dbb9e3417d0980de290e79de582d88c97e17c9535950c35f4f16d311bc66d1ac2892d59f7b0697257eba9fc1e3accbc85729218306b34996eedf99292c814e8a75f41ddc5a5b5177b6e60c0211ad8d8f78395c7c2d2c483e7e", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); Assert.Equal("c801f8e08ae91b180b98dd7d9f65ceb687ca86358c6905f60a7d1014c182b04ee2ab1bd0066e9857a7f7de000000000000000000000000000000000000000000000000000000000000000000000000000000018045149ade1c79abe3b709a406f7d9205169bec59b126140bcb96f9d5d3e2ea91dfc0f40af8e7e3f25d14c3186", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); - Assert.Equal("98d7c19fbce653df221b9f717d3490ff95ca87fdaef30d1b823372f85b24a372f50e380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007685cd81a491dbccc21ad06648d09a5c8cf5a78482054e91470b33dde77252caef66597", h.ComputeHash(byte.MinValue).ToHexadecimalString()); + Assert.Equal("000000000000000098d7c19fbce653df221b9f717d3490ff95ca87fdaef30d1b823372f85b24a372f50e380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007685cd81a491dbccc21ad06648d09a5c8cf5a78482054e91470b33dde77252caef66597", h.ComputeHash(byte.MinValue).ToHexadecimalString()); + } } } \ No newline at end of file diff --git a/test/Cuemon.Core.Tests/StringDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/StringDecoratorExtensionsTest.cs index f07053d9e..b33160105 100644 --- a/test/Cuemon.Core.Tests/StringDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/StringDecoratorExtensionsTest.cs @@ -10,7 +10,7 @@ namespace Cuemon { public class StringDecoratorExtensionsTest : Test { - public StringDecoratorExtensionsTest(ITestOutputHelper output = null) : base(output) + public StringDecoratorExtensionsTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Core.Tests/TypeDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/TypeDecoratorExtensionsTest.cs index 999b770b5..9e694d3d9 100644 --- a/test/Cuemon.Core.Tests/TypeDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/TypeDecoratorExtensionsTest.cs @@ -21,7 +21,7 @@ namespace Cuemon { public class TypeDecoratorExtensionsTest : Test { - public TypeDecoratorExtensionsTest(ITestOutputHelper output = null) : base(output) + public TypeDecoratorExtensionsTest(ITestOutputHelper output) : base(output) { } @@ -255,12 +255,15 @@ public void ToFriendlyName_ShouldProvideDefaultImplementationOfTypes() var defaultString = Decorator.Enclose(typeof(Tuple)).ToFriendlyName(); var fullNameString = Decorator.Enclose(typeof(Tuple)).ToFriendlyName(o => o.FullName = true); var noGenericsString = Decorator.Enclose(typeof(Tuple)).ToFriendlyName(o => o.ExcludeGenericArguments = true); - var seCultureInfo = Decorator.Enclose(typeof(Tuple)).ToFriendlyName(o => o.FormatProvider = CultureInfo.GetCultureInfo("se-SV")); Assert.Equal("Tuple", defaultString); Assert.Equal("System.Tuple", fullNameString); Assert.Equal("Tuple", noGenericsString); - Assert.Equal("Tuple", seCultureInfo); + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + var seCultureInfo = Decorator.Enclose(typeof(Tuple)).ToFriendlyName(o => o.FormatProvider = CultureInfo.GetCultureInfo("sv-SE")); // unix has different culture interpretation + Assert.Equal("Tuple", seCultureInfo); + } } [Fact] diff --git a/test/Cuemon.Core.Tests/ValidatorTest.cs b/test/Cuemon.Core.Tests/ValidatorTest.cs index c9f196a5e..1f7d8c1c2 100644 --- a/test/Cuemon.Core.Tests/ValidatorTest.cs +++ b/test/Cuemon.Core.Tests/ValidatorTest.cs @@ -9,7 +9,7 @@ namespace Cuemon { public class ValidatorTest : Test { - public ValidatorTest(ITestOutputHelper output = null) : base(output) + public ValidatorTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Data.SqlClient.Tests/Assets/UserSecretsHostFixture.cs b/test/Cuemon.Data.SqlClient.Tests/Assets/UserSecretsHostFixture.cs new file mode 100644 index 000000000..15ebb0759 --- /dev/null +++ b/test/Cuemon.Data.SqlClient.Tests/Assets/UserSecretsHostFixture.cs @@ -0,0 +1,37 @@ +using System.IO; +using Cuemon.Extensions.Xunit; +using Cuemon.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Cuemon.Data.SqlClient.Assets +{ + public sealed class UserSecretsHostFixture : HostFixture + { + public override void ConfigureHost(Test hostTest) + { + var hostTestType = hostTest?.GetType(); + Validator.ThrowIfNull(hostTest, nameof(hostTest)); + Validator.ThrowIfNotContainsType(hostTestType, nameof(hostTestType), $"{nameof(hostTest)} is not assignable from HostTest.", typeof(HostTest<>)); + + Host = new HostBuilder() + .ConfigureHostConfiguration(config => config.AddEnvironmentVariables("DOTNET_")) + .ConfigureAppConfiguration((context, config) => + { + config.SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true) + .AddEnvironmentVariables() + .AddUserSecrets(); + + ConfigureCallback(config.Build(), context.HostingEnvironment); + }) + .ConfigureServices((context, services) => + { + Configuration = context.Configuration; + HostingEnvironment = context.HostingEnvironment; + ConfigureServicesCallback(services); + }).Build(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Data.SqlClient.Tests/Cuemon.Data.SqlClient.Tests.csproj b/test/Cuemon.Data.SqlClient.Tests/Cuemon.Data.SqlClient.Tests.csproj new file mode 100644 index 000000000..0303a138d --- /dev/null +++ b/test/Cuemon.Data.SqlClient.Tests/Cuemon.Data.SqlClient.Tests.csproj @@ -0,0 +1,20 @@ + + + + Cuemon.Data.SqlClient + a3ad04eb-1ef8-4aa4-a20d-cc87b2467342 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Data.SqlClient.Tests/SqlDataManagerTest.cs b/test/Cuemon.Data.SqlClient.Tests/SqlDataManagerTest.cs new file mode 100644 index 000000000..4827477b1 --- /dev/null +++ b/test/Cuemon.Data.SqlClient.Tests/SqlDataManagerTest.cs @@ -0,0 +1,125 @@ +using System; +using System.Data.SqlClient; +using System.Linq; +using Cuemon.Data.SqlClient.Assets; +using Cuemon.Extensions; +using Cuemon.Extensions.Data; +using Cuemon.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Data.SqlClient +{ + public class SqlDataManagerTest : HostTest + { + private readonly SqlDataManager _manager; + + public SqlDataManagerTest(UserSecretsHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _manager = hostFixture.ServiceProvider.GetRequiredService(); + } + + [Fact] + public void ExecuteReader_ShouldReadAllProducts() + { + using (var reader = _manager.ExecuteReader(new DataCommand("SELECT * FROM [Production].[Product]"))) + { + var rows = reader.ToRows(); + var columns = rows.ColumnNames.ToList(); + Assert.Equal(504, rows.Count); + Assert.Equal(25, columns.Count); + Assert.InRange(rows.Select(row => row["ModifiedDate"].As().Year).Distinct().Single(), 2013, 2015); + } + } + + [Fact] + public void ExecuteScalarAsInt32_ShouldInsertNewRow() + { + var existsBefore = _manager.ExecuteExists(new DataCommand("SELECT * FROM [ErrorLog]")); + var current = _manager.ExecuteScalarAsInt32(new DataCommand("SELECT COUNT(*) FROM [ErrorLog]")); + var affected = _manager.Execute(new DataCommand(@"INSERT INTO [ErrorLog] ([ErrorTime] +,[UserName] +,[ErrorNumber] +,[ErrorSeverity] +,[ErrorState] +,[ErrorProcedure] +,[ErrorLine] +,[ErrorMessage]) +VALUES +(@utcNow, +@userName, +@errNo, +@errSeverity, +@errState, +@errProcedure, +@errorLine, +@errorMessage +)"), + new SqlParameter("@utcNow", DateTime.UtcNow), + new SqlParameter("@userName", "MMORT"), + new SqlParameter("@errNo", 42), + new SqlParameter("@errSeverity", 1), + new SqlParameter("@errState", 5), + new SqlParameter("@errProcedure", "Do not try this at home."), + new SqlParameter("@errorLine", 215), + new SqlParameter("@errorMessage", "Catastrophic failure.")); + var after = _manager.ExecuteScalarAsInt32(new DataCommand("SELECT COUNT(*) FROM [ErrorLog]")); + var existsAfter = _manager.ExecuteExists(new DataCommand("SELECT * FROM [ErrorLog]")); + + Assert.False(existsBefore); + Assert.Equal(0, current); + Assert.Equal(1, affected); + Assert.Equal(1, after); + Assert.True(existsAfter); + } + + + [Fact] + public void Execute_ShouldUpdateRows() + { + DataTransferRowCollection before; + using (var reader = _manager.ExecuteReader(new DataCommand("SELECT * FROM [HumanResources].[Department]"))) + { + before = reader.ToRows(); + } + + var affected = _manager.Execute(new DataCommand("UPDATE [HumanResources].[Department] SET [Name] = [Name] + ' XXX'")); + + DataTransferRowCollection after; + using (var reader = _manager.ExecuteReader(new DataCommand("SELECT * FROM [HumanResources].[Department]"))) + { + after = reader.ToRows(); + } + + Assert.Equal(16, before.Count); + Assert.Equal(16, affected); + Assert.Equal(16, after.Count); + + for (var i = 0; i < before.Count; i++) + { + Assert.StartsWith(before[i]["Name"].As(), after[i]["Name"].As()); + Assert.NotEqual(before[i]["Name"].As(), after[i]["Name"].As()); + Assert.EndsWith("XXX", after[i]["Name"].As()); + } + } + + [Fact] + public void Execute_ShouldDeleteRows() + { + var before = _manager.ExecuteScalarAsInt32(new DataCommand("SELECT COUNT(*) FROM [HumanResources].[EmployeeDepartmentHistory]")); + var affected = _manager.Execute(new DataCommand("DELETE [HumanResources].[EmployeeDepartmentHistory] WHERE BusinessEntityID >= 270")); + var after = _manager.ExecuteScalarAsInt32(new DataCommand("SELECT COUNT(*) FROM [HumanResources].[EmployeeDepartmentHistory]")); + Assert.Equal(296, before); + Assert.Equal(21, affected); + Assert.Equal(275, after); + } + + public override void ConfigureServices(IServiceCollection services) + { + var cnn = Configuration.GetConnectionString("AdventureWorks"); + services.AddSingleton(new SqlDataManager(cnn)); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Data.SqlClient.Tests/SqlInOperatorTest.cs b/test/Cuemon.Data.SqlClient.Tests/SqlInOperatorTest.cs new file mode 100644 index 000000000..908616a79 --- /dev/null +++ b/test/Cuemon.Data.SqlClient.Tests/SqlInOperatorTest.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Cuemon.Collections.Generic; +using Cuemon.Data.SqlClient.Assets; +using Cuemon.Extensions.Data; +using Cuemon.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Data.SqlClient +{ + public class SqlInOperatorTest : HostTest + { + private readonly SqlDataManager _manager; + + public SqlInOperatorTest(UserSecretsHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _manager = hostFixture.ServiceProvider.GetRequiredService(); + } + + [Fact] + public void ShouldSafeGuardInOperation() + { + var io = new SqlInOperator(); + var sr = io.ToSafeResult(Arguments.ToEnumerableOf("A", "B", "C")); + using (var reader = _manager.ExecuteReader(new DataCommand($"SELECT * FROM [Production].[ProductInventory] WHERE Shelf IN ({sr})"), sr.ToParametersArray())) + { + var rows = reader.ToRows(); + Assert.Equal(172, rows.Count); + Assert.Equal(sr.Arguments, sr.Parameters.Select(dbp => dbp.ParameterName)); + } + } + + public override void ConfigureServices(IServiceCollection services) + { + var cnn = Configuration.GetConnectionString("AdventureWorks"); + services.AddSingleton(new SqlDataManager(cnn)); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Data.SqlClient.Tests/SqlQueryBuilderTest.cs b/test/Cuemon.Data.SqlClient.Tests/SqlQueryBuilderTest.cs new file mode 100644 index 000000000..552028053 --- /dev/null +++ b/test/Cuemon.Data.SqlClient.Tests/SqlQueryBuilderTest.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using Cuemon.Data.SqlClient.Assets; +using Cuemon.Extensions.Data; +using Cuemon.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Data.SqlClient +{ + public class SqlQueryBuilderTest : HostTest + { + private readonly SqlDataManager _manager; + + public SqlQueryBuilderTest(UserSecretsHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _manager = hostFixture.ServiceProvider.GetRequiredService(); + } + + [Fact] + public void BuildSelectQuery_ShouldSelectStateProvince() + { + var builder = new SqlQueryBuilder("[Person].[StateProvince]", new Dictionary(), new Dictionary() { { "name", null } } ) + { + EnableDirtyReads = true, + EnableReadLimit = true, + ReadLimit = 10, + EnableTableAndColumnEncapsulation = true + }; + + Assert.True(builder.EnableDirtyReads); + Assert.True(builder.EnableReadLimit); + Assert.True(builder.EnableTableAndColumnEncapsulation); + Assert.Equal(10, builder.ReadLimit); + + var sql = builder.GetQuery(QueryType.Select); + + Assert.Contains("WITH(NOLOCK)", sql); + Assert.Contains("TOP 10", sql); + + using (var reader = _manager.ExecuteReader(new DataCommand(sql))) + { + var rows = reader.ToRows(); + Assert.Equal(10, rows.Count); + + TestOutput.WriteLine(DelimitedString.Create(rows.Select(dtr => dtr["name"]))); + + } + } + + public override void ConfigureServices(IServiceCollection services) + { + var cnn = Configuration.GetConnectionString("AdventureWorks"); + services.AddSingleton(new SqlDataManager(cnn)); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Data.Tests/Assets/DsvDataReaderTest_Wiki.csv b/test/Cuemon.Data.Tests/Assets/DsvDataReaderTest_Wiki.csv new file mode 100644 index 000000000..6d87d0f5d --- /dev/null +++ b/test/Cuemon.Data.Tests/Assets/DsvDataReaderTest_Wiki.csv @@ -0,0 +1,6 @@ +Year,Make,Model,Description,Price +1997,Ford,E350,"ac, abs, moon",3000.00 +1999,Chevy,"Venture ""Extended Edition""","",4900.00 +1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00 +1996,Jeep,Grand Cherokee,"MUST SELL! +air, moon roof, loaded",4799.50 \ No newline at end of file diff --git a/test/Cuemon.Data.Tests/Assets/Professional.xml b/test/Cuemon.Data.Tests/Assets/Professional.xml new file mode 100644 index 000000000..c6f1c3d78 --- /dev/null +++ b/test/Cuemon.Data.Tests/Assets/Professional.xml @@ -0,0 +1,376 @@ + + + + + 2 + 2 + + + 2 + 2 + + + 1 + 7 + 1 + + + 1 + 7 + 1 + + + 2 + 2 + 2 + 2 + 1 + 0 + 0 + 0 + + + 2 + 2 + 2 + 2 + 1 + 0 + 0 + 0 + + + 1 + + + 1 + + + 524288 + + + 65536 + + + WinNT + Terminal&#x20;Server + + + WinNT + Terminal&#x20;Server + + + %SystemRoot%\system32\BcastDVRBroker.dll + + + %SystemRoot%\system32\BcastDVRBroker.dll + + + 2 + 20 + + + 2 + 20 + + + 2 + 1 + + + 2 + 1 + + + + + + + + + + + + 1 + 100 + 3 + 3 + 0 + 0 + 2 + 1 + 1 + 1200 + 600 + 10 + 10 + 0 + 0 + 50000 + 50000 + 20 + 30 + 1 + 1 + 60 + 30 + 30 + 1 + 1 + 1800 + 900 + 0 + 0 + 120 + 120 + 1 + 70 + 0 + 20 + 1 + 900 + 5 + 300 + 1200 + 1200 + 1200 + 75 + 2 + 2 + 1 + 1 + 1200 + 1200 + 20 + 20 + 7 + 7 + 10 + 10 + 1 + 1 + 1 + 30 + 30 + 2 + 15 + 15 + 1 + 1 + 0 + 0 + 120 + 120 + 70 + 1 + 2 + 2 + 1 + 1 + 2 + 2 + 0 + 0 + 50000 + 50000 + 60 + 60 + 2 + 2 + 1 + 1 + 200 + 200 + 100 + 100 + 1 + 1 + 900 + 600 + 0 + 0 + 120 + 120 + 70 + 0 + 20 + 1 + O:BAG:SYD:P(A;CI;KRKW;;;BU)(A;CI;KA;;;BA)(A;CI;KA;;;SY)(A;CI;KA;;;CO)(A;CI;KR;;;AC)(A;CI;KR;;;S-1-15-3-1024-1502825166-1963708345-2616377461-2562897074-4192028372-3968301570-1997628692-1435953622) + 1 + 0 + 1 + 300 + 120 + 99 + 99 + 99 + 99 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2 + 2 + 2 + 2 + 2 + 2 + 97 + 97 + 1 + 1 + 1 + 1 + 1 + 1 + + + 2 + 1 + 1 + 1200 + 600 + 10 + 10 + 0 + 0 + 50000 + 50000 + 20 + 30 + 1 + 1 + 60 + 30 + 30 + 1 + 1 + 1800 + 900 + 0 + 0 + 120 + 120 + 1 + 70 + 0 + 20 + 1 + 900 + 5 + 300 + 1200 + 1200 + 1200 + 75 + 2 + 2 + 1 + 1 + 1200 + 1200 + 20 + 20 + 7 + 7 + 10 + 10 + 1 + 1 + 1 + 30 + 30 + 2 + 15 + 15 + 1 + 1 + 0 + 0 + 120 + 120 + 70 + 1 + 2 + 2 + 1 + 1 + 2 + 2 + 0 + 0 + 50000 + 50000 + 60 + 60 + 2 + 2 + 1 + 1 + 200 + 200 + 100 + 100 + 1 + 1 + 900 + 600 + 0 + 0 + 120 + 120 + 70 + 0 + 20 + 1 + O:BAG:SYD:P(A;CI;KRKW;;;BU)(A;CI;KA;;;BA)(A;CI;KA;;;SY)(A;CI;KA;;;CO)(A;CI;KR;;;AC)(A;CI;KR;;;S-1-15-3-1024-1502825166-1963708345-2616377461-2562897074-4192028372-3968301570-1997628692-1435953622) + 1 + 0 + 1 + 300 + 120 + 99 + 99 + 99 + 99 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2 + 2 + 2 + 2 + 2 + 2 + 97 + 97 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 100 + 3 + 3 + 0 + 0 + + + 0 + + + 0 + + + \ No newline at end of file diff --git a/test/Cuemon.Data.Tests/ConcurrentDsvDataReaderTest.cs b/test/Cuemon.Data.Tests/ConcurrentDsvDataReaderTest.cs new file mode 100644 index 000000000..09f1779f7 --- /dev/null +++ b/test/Cuemon.Data.Tests/ConcurrentDsvDataReaderTest.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Cuemon.Extensions.Xunit; +using Cuemon.Reflection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Data +{ + public class ConcurrentDsvDataReaderTest : Test + { + public ConcurrentDsvDataReaderTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task DsvDataReader_ShouldReadEmbeddedResourceLineByLine() + { + var file = Decorator.Enclose(typeof(DsvDataReaderTest).Assembly).GetManifestResources("DsvDataReaderTest_Wiki.csv", ManifestResourceMatch.ContainsName).Values.Single(); + ConcurrentDsvDataReader reader = null; + using (reader = new ConcurrentDsvDataReader(new StreamReader(file))) + { + while (await reader.ReadAsync()) + { + TestOutput.WriteLine(reader.ToString()); + } + } + Assert.Equal(5, reader.FieldCount); + Assert.Equal(4, reader.RowCount); + Assert.True(reader.Disposed); + await Assert.ThrowsAsync(() => reader.ReadAsync()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj b/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj index 9e03c0974..636f5b47c 100644 --- a/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj +++ b/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj @@ -1,29 +1,23 @@ - netcoreapp3.1 - - false - Cuemon.Data - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + - + + - + \ No newline at end of file diff --git a/test/Cuemon.Data.Tests/DsvDataReaderTest.cs b/test/Cuemon.Data.Tests/DsvDataReaderTest.cs new file mode 100644 index 000000000..eb87f4962 --- /dev/null +++ b/test/Cuemon.Data.Tests/DsvDataReaderTest.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Linq; +using Cuemon.Extensions.Xunit; +using Cuemon.Reflection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Data +{ + public class DsvDataReaderTest : Test + { + public DsvDataReaderTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void DsvDataReader_ShouldReadEmbeddedResourceLineByLine() + { + var file = Decorator.Enclose(typeof(DsvDataReaderTest).Assembly).GetManifestResources("DsvDataReaderTest_Wiki.csv", ManifestResourceMatch.ContainsName).Values.Single(); + DsvDataReader reader = null; + using (reader = new DsvDataReader(new StreamReader(file))) + { + while (reader.Read()) + { + TestOutput.WriteLine(reader.ToString()); + } + } + Assert.Equal(5, reader.FieldCount); + Assert.Equal(4, reader.RowCount); + Assert.True(reader.Disposed); + Assert.Throws(() => reader.Read()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Data.Tests/ExceptionTest.cs b/test/Cuemon.Data.Tests/ExceptionTest.cs index 5acfe099a..7388851ad 100644 --- a/test/Cuemon.Data.Tests/ExceptionTest.cs +++ b/test/Cuemon.Data.Tests/ExceptionTest.cs @@ -8,7 +8,7 @@ namespace Cuemon.Data { public class ExceptionTest : Test { - public ExceptionTest(ITestOutputHelper output = null) : base(output) + public ExceptionTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Data.Tests/Xml/XmlDataReaderTest.cs b/test/Cuemon.Data.Tests/Xml/XmlDataReaderTest.cs new file mode 100644 index 000000000..b885da882 --- /dev/null +++ b/test/Cuemon.Data.Tests/Xml/XmlDataReaderTest.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.XPath; +using Cuemon.Extensions.Reflection; +using Cuemon.Extensions.Xunit; +using Cuemon.IO; +using Cuemon.Reflection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Data.Xml +{ + public class XmlDataReaderTest : Test + { + public XmlDataReaderTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void XmlDataReader_ShouldReadAllRows() + { + var file = typeof(XmlDataReaderTest).GetEmbeddedResources("Professional.xml", ManifestResourceMatch.ContainsName).Values.Single(); + var msXml = new MemoryStream(); + Decorator.Enclose(file).CopyStream(msXml); + var xp = new XPathDocument(msXml).CreateNavigator(); + var xmlReader = xp.ReadSubtree(); + var sb1 = new StringBuilder(); + var sb2 = new StringBuilder(); + + string elementName = null; + while (xmlReader.Read()) + { + if (xmlReader.NodeType == XmlNodeType.Element) + { + elementName = xmlReader.LocalName; + } + + if (xmlReader.HasAttributes) + { + while (xmlReader.MoveToNextAttribute()) + { + sb1.AppendLine($"{xmlReader.LocalName}={xmlReader.Value}"); + } + + } + else if (!string.IsNullOrEmpty(xmlReader.Value)) + { + sb1.AppendLine($"{elementName}={xmlReader.Value}"); + } + } + + + XmlDataReader dataReader; + using (dataReader = new XmlDataReader(XmlReader.Create(file))) + { + while (dataReader.Read()) + { + for (var i = 0; i < dataReader.FieldCount; i++) + { + sb2.AppendLine($"{dataReader.GetName(i)}={dataReader.GetValue(i)}"); + } + } + Assert.True(xmlReader.EOF); + } + + Assert.Equal(sb1.ToString(), sb2.ToString()); + Assert.Equal(344, dataReader.RowCount); + Assert.True(dataReader.Disposed); + Assert.Throws(() => dataReader.Read()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Diagnostics.Tests/Assets/ArgumentNullExceptionDescriptorAttribute.cs b/test/Cuemon.Diagnostics.Tests/Assets/ArgumentNullExceptionDescriptorAttribute.cs new file mode 100644 index 000000000..445407c3d --- /dev/null +++ b/test/Cuemon.Diagnostics.Tests/Assets/ArgumentNullExceptionDescriptorAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace Cuemon.Diagnostics.Assets +{ + public sealed class ArgumentNullExceptionDescriptorAttribute : ExceptionDescriptorAttribute + { + public ArgumentNullExceptionDescriptorAttribute() : base(typeof(ArgumentNullException)) + { + Code = "ArgumentNullException"; + Message = TestContext.FaultDescriptor_ArgumentNullException; + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Diagnostics.Tests/Assets/SomeClass.cs b/test/Cuemon.Diagnostics.Tests/Assets/SomeClass.cs new file mode 100644 index 000000000..97eded2bc --- /dev/null +++ b/test/Cuemon.Diagnostics.Tests/Assets/SomeClass.cs @@ -0,0 +1,29 @@ +using System; +using Cuemon.Extensions.Collections.Generic; + +namespace Cuemon.Diagnostics.Assets +{ + public class SomeClass + { + [ArgumentNullExceptionDescriptor] + public string[] StringToArray(string value) + { + Validator.ThrowIfNull(value, nameof(value), "Null is a no-go!"); + return value.Split(','); + } + + [ExceptionDescriptor(typeof(ArgumentNullException), Code = "ArgumentNullException", Message = "The value cannot be null (none-resource).", MessageResourceName = "FaultDescriptor_ArgumentNullException", ResourceType = typeof(TestContext))] + public string Shuffle(string value) + { + Validator.ThrowIfNull(value, nameof(value), "Null is a no-go!"); + return string.Concat(value.Shuffle()); + } + + [ExceptionDescriptor(typeof(ArgumentNullException), Code = "ArgumentNullException", Message = "The value cannot be null (none-resource).")] + public string ShuffleNoLoc(string value) + { + Validator.ThrowIfNull(value, nameof(value), "Null is a no-go!"); + return string.Concat(value.Shuffle()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Diagnostics.Tests/Cuemon.Diagnostics.Tests.csproj b/test/Cuemon.Diagnostics.Tests/Cuemon.Diagnostics.Tests.csproj index 075965305..92bf21970 100644 --- a/test/Cuemon.Diagnostics.Tests/Cuemon.Diagnostics.Tests.csproj +++ b/test/Cuemon.Diagnostics.Tests/Cuemon.Diagnostics.Tests.csproj @@ -1,29 +1,27 @@ - netcoreapp3.1 - - false - Cuemon.Diagnostics - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + - - + + True + True + TestContext.resx + + + + + + ResXFileCodeGenerator + TestContext.Designer.cs + \ No newline at end of file diff --git a/test/Cuemon.Diagnostics.Tests/ExceptionDescriptorTest.cs b/test/Cuemon.Diagnostics.Tests/ExceptionDescriptorTest.cs new file mode 100644 index 000000000..bb572f18d --- /dev/null +++ b/test/Cuemon.Diagnostics.Tests/ExceptionDescriptorTest.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using Cuemon.Collections.Generic; +using Cuemon.Diagnostics.Assets; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Diagnostics +{ + public class ExceptionDescriptorTest : Test + { + public ExceptionDescriptorTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Extract_VerifyThatValidatorProvideInsights() + { + Stream someObject = null; + var enrichedException = Assert.Throws(() => Validator.ThrowIfNull(someObject, nameof(someObject))); + var ed = ExceptionDescriptor.Extract(enrichedException); + + Assert.Equal(enrichedException.ToString(), ed.ToString()); + Assert.Equal("UnhandledException", ed.Code); + Assert.Equal("An unhandled exception occurred.", ed.Message); + var me = Assert.Single(ed.Evidence).Value as MemberEvidence; + Assert.Equal(me.MemberSignature, "Cuemon.Validator.ThrowIfNull(T value, String paramName, String message)"); + Assert.Equal(3, me.RuntimeParameters.Count); + Assert.True(me.RuntimeParameters.ContainsKey("value")); + Assert.True(me.RuntimeParameters.ContainsKey("paramName")); + Assert.True(me.RuntimeParameters.ContainsKey("message")); + Assert.Null(me.RuntimeParameters["value"]); + Assert.Equal(nameof(someObject), me.RuntimeParameters["paramName"]); + Assert.Equal("Value cannot be null.", me.RuntimeParameters["message"]); + } + + [Fact] + public void Extract_VerifyThatInlineExceptionIncludesSystemSnapshot() + { + var ane = new ArgumentNullException("myParam", "myMessage"); + var enrichedException = ExceptionInsights.Embed(ane, MethodBase.GetCurrentMethod(), Arguments.ToArray(null, "myParam", "myMessage"), SystemSnapshot.CaptureAll); + var ed = ExceptionDescriptor.Extract(enrichedException); + + Assert.Equal(enrichedException.ToString(), ed.ToString()); + Assert.Equal("UnhandledException", ed.Code); + Assert.Equal("An unhandled exception occurred.", ed.Message); + Assert.Equal(4, ed.Evidence.Count); + var me = ed.Evidence.Single(pair => pair.Key == "Thrower") .Value as MemberEvidence; + Assert.Equal(me.MemberSignature, "Cuemon.Diagnostics.ExceptionDescriptorTest.Extract_VerifyThatInlineExceptionIncludesSystemSnapshot()"); + Assert.Equal(3, me.RuntimeParameters.Count); + Assert.True(me.RuntimeParameters.ContainsKey("arg1")); + Assert.True(me.RuntimeParameters.ContainsKey("arg2")); + Assert.True(me.RuntimeParameters.ContainsKey("arg3")); + Assert.Null(me.RuntimeParameters["arg1"]); + Assert.Equal("myParam", me.RuntimeParameters["arg2"]); + Assert.Equal("myMessage", me.RuntimeParameters["arg3"]); + var ti = ed.Evidence.Single(pair => pair.Key == "Thread").Value as IDictionary; + Assert.NotNull(ti); + Assert.True(ti.Count > 0); + TestOutput.WriteLine(DelimitedString.Create(ti, o => o.Delimiter = Environment.NewLine)); + var pi = ed.Evidence.Single(pair => pair.Key == "Process").Value as IDictionary; + Assert.NotNull(pi); + Assert.True(pi.Count > 0); + TestOutput.WriteLine(DelimitedString.Create(pi, o => o.Delimiter = Environment.NewLine)); + var ei = ed.Evidence.Single(pair => pair.Key == "Environment").Value as IDictionary; + Assert.NotNull(ei); + Assert.True(ei.Count > 0); + TestOutput.WriteLine(DelimitedString.Create(ei, o => o.Delimiter = Environment.NewLine)); + } + + [Fact] + public void ShouldCreateDefaultInstance() + { + var hu = new Uri("https://docs.cuemon.net/api/dotnet/Cuemon.Diagnostics.ExceptionDescriptor.html"); + var ex = new InvalidOperationException("Invalid operation test."); + var ed = new ExceptionDescriptor(ex, "Invalid Operation Exception", "Developer did something unexpected.", hu); + + Assert.Equal(ex.ToString(), ed.ToString()); + Assert.Equal("InvalidOperationException", ed.Code); + Assert.Equal("Developer did something unexpected.", ed.Message); + Assert.Equal(hu, ed.HelpLink); + Assert.Equal(0, ed.Evidence.Count); + Assert.Equal(ex, ed.Failure); + } + + [Fact] + public void ShouldCreateDefaultInstanceWithPostInitializeUsingCustomImplementedExceptionDescriptorAttribute() + { + var sc = new SomeClass(); + var hu = new Uri("https://docs.cuemon.net/api/dotnet/Cuemon.Diagnostics.ExceptionDescriptor.html"); + var ex = Assert.Throws(() => sc.StringToArray(null)); + var ed = new ExceptionDescriptor(ex, "Not Null Exception", "Null is not allowed.", hu); + + Assert.Equal(ex.Message, "Null is a no-go! (Parameter 'value')"); + Assert.Equal(ex.ToString(), ed.ToString()); + Assert.Equal("NotNullException", ed.Code); + Assert.Equal("Null is not allowed.", ed.Message); + Assert.Equal(hu, ed.HelpLink); + Assert.Equal(0, ed.Evidence.Count); + Assert.Equal(ex, ed.Failure); + + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + + ed.PostInitializeWith(sc.GetType().GetMethod("StringToArray").GetCustomAttribute()); + + Assert.Equal("ArgumentNullException", ed.Code); + Assert.NotEqual("The value cannot be null (none-resource).", ed.Message); + Assert.Equal("Value cannot be null.", ed.Message); + + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("da-DK"); + + ed.PostInitializeWith(sc.GetType().GetMethod("StringToArray").GetCustomAttribute()); + + Assert.Equal("ArgumentNullException", ed.Code); + Assert.NotEqual("Value cannot be null.", ed.Message); + Assert.Equal("Null er ikke en gyldig værdi.", ed.Message); + } + + [Fact] + public void ShouldCreateDefaultInstanceWithPostInitializeUsingExceptionDescriptorAttributeWithLocalization() + { + var sc = new SomeClass(); + var hu = new Uri("https://docs.cuemon.net/api/dotnet/Cuemon.Diagnostics.ExceptionDescriptor.html"); + var ex = Assert.Throws(() => sc.Shuffle(null)); + var ed = new ExceptionDescriptor(ex, "Not Null Exception", "Null is not allowed.", hu); + + Assert.Equal(ex.Message, "Null is a no-go! (Parameter 'value')"); + Assert.Equal(ex.ToString(), ed.ToString()); + Assert.Equal("NotNullException", ed.Code); + Assert.Equal("Null is not allowed.", ed.Message); + Assert.Equal(hu, ed.HelpLink); + Assert.Equal(0, ed.Evidence.Count); + Assert.Equal(ex, ed.Failure); + + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + + ed.PostInitializeWith(sc.GetType().GetMethod("Shuffle").GetCustomAttribute()); + + Assert.Equal("ArgumentNullException", ed.Code); + Assert.NotEqual("The value cannot be null (none-resource).", ed.Message); + Assert.Equal("Value cannot be null.", ed.Message); + + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("da-DK"); + + ed.PostInitializeWith(sc.GetType().GetMethod("Shuffle").GetCustomAttribute()); + + Assert.Equal("ArgumentNullException", ed.Code); + Assert.NotEqual("Value cannot be null.", ed.Message); + Assert.Equal("Null er ikke en gyldig værdi.", ed.Message); + } + + [Fact] + public void ShouldCreateDefaultInstanceWithPostInitializeUsingExceptionDescriptorAttributeWithoutLocalization() + { + var sc = new SomeClass(); + var hu = new Uri("https://docs.cuemon.net/api/dotnet/Cuemon.Diagnostics.ExceptionDescriptor.html"); + var ex = Assert.Throws(() => sc.ShuffleNoLoc(null)); + var ed = new ExceptionDescriptor(ex, "Not Null Exception", "Null is not allowed.", hu); + + Assert.Equal(ex.Message, "Null is a no-go! (Parameter 'value')"); + Assert.Equal(ex.ToString(), ed.ToString()); + Assert.Equal("NotNullException", ed.Code); + Assert.Equal("Null is not allowed.", ed.Message); + Assert.Equal(hu, ed.HelpLink); + Assert.Equal(0, ed.Evidence.Count); + Assert.Equal(ex, ed.Failure); + + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + + ed.PostInitializeWith(sc.GetType().GetMethod("ShuffleNoLoc").GetCustomAttribute()); + + Assert.Equal("ArgumentNullException", ed.Code); + Assert.Equal("The value cannot be null (none-resource).", ed.Message); + Assert.NotEqual("Value cannot be null.", ed.Message); + + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("da-DK"); + + ed.PostInitializeWith(sc.GetType().GetMethod("ShuffleNoLoc").GetCustomAttribute()); + + Assert.Equal("ArgumentNullException", ed.Code); + Assert.Equal("The value cannot be null (none-resource).", ed.Message); + Assert.NotEqual("Null er ikke en gyldig værdi.", ed.Message); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Diagnostics.Tests/GlobalSuppressions.cs b/test/Cuemon.Diagnostics.Tests/GlobalSuppressions.cs deleted file mode 100644 index 53ac92000..000000000 --- a/test/Cuemon.Diagnostics.Tests/GlobalSuppressions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_0_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_1_Argument_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_10_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_2_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_3_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_4_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_5_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_6_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_7_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_8_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithActionAsync_Use_9_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_0_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_1_Argument_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_10_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_2_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_3_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_4_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_5_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_6_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_7_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_8_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Diagnostics.TimeMeasureTest.WithFuncAsync_Use_9_Arguments_And_CancellationToken_ShouldTakeAroundOneSecond~System.Threading.Tasks.Task")] diff --git a/test/Cuemon.Diagnostics.Tests/TestContext.Designer.cs b/test/Cuemon.Diagnostics.Tests/TestContext.Designer.cs new file mode 100644 index 000000000..a59d6b198 --- /dev/null +++ b/test/Cuemon.Diagnostics.Tests/TestContext.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// 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 Cuemon.Diagnostics { + using System; + + + /// + /// 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", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class TestContext { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal TestContext() { + } + + /// + /// 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 (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Cuemon.Diagnostics.TestContext", typeof(TestContext).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; + } + } + + /// + /// Looks up a localized string similar to Value cannot be null.. + /// + internal static string FaultDescriptor_ArgumentNullException { + get { + return ResourceManager.GetString("FaultDescriptor_ArgumentNullException", resourceCulture); + } + } + } +} diff --git a/test/Cuemon.Diagnostics.Tests/TestContext.da.resx b/test/Cuemon.Diagnostics.Tests/TestContext.da.resx new file mode 100644 index 000000000..d1656eb7d --- /dev/null +++ b/test/Cuemon.Diagnostics.Tests/TestContext.da.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Null er ikke en gyldig værdi. + + \ No newline at end of file diff --git a/test/Cuemon.Diagnostics.Tests/TestContext.resx b/test/Cuemon.Diagnostics.Tests/TestContext.resx new file mode 100644 index 000000000..c28d91b4b --- /dev/null +++ b/test/Cuemon.Diagnostics.Tests/TestContext.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Value cannot be null. + + \ No newline at end of file diff --git a/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs b/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs index 80ac17efb..1cacd2554 100644 --- a/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs +++ b/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs @@ -523,7 +523,7 @@ public async Task WithActionAsync_Use_0_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync(token => Task.Delay(expected, token), ctsShouldFail.Token); }); @@ -545,7 +545,7 @@ public async Task WithActionAsync_Use_1_Argument_And_CancellationToken_ShouldTak var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a, token) => Task.Delay(expected, token), 1, ctsShouldFail.Token); }); @@ -569,7 +569,7 @@ public async Task WithActionAsync_Use_2_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, token) => Task.Delay(expected, token), 1, 2, ctsShouldFail.Token); }); @@ -594,7 +594,7 @@ public async Task WithActionAsync_Use_3_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, token) => Task.Delay(expected, token), 1, 2, 3, ctsShouldFail.Token); }); @@ -620,7 +620,7 @@ public async Task WithActionAsync_Use_4_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, a4, token) => Task.Delay(expected, token), 1, 2, 3, 4, ctsShouldFail.Token); }); @@ -647,7 +647,7 @@ public async Task WithActionAsync_Use_5_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, ctsShouldFail.Token); }); @@ -675,7 +675,7 @@ public async Task WithActionAsync_Use_6_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, ctsShouldFail.Token); }); @@ -704,7 +704,7 @@ public async Task WithActionAsync_Use_7_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, ctsShouldFail.Token); }); @@ -734,7 +734,7 @@ public async Task WithActionAsync_Use_8_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, a8, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, 8, ctsShouldFail.Token); }); @@ -765,7 +765,7 @@ public async Task WithActionAsync_Use_9_Arguments_And_CancellationToken_ShouldTa var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, a8, a9, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, 8, 9, ctsShouldFail.Token); }); @@ -797,7 +797,7 @@ public async Task WithActionAsync_Use_10_Arguments_And_CancellationToken_ShouldT var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ctsShouldFail.Token); }); @@ -830,7 +830,7 @@ public async Task WithFuncAsync_Use_0_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async token => { @@ -862,7 +862,7 @@ public async Task WithFuncAsync_Use_1_Argument_And_CancellationToken_ShouldTakeA var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a, token) => { @@ -896,7 +896,7 @@ public async Task WithFuncAsync_Use_2_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, token) => { @@ -931,7 +931,7 @@ public async Task WithFuncAsync_Use_3_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, token) => { @@ -967,7 +967,7 @@ public async Task WithFuncAsync_Use_4_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, token) => { @@ -1004,7 +1004,7 @@ public async Task WithFuncAsync_Use_5_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, token) => { @@ -1042,7 +1042,7 @@ public async Task WithFuncAsync_Use_6_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, token) => { @@ -1081,7 +1081,7 @@ public async Task WithFuncAsync_Use_7_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, token) => { @@ -1121,7 +1121,7 @@ public async Task WithFuncAsync_Use_8_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, a8, token) => { @@ -1162,7 +1162,7 @@ public async Task WithFuncAsync_Use_9_Arguments_And_CancellationToken_ShouldTake var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, a8, a9, token) => { @@ -1204,7 +1204,7 @@ public async Task WithFuncAsync_Use_10_Arguments_And_CancellationToken_ShouldTak var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); var ctsShouldPass = new CancellationTokenSource(); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, token) => { diff --git a/test/Cuemon.Extensions.Core.Tests/Cuemon.Extensions.Core.Tests.csproj b/test/Cuemon.Extensions.Core.Tests/Cuemon.Extensions.Core.Tests.csproj index 4acda19a8..32bfd9949 100644 --- a/test/Cuemon.Extensions.Core.Tests/Cuemon.Extensions.Core.Tests.csproj +++ b/test/Cuemon.Extensions.Core.Tests/Cuemon.Extensions.Core.Tests.csproj @@ -1,29 +1,12 @@ - netcoreapp3.1 - - false - Cuemon.Extensions - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + - + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Core.Tests/ObjectExtensionsTest.cs b/test/Cuemon.Extensions.Core.Tests/ObjectExtensionsTest.cs new file mode 100644 index 000000000..9c1c9b69d --- /dev/null +++ b/test/Cuemon.Extensions.Core.Tests/ObjectExtensionsTest.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using Cuemon.Collections.Generic; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions +{ + public class ObjectExtensionsTest : Test + { + public ObjectExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void UseWrapper_ShouldWrapTheAnswerToEverything() + { + var si = 42.UseWrapper(info => info.Add("funFact", "THE ANSWER TO LIFE, THE UNIVERSE AND EVERYTHING")); + + Assert.Equal(42, si.Instance); + Assert.Equal(typeof(int), si.InstanceType); + Assert.Equal(typeof(long), si.InstanceAs().GetType()); + Assert.Equal(typeof(byte), si.InstanceAs().GetType()); + Assert.Equal("THE ANSWER TO LIFE, THE UNIVERSE AND EVERYTHING", si.Data.Single(pair => pair.Key == "funFact").Value); + } + + [Fact] + public void As_ConvertAnythingOrDefault() + { + object answer = 42; + Assert.Equal("42", 42.As()); + Assert.Equal(42, 42.As()); + Assert.Equal(42, 42.As()); + Assert.Equal(42, 42.As()); + Assert.Equal(42, 42.As()); + Assert.Equal((ulong)42, 42.As()); + Assert.Equal((uint)42, 42.As()); + Assert.Equal(42, 42.As()); + Assert.Equal(42, 42.As()); + Assert.Equal(TimeSpan.FromTicks(42), 42.As(TimeSpan.FromTicks(42))); + } + + [Fact] + public void GetHashCode32_ShouldGenerateASuitable32bitHashCode() + { + var v = Arguments.ToEnumerableOf(42, Guid.Empty.ToString("N"), DateTime.MaxValue, TimeSpan.TicksPerDay, true, decimal.MaxValue, double.Epsilon, float.Epsilon); + Assert.Equal(1246074942, v.GetHashCode32()); + } + + [Fact] + public void GetHashCode64_ShouldGenerateASuitable32bitHashCode() + { + var v = Arguments.ToEnumerableOf(42, Guid.Empty.ToString("N"), DateTime.MaxValue, TimeSpan.TicksPerDay, true, decimal.MaxValue, double.Epsilon, float.Epsilon); + Assert.Equal(6647510224603551806, v.GetHashCode64()); + } + + [Fact] + public void ToDelimitedString_ShouldGenerateDelimitedString() + { + var s = Generate.RangeOf(10, i => i); + var ds = s.ToDelimitedString(); + + Assert.Equal("0,1,2,3,4,5,6,7,8,9", ds); + } + + [Fact] + public void Adjust_ShouldChangeTheStateOfExistingInstance() + { + var dt1 = DateTime.MinValue; + var dt2 = dt1.Adjust(i => DateTime.MaxValue); + + Assert.Equal(dt1, DateTime.MinValue); + Assert.Equal(dt2, DateTime.MaxValue); + } + + [Fact] + public void IsNullable_ShouldBeFalse() + { + var dt1 = DateTime.MinValue; + + Assert.False(dt1.IsNullable()); + } + + [Fact] + public void IsNullable_ShouldBeTrue() + { + DateTime? dt1 = null; + + Assert.True(dt1.IsNullable()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Core.Tests/StringExtensionsTest.cs b/test/Cuemon.Extensions.Core.Tests/StringExtensionsTest.cs new file mode 100644 index 000000000..b2b591c11 --- /dev/null +++ b/test/Cuemon.Extensions.Core.Tests/StringExtensionsTest.cs @@ -0,0 +1,199 @@ +using System; +using System.Globalization; +using System.Linq; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions +{ + public class StringExtensionsTest : Test + { + public StringExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Difference_ShouldGetDifference() + { + var s1 = Alphanumeric.UppercaseLetters; + var s2 = Alphanumeric.Letters; + var s3 = s1.Difference(s2); + + Assert.Equal(Alphanumeric.LowercaseLetters, s3); + } + + [Fact] + public void ToCharArray_ShouldConvertStringToCharArray() + { + var s1 = Alphanumeric.LettersAndNumbers; + var c1 = s1.ToCharArray(); + + Assert.Equal(s1, string.Concat(c1)); + } + + [Fact] + public void ToByteArray_ShouldConvertStringToByteArray() + { + var s1 = Alphanumeric.LettersAndNumbers; + var b1 = s1.ToByteArray(); + + Assert.Equal(s1, Convertible.ToString(b1)); + } + + [Fact] + public void FromUrlEncodedBase64String_ShouldConvertUrlEncodedBase64StringToByteArray() + { + var s1 = "VGhpcyBpcyBhIHRlc3Qgd2l0aCBzcGVjaWFsIGNoYXJhY3RlcnMgISIjwqQlJi8oKUAhPQ"; + var b1 = s1.FromUrlEncodedBase64(); + var s2 = Convertible.ToString(b1); + + Assert.Equal("This is a test with special characters !\"#¤%&/()@!=", s2); + } + + [Fact] + public void FromBinaryDigits_ShouldConvertBinaryDigitsStringToByteArray() + { + var s1 = "01010100011010000110100101110011001000000110100101110011001000000110000100100000011101000110010101110011011101000010000001110111011010010111010001101000001000000111001101110000011001010110001101101001011000010110110000100000011000110110100001100001011100100110000101100011011101000110010101110010011100110010000000100001001000100010001111000010101001000010010100100110001011110010100000101001010000000010000100111101"; + var b1 = s1.FromBinaryDigits(); + var s2 = Convertible.ToString(b1); + + Assert.Equal("This is a test with special characters !\"#¤%&/()@!=", s2); + } + + [Fact] + public void FromBase64_ShouldConvertBase64StringToByteArray() + { + var s1 = "VGhpcyBpcyBhIHRlc3Qgd2l0aCBzcGVjaWFsIGNoYXJhY3RlcnMgISIjwqQlJi8oKUAhPQ=="; + var b1 = s1.FromBase64(); + var s2 = Convertible.ToString(b1); + + Assert.Equal("This is a test with special characters !\"#¤%&/()@!=", s2); + } + + [Fact] + public void ToCasing_ShouldConvertToDifferentCasingMethods() + { + var s1 = "Cuemon for .net"; + var s2 = s1.ToCasing(); + var s3 = s1.ToCasing(CasingMethod.LowerCase); + var s4 = s1.ToCasing(CasingMethod.TitleCase); + var s5 = s1.ToCasing(CasingMethod.UpperCase); + + Assert.Equal(s1, s2); + Assert.Equal(s1.ToLowerInvariant(), s3); + Assert.Equal(CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s1), s4); + Assert.Equal(s1.ToUpperInvariant(), s5); + } + + [Fact] + public void ToUri_ShouldConvertToUri() + { + var s1 = "https://www.cuemon.net/"; + var u1 = s1.ToUri(); + + Assert.Equal(s1, u1.OriginalString); + } + + [Fact] + public void IsNullOrEmpty_ShouldGiveFalseOnAtLeastOneNullOrEmpty() + { + var l1 = Generate.RangeOf(5, i => + { + if (i < 4) { return $"{i}"; } + return null; + }); + + var l2 = Generate.RangeOf(5, i => + { + if (i < 4) { return $"{i}"; } + return ""; + }); + + var l3 = Generate.RangeOf(5, i => $"{i}"); + + Assert.True(l1.IsNullOrEmpty()); + Assert.True(l2.IsNullOrEmpty()); + Assert.False(l3.IsNullOrEmpty()); + } + + [Fact] + public void Count_ShouldCountSpecifiedChar() + { + Assert.Equal(10, string.Concat(Alphanumeric.LettersAndNumbers, new string('a', 9)).Count('a')); + Assert.Equal(10, string.Concat(Alphanumeric.LettersAndNumbers, new string('Z', 9)).Count('Z')); + } + + [Fact] + public void JsEscape_ShouldDoJavascriptEscape() + { + Assert.Equal("Need%20complemental%20framework%20for%20.NET%3F%20Use%20Cuemon%20for%20.NET%21", "Need complemental framework for .NET? Use Cuemon for .NET!".JsEscape()); + } + + [Fact] + public void JsUnescape_ShouldDoJavascriptUnescape() + { + Assert.Equal("Need complemental framework for .NET? Use Cuemon for .NET!", "Need%20complemental%20framework%20for%20.NET%3F%20Use%20Cuemon%20for%20.NET%21".JsUnescape()); + } + + private static readonly string SentenceWithCuemonInIt = "Cuemon is following most of the Framework Design Guidelines."; + private static readonly string SentenceWithForInIt = "Let's live for freedom!"; + private static readonly string SentenceWithDotNetInIt = "Microsoft .NET - one platform to rule them all."; + + [Fact] + public void ContainsAny() + { + Assert.True("Cuemon for .NET".ContainsAny(SentenceWithCuemonInIt.Split(' '))); + Assert.True("Cuemon for .NET".ContainsAny(SentenceWithForInIt.Split(' '))); + Assert.True("Cuemon for .NET".ContainsAny(SentenceWithDotNetInIt.Split(' '))); + Assert.False("Cuemon for .NET".ContainsAny("some", "random", "words")); + } + + [Fact] + public void ContainsAll() + { + Assert.True("Cuemon for .NET".ContainsAll("Cuemon", "for", ".NET")); + Assert.False("Cuemon for .NET".ContainsAll("Cuemon", "for", "all")); + } + + [Fact] + public void EqualsAny() + { + Assert.True("Cuemon for .NET".EqualsAny("some sentence", "Cuemon for .NET")); + Assert.False("Cuemon for .NET".EqualsAny(SentenceWithCuemonInIt.Split(' '))); + Assert.False("Cuemon for .NET".EqualsAny(SentenceWithForInIt.Split(' '))); + Assert.False("Cuemon for .NET".EqualsAny(SentenceWithDotNetInIt.Split(' '))); + Assert.False("Cuemon for .NET".EqualsAny("some", "random", "words")); + } + + [Fact] + public void StartsWith() + { + Assert.True("Cuemon for .NET".StartsWith("some sentence", "Cuemon for .NET")); + Assert.True("Cuemon for .NET".StartsWith(SentenceWithCuemonInIt.Split(' '))); + Assert.False("Cuemon for .NET".StartsWith(SentenceWithForInIt.Split(' '))); + Assert.False("Cuemon for .NET".StartsWith(SentenceWithDotNetInIt.Split(' '))); + Assert.False("Cuemon for .NET".StartsWith("some", "random", "words")); + } + + [Fact] + public void ToGuid_ShouldConvertStringToGuid() + { + var g1 = Guid.NewGuid(); + var s1 = g1.ToString("N"); + var g2 = s1.ToGuid(o => o.Formats = GuidFormats.N); + + Assert.Equal(g1, g2); + } + + [Fact] + public void SplitDelimited_ShouldRevertDelimitedStringToSequence() + { + var i = Generate.RangeOf(10, i1 => i1.ToString()); + var s = "0,1,2,3,4,5,6,7,8,9".SplitDelimited().ToList(); + + Assert.Equal(10, s.Count); + Assert.True(s.SequenceEqual(i)); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Core.Tests/TimeSpanExtensionsTest.cs b/test/Cuemon.Extensions.Core.Tests/TimeSpanExtensionsTest.cs new file mode 100644 index 000000000..20cfaf701 --- /dev/null +++ b/test/Cuemon.Extensions.Core.Tests/TimeSpanExtensionsTest.cs @@ -0,0 +1,54 @@ +using System; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions +{ + public class TimeSpanExtensionsTest : Test + { + public TimeSpanExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void GetTotalNanoseconds_ShouldConvertTicksToNanoseconds() + { + var ts = TimeSpan.FromHours(1); + Assert.Equal(3.6E+12, ts.GetTotalNanoseconds()); + } + + [Fact] + public void GetTotalMicroseconds_ShouldConvertTicksToMicroseconds() + { + var ts = TimeSpan.FromHours(1); + Assert.Equal(3.6E+9, ts.GetTotalMicroseconds()); + } + + [Fact] + public void Floor_ShouldRoundDownToNearestUnit() + { + var ts1 = TimeSpan.FromMinutes(280); + var ts2 = TimeSpan.FromMinutes(45); + + Assert.Equal(ts1.Floor(1, TimeUnit.Hours), ts1.Floor(TimeSpan.FromHours(1))); + Assert.Equal(TimeSpan.FromHours(4), ts1.Floor(1, TimeUnit.Hours)); + + Assert.Equal(ts2.Floor(1, TimeUnit.Hours), ts2.Floor(TimeSpan.FromHours(1))); + Assert.Equal(TimeSpan.FromHours(0), ts2.Floor(1, TimeUnit.Hours)); + } + + [Fact] + public void Ceiling_ShouldRoundUpToNearestUnit() + { + var ts1 = TimeSpan.FromMinutes(280); + var ts2 = TimeSpan.FromMinutes(45); + + Assert.Equal(ts1.Ceiling(1, TimeUnit.Hours), ts1.Ceiling(TimeSpan.FromHours(1))); + Assert.Equal(TimeSpan.FromHours(5), ts1.Ceiling(1, TimeUnit.Hours)); + + Assert.Equal(ts2.Ceiling(1, TimeUnit.Hours), ts2.Ceiling(TimeSpan.FromHours(1))); + Assert.Equal(TimeSpan.FromHours(1), ts2.Ceiling(1, TimeUnit.Hours)); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Core.Tests/TypeExtensionsTest.cs b/test/Cuemon.Extensions.Core.Tests/TypeExtensionsTest.cs new file mode 100644 index 000000000..3f1f6b682 --- /dev/null +++ b/test/Cuemon.Extensions.Core.Tests/TypeExtensionsTest.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Reflection.Metadata; +using System.Text; +using System.Xml.Serialization; +using Cuemon.Extensions.Xunit; +using Cuemon.Xml.Serialization; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions +{ + public class TypeExtensionsTest : Test + { + public TypeExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void ToFriendlyName_ShouldConvertTypeToHumanFriendlyRepresentation() + { + var type = typeof(IList); + var typeFriendlyString = type.ToFriendlyName(); + var typeFriendlyFqString = type.ToFriendlyName(o => o.FullName = true); + + Assert.Equal("IList", typeFriendlyString); + Assert.Equal("System.Collections.Generic.IList", typeFriendlyFqString); + } + + [Fact] + public void ToTypeCode_ShouldConvertTypeToCorrectTypeCode() + { + Type o = null; + Assert.Equal(TypeCode.Boolean, typeof(bool).ToTypeCode()); + Assert.Equal(TypeCode.Byte, typeof(byte).ToTypeCode()); + Assert.Equal(TypeCode.Char, typeof(char).ToTypeCode()); + Assert.Equal(TypeCode.DBNull, typeof(DBNull).ToTypeCode()); + Assert.Equal(TypeCode.DateTime, typeof(DateTime).ToTypeCode()); + Assert.Equal(TypeCode.Decimal, typeof(decimal).ToTypeCode()); + Assert.Equal(TypeCode.Double, typeof(double).ToTypeCode()); + Assert.Equal(TypeCode.Empty, o.ToTypeCode()); + Assert.Equal(TypeCode.Int16, typeof(short).ToTypeCode()); + Assert.Equal(TypeCode.Int32, typeof(int).ToTypeCode()); + Assert.Equal(TypeCode.Int64, typeof(long).ToTypeCode()); + Assert.Equal(TypeCode.Object, typeof(object).ToTypeCode()); + Assert.Equal(TypeCode.SByte, typeof(sbyte).ToTypeCode()); + Assert.Equal(TypeCode.Single, typeof(float).ToTypeCode()); + Assert.Equal(TypeCode.String, typeof(string).ToTypeCode()); + Assert.Equal(TypeCode.UInt16, typeof(ushort).ToTypeCode()); + Assert.Equal(TypeCode.UInt32, typeof(uint).ToTypeCode()); + Assert.Equal(TypeCode.UInt64, typeof(ulong).ToTypeCode()); + } + + [Fact] + public void HasEqualityComparerImplementation() + { + var truetype = typeof(StringComparer); + var falseType = typeof(ArgumentException); + Assert.True(truetype.HasEqualityComparerImplementation()); + Assert.False(falseType.HasEqualityComparerImplementation()); + } + + [Fact] + public void HasComparableImplementation() + { + var truetype = typeof(string); + var falseType = typeof(ArgumentException); + Assert.True(truetype.HasComparableImplementation()); + Assert.False(falseType.HasComparableImplementation()); + } + + [Fact] + public void HasComparerImplementation() + { + var truetype = typeof(HandleComparer); + var falseType = typeof(ArgumentException); + Assert.True(truetype.HasComparerImplementation()); + Assert.False(falseType.HasComparerImplementation()); + } + + [Fact] + public void HasEnumerableImplementation() + { + var truetype = typeof(ConcurrentBag<>); + var falseType = typeof(ArgumentException); + Assert.True(truetype.HasEnumerableImplementation()); + Assert.False(falseType.HasEnumerableImplementation()); + } + + [Fact] + public void HasDictionaryImplementation() + { + var truetype = typeof(ConcurrentDictionary<,>); + var falseType = typeof(List<>); + Assert.True(truetype.HasDictionaryImplementation()); + Assert.False(falseType.HasDictionaryImplementation()); + } + + [Fact] + public void HasKeyValuePairImplementation() + { + var truetype = typeof(KeyValuePair<,>); + var falseType = typeof(List<>); + Assert.True(truetype.HasKeyValuePairImplementation()); + Assert.False(falseType.HasKeyValuePairImplementation()); + } + + [Fact] + public void IsNullable() + { + var truetype = typeof(int?); + var falseType = typeof(int); + Assert.True(truetype.IsNullable()); + Assert.False(falseType.IsNullable()); + } + + [Fact] + public void HasAnonymousCharacteristics() + { + var truetype = new Func(s => s).Target.GetType(); + var falseType = typeof(int); + Assert.True(truetype.HasAnonymousCharacteristics()); + Assert.False(falseType.HasAnonymousCharacteristics()); + } + + [Fact] + public void IsComplex() + { + var truetype = typeof(Stream); + var falseType = typeof(int); + Assert.True(truetype.IsComplex()); + Assert.False(falseType.IsComplex()); + } + + [Fact] + public void IsSimple() + { + var truetype = typeof(int); + var falseType = typeof(Stream); + Assert.True(truetype.IsSimple()); + Assert.False(falseType.IsSimple()); + } + + [Fact] + public void GetDefaultValue_ShouldBeDefaultValueFromValueTypesAndReferenceTypesWithDefaultConstructor() + { + Assert.Equal(0, typeof(int).GetDefaultValue()); + Assert.Equal(decimal.Zero, typeof(decimal).GetDefaultValue()); + Assert.Equal(Guid.Empty, typeof(Guid).GetDefaultValue()); + Assert.Equal(DateTime.MinValue, typeof(DateTime).GetDefaultValue()); + Assert.Equal(TimeSpan.Zero, typeof(TimeSpan).GetDefaultValue()); + Assert.Null(typeof(int?).GetDefaultValue()); + Assert.Null(typeof(bool?).GetDefaultValue()); + } + + [Fact] + public void HasTypes_ShouldBeTrueForThoseImplementingTheTypeAndFalseForRest() + { + Assert.True(typeof(FileStream).HasTypes(typeof(Stream))); + Assert.True(typeof(MemoryStream).HasTypes(typeof(MarshalByRefObject))); + Assert.False(typeof(StringBuilder).HasTypes(typeof(string))); + Assert.False(typeof(UTF32Encoding).HasTypes(typeof(string))); + } + + [Fact] + public void HasInterfaces_ShouldBeTrueForThoseImplementingTheInterfaceAndFalseForRest() + { + Assert.True(typeof(FileStream).HasInterfaces(typeof(IDisposable))); + Assert.True(typeof(List<>).HasInterfaces(typeof(IEnumerable<>))); + Assert.False(typeof(StringBuilder).HasInterfaces(typeof(IDisposable))); + Assert.False(typeof(UTF32Encoding).HasInterfaces(typeof(IEnumerable<>))); + } + + [Fact] + public void HasAttributes_ShouldBeTrueForThoseMembersImplementingTheAttributesAndFalseForRest() + { + Assert.True(typeof(DisplayAttribute).HasAttributes(typeof(AttributeUsageAttribute))); + Assert.True(typeof(XmlWrapper).HasAttributes(typeof(XmlIgnoreAttribute))); + Assert.True(typeof(StringBuilder).HasAttributes(typeof(SerializableAttribute))); + Assert.True(typeof(UTF32Encoding).HasAttributes(typeof(CLSCompliantAttribute))); + Assert.False(typeof(int).HasAttributes(typeof(AttributeUsageAttribute))); + Assert.False(typeof(string).HasAttributes(typeof(AttributeUsageAttribute))); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Core.Tests/ValidatorExtensionsTest.cs b/test/Cuemon.Extensions.Core.Tests/ValidatorExtensionsTest.cs index 6faadff8c..397fdc04c 100644 --- a/test/Cuemon.Extensions.Core.Tests/ValidatorExtensionsTest.cs +++ b/test/Cuemon.Extensions.Core.Tests/ValidatorExtensionsTest.cs @@ -7,35 +7,17 @@ namespace Cuemon.Extensions { public class ValidatorExtensionsTest : Test { - public ValidatorExtensionsTest(ITestOutputHelper output = null) : base(output) + public ValidatorExtensionsTest(ITestOutputHelper output) : base(output) { } - [Fact] - public void IfHasDistinctDifference_ShouldThrowArgumentOutOfRangeException() - { - Assert.Throws(() => - { - Validator.Throw.IfHasDistinctDifference("aaabbbccc", "dddeeefff", "paramName"); - }); - } - - [Fact] - public void IfHasNotDistinctDifference_ShouldThrowArgumentOutOfRangeException() - { - Assert.Throws(() => - { - Validator.Throw.IfHasNotDistinctDifference("aaabbbccc", "cccbbbbaaaa", "paramName"); - }); - } - [Fact] public void IfHasDifference_ShouldThrowArgumentOutOfRangeException() { Assert.Throws(() => { - Validator.Throw.IfHasDifference("aaabbbccc", "dddeeefff", "paramName"); + Validator.ThrowIf.HasDifference("aaabbbccc", "dddeeefff", "paramName"); }); } @@ -44,7 +26,7 @@ public void IfHasNotDifference_ShouldThrowArgumentOutOfRangeException() { Assert.Throws(() => { - Validator.Throw.IfHasNotDifference("aaabbbccc", "cccbbbbaaaa", "paramName"); + Validator.ThrowIf.NoDifference("aaabbbccc", "cccbbbbaaaa", "paramName"); }); } } diff --git a/test/Cuemon.Extensions.Data.Integrity.Tests/AssemblyExtensionsTest.cs b/test/Cuemon.Extensions.Data.Integrity.Tests/AssemblyExtensionsTest.cs new file mode 100644 index 000000000..f509c033f --- /dev/null +++ b/test/Cuemon.Extensions.Data.Integrity.Tests/AssemblyExtensionsTest.cs @@ -0,0 +1,30 @@ +using Cuemon.Data.Integrity; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Data.Integrity +{ + public class AssemblyExtensionsTest : Test + { + public AssemblyExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void GetCacheValidator_ShouldHaveStrongIntegrityChecksum() + { + + var a = typeof(AssemblyExtensionsTest).Assembly; + var cv1 = a.GetCacheValidator(); + Assert.Equal(EntityDataIntegrityValidation.Weak, cv1.Validation); + var cv2 = a.GetCacheValidator(setup: o => o.BytesToRead = 400); + Assert.Equal(EntityDataIntegrityValidation.Strong, cv2.Validation); + Assert.NotEqual(cv1.ToString(), CacheValidator.Default.ToString()); + Assert.NotEqual(cv2.ToString(), CacheValidator.Default.ToString()); + Assert.NotEqual(cv1.ToString(), cv2.ToString()); + TestOutput.WriteLine(cv1.ToString()); + TestOutput.WriteLine(cv2.ToString()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Data.Integrity.Tests/Cuemon.Extensions.Data.Integrity.Tests.csproj b/test/Cuemon.Extensions.Data.Integrity.Tests/Cuemon.Extensions.Data.Integrity.Tests.csproj new file mode 100644 index 000000000..de0895e5b --- /dev/null +++ b/test/Cuemon.Extensions.Data.Integrity.Tests/Cuemon.Extensions.Data.Integrity.Tests.csproj @@ -0,0 +1,11 @@ + + + + Cuemon.Extensions.Data.Integrity + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Data.Integrity.Tests/DateTimeExtensionsTest.cs b/test/Cuemon.Extensions.Data.Integrity.Tests/DateTimeExtensionsTest.cs new file mode 100644 index 000000000..48068cb45 --- /dev/null +++ b/test/Cuemon.Extensions.Data.Integrity.Tests/DateTimeExtensionsTest.cs @@ -0,0 +1,55 @@ +using System; +using Cuemon.Data.Integrity; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Data.Integrity +{ + public class DateTimeExtensionsTest : Test + { + public DateTimeExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void GetCacheValidator_UseDefaultMethod_ShouldHaveUnspecifiedIntegrityValidation() + { + var dt = DateTime.UnixEpoch.GetCacheValidator(); + var expected = "6c62272e07bb014262b821756295c58d"; + Assert.Equal(expected, dt.ToString()); + Assert.Equal(EntityDataIntegrityValidation.Unspecified, dt.Validation); + TestOutput.WriteLine(dt.ToString()); + } + + [Fact] + public void GetCacheValidator_UseTimestampMethod_ShouldHaveUnspecifiedIntegrityValidation() + { + var dt = DateTime.UnixEpoch.GetCacheValidator(DateTime.UnixEpoch.AddDays(7), method: EntityDataIntegrityMethod.Timestamp); + var expected = "1192c6957365995ad3a62bff3cf1b3ad"; + Assert.Equal(expected, dt.ToString()); + Assert.Equal(EntityDataIntegrityValidation.Unspecified, dt.Validation); + TestOutput.WriteLine(dt.ToString()); + } + + [Fact] + public void GetCacheValidator_UseDefaultMethod_ShouldHaveWeakIntegrityValidation() + { + var dt = DateTime.UnixEpoch.GetCacheValidator(DateTime.UnixEpoch.AddDays(7), Convertible.GetBytes(1234567890)); + var expected = "65567817ef757277b806e833c0139a60"; + Assert.Equal(expected, dt.ToString()); + Assert.Equal(EntityDataIntegrityValidation.Weak, dt.Validation); + TestOutput.WriteLine(dt.ToString()); + } + + [Fact] + public void GetCacheValidator_UseDefaultMethod_ShouldHaveStrongIntegrityValidation() + { + var dt = DateTime.UnixEpoch.GetCacheValidator(DateTime.UnixEpoch.AddDays(7), Convertible.GetBytes(1234567890), EntityDataIntegrityValidation.Strong); + var expected = "65567817ef757277b806e833c0139a60"; + Assert.Equal(expected, dt.ToString()); + Assert.Equal(EntityDataIntegrityValidation.Strong, dt.Validation); + TestOutput.WriteLine(dt.ToString()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Diagnostics.Tests/Cuemon.Extensions.Diagnostics.Tests.csproj b/test/Cuemon.Extensions.Diagnostics.Tests/Cuemon.Extensions.Diagnostics.Tests.csproj index 51ff59d46..413175ffd 100644 --- a/test/Cuemon.Extensions.Diagnostics.Tests/Cuemon.Extensions.Diagnostics.Tests.csproj +++ b/test/Cuemon.Extensions.Diagnostics.Tests/Cuemon.Extensions.Diagnostics.Tests.csproj @@ -1,29 +1,11 @@ - netcoreapp3.1 - - false - Cuemon.Extensions.Diagnostics - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Integrity.Tests/AssemblyExtensionsTest.cs b/test/Cuemon.Extensions.Integrity.Tests/AssemblyExtensionsTest.cs deleted file mode 100644 index 97b560636..000000000 --- a/test/Cuemon.Extensions.Integrity.Tests/AssemblyExtensionsTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Cuemon.Data.Integrity; -using Cuemon.Extensions.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace Cuemon.Extensions.Data.Integrity -{ - public class AssemblyExtensionsTest : Test - { - public AssemblyExtensionsTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void GetCacheValidator_ShouldHaveStrongIntegrityChecksum() - { - - var a = typeof(AssemblyExtensionsTest).Assembly; - var cv = a.GetCacheValidator(); - Assert.Equal(EntityDataIntegrityStrength.Strong, cv.Strength); - Assert.NotEqual(cv.ToString(), CacheValidator.Default.ToString()); - TestOutput.WriteLine(cv.ToString()); - } - } -} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Integrity.Tests/Cuemon.Extensions.Data.Integrity.Tests.csproj b/test/Cuemon.Extensions.Integrity.Tests/Cuemon.Extensions.Data.Integrity.Tests.csproj deleted file mode 100644 index 572cbb288..000000000 --- a/test/Cuemon.Extensions.Integrity.Tests/Cuemon.Extensions.Data.Integrity.Tests.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - netcoreapp3.1 - - false - - Cuemon.Extensions.Integrity.Tests - - Cuemon.Extensions.Data.Integrity - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - diff --git a/test/Cuemon.Extensions.Integrity.Tests/DateTimeExtensionsTest.cs b/test/Cuemon.Extensions.Integrity.Tests/DateTimeExtensionsTest.cs deleted file mode 100644 index 7cc28f3a2..000000000 --- a/test/Cuemon.Extensions.Integrity.Tests/DateTimeExtensionsTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Cuemon.Data.Integrity; -using Cuemon.Extensions.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace Cuemon.Extensions.Data.Integrity -{ - public class DateTimeExtensionsTest : Test - { - public DateTimeExtensionsTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void GetCacheValidator_ShouldHaveNoneIntegrityChecksum() - { - var dt = DateTime.UnixEpoch.GetCacheValidator(); - var expected = "d41d8cd98f00b204e9800998ecf8427e"; - Assert.Equal(expected, dt.ToString()); - Assert.Equal(EntityDataIntegrityStrength.Unspecified, dt.Strength); - TestOutput.WriteLine(dt.ToString()); - } - - [Fact] - public void GetCacheValidator_ShouldHaveWeakIntegrityChecksum() - { - var dt = DateTime.UnixEpoch.GetCacheValidator(DateTime.UnixEpoch.AddDays(7), o => o.Method = EntityDataIntegrityMethod.Timestamp); - var expected = "15f5959e2cf903cf2bf3dc93d4c89cb3"; - Assert.Equal(expected, dt.ToString()); - Assert.Equal(EntityDataIntegrityStrength.Weak, dt.Strength); - TestOutput.WriteLine(dt.ToString()); - } - - [Fact] - public void GetCacheValidator_ShouldHaveStrongIntegrityChecksum() - { - var dt = DateTime.UnixEpoch.GetCacheValidator(DateTime.UnixEpoch.AddDays(7), 1234567890); - var expected = "bbb1f04aceb3b6903f7f364a25501220"; - Assert.Equal(expected, dt.ToString()); - Assert.Equal(EntityDataIntegrityStrength.Strong, dt.Strength); - TestOutput.WriteLine(dt.ToString()); - } - } -} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Net.Tests/Cuemon.Extensions.Net.Tests.csproj b/test/Cuemon.Extensions.Net.Tests/Cuemon.Extensions.Net.Tests.csproj index 99fc921bf..6c47fd52f 100644 --- a/test/Cuemon.Extensions.Net.Tests/Cuemon.Extensions.Net.Tests.csproj +++ b/test/Cuemon.Extensions.Net.Tests/Cuemon.Extensions.Net.Tests.csproj @@ -1,30 +1,12 @@ - netcoreapp3.1 - - false - Cuemon.Extensions.Net - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs b/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs index 7ad54736d..4f9d7a526 100644 --- a/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs +++ b/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs @@ -27,9 +27,9 @@ public async Task HttpGetAsync_ShouldGetResponseFromUri() var uri = new Uri("https://www.cuemon.net/"); var expected = 125; var atomicCount = 0; - await ParallelFactory.ForAsync(0, expected, i => + await ParallelFactory.ForAsync(0, expected, async (i, ct) => { - using (var response = uri.HttpGetAsync().GetAwaiter().GetResult()) + using (var response = await uri.HttpGetAsync(ct)) { Interlocked.Increment(ref atomicCount); Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Cuemon.Extensions.Newtonsoft.Json.Tests.csproj b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Cuemon.Extensions.Newtonsoft.Json.Tests.csproj new file mode 100644 index 000000000..febd3b6cf --- /dev/null +++ b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Cuemon.Extensions.Newtonsoft.Json.Tests.csproj @@ -0,0 +1,13 @@ + + + + Cuemon.Extensions.Newtonsoft.Json + + + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Formatters/JsonFormatterOptionsTest.cs b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Formatters/JsonFormatterOptionsTest.cs new file mode 100644 index 000000000..13878ef2c --- /dev/null +++ b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Formatters/JsonFormatterOptionsTest.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Cuemon.Extensions.Xunit; +using Newtonsoft.Json; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Newtonsoft.Json.Formatters +{ + public class JsonFormatterOptionsTest : Test + { + public JsonFormatterOptionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void DefaultConverters_ShouldHaveSameAmountOfDefaultConverters() + { + var defaultConverters = new List(); + JsonFormatterOptions.DefaultConverters(defaultConverters); + + var x = new JsonFormatterOptions(); + var y = new JsonFormatterOptions(); + var bootstrapInvocationList = JsonFormatterOptions.DefaultConverters.GetInvocationList().Length; + + Assert.Equal(4, defaultConverters.Count); + Assert.Equal(1, bootstrapInvocationList); + Assert.Equal(2, x.Settings.Converters.Count - defaultConverters.Count); + Assert.Equal(2, y.Settings.Converters.Count - defaultConverters.Count); + + Assert.Equal(x.Settings.Converters.Count, y.Settings.Converters.Count); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Formatters/JsonFormatterTest.cs b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Formatters/JsonFormatterTest.cs new file mode 100644 index 000000000..7ad08f5ee --- /dev/null +++ b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/Formatters/JsonFormatterTest.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using Cuemon.Extensions.IO; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Newtonsoft.Json.Formatters +{ + public class JsonFormatterTest : Test + { + public JsonFormatterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Serialize_ShouldSerializeUsingExceptionConverter() + { + try + { + throw new OutOfMemoryException("First", new AggregateException(new AccessViolationException("I1"), new AbandonedMutexException("I2"), new ArithmeticException("I3"))); + } + catch (Exception e) + { + e.Data.Add("Cuemon", "XmlFormatterTest"); + var f = new JsonFormatter(o => + { + o.IncludeExceptionStackTrace = true; + }); + var r = f.Serialize(e); + var x = new StreamReader(r).ReadAllLines().ToList(); + Assert.Contains(e.Data.Keys.Cast(), s => s.Equals("Cuemon")); + Assert.Contains(e.Data.Values.Cast(), s => s.Equals("XmlFormatterTest")); + Assert.Equal("{", x[0]); + Assert.Contains("\"Type\": \"System.OutOfMemoryException\"", x[1]); + Assert.Contains("\"Source\": \"Cuemon.Extensions.Newtonsoft.Json.Tests\"", x[2]); + Assert.Contains("\"Message\": \"First\"", x[3]); + Assert.Contains("\"Stack\": [", x[4]); + Assert.Contains("at Cuemon.Extensions.Newtonsoft.Json.Formatters.JsonFormatterTest", x[5]); + Assert.Contains("\"Data\": {", x[7]); + Assert.Contains("\"Cuemon\": \"XmlFormatterTest\"", x[8]); + Assert.Contains("},", x[9]); + Assert.Contains("\"Inner\": {", x[10]); + Assert.Contains("\"Type\": \"System.AggregateException\",", x[11]); + Assert.Contains("\"Type\": \"System.AccessViolationException\"", x[14]); + Assert.Contains("\"Type\": \"System.Threading.AbandonedMutexException\"", x[17]); + Assert.Contains("\"Type\": \"System.ArithmeticException\"", x[21]); + + TestOutput.WriteLine(r.ToEncodedString()); + r.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Newtonsoft.Json.Tests/JDataTest.cs b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/JDataTest.cs new file mode 100644 index 000000000..14404efb3 --- /dev/null +++ b/test/Cuemon.Extensions.Newtonsoft.Json.Tests/JDataTest.cs @@ -0,0 +1,113 @@ +using System; +using System.Linq; +using System.Threading; +using Cuemon.Extensions.IO; +using Cuemon.Extensions.Newtonsoft.Json.Formatters; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Newtonsoft.Json +{ + public class JDataTest : Test + { + public JDataTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void ReadAll_PrimitiveValuesShouldBeCompliantWithRfc7159() + { + object[] primitives = { "null", 1, 2, 3, 4, 5, 6, 7, 8, 9, "\"cuemon\"", false, true, 100.4 }; + + foreach (var p in primitives) + { + var f = new JsonFormatter(); + var r = f.Serialize(p); + + var x0 = JData.ReadAll(r, o => o.LeaveOpen = true); + + TestOutput.WriteLine(DelimitedString.Create(x0, o => o.Delimiter = Environment.NewLine)); + TestOutput.WriteLine(r.ToEncodedString()); + } + } + + [Fact] + public void ReadAll_ShouldReadSimpleObject() + { + var e = new ArgumentException("The amazing message of this exception.", "fakeArg"); + var f = new JsonFormatter(); + var r = f.Serialize(e); + var x0 = JData.ReadAll(r, o => o.LeaveOpen = true); + + TestOutput.WriteLine(DelimitedString.Create(x0, o => o.Delimiter = Environment.NewLine)); + + TestOutput.WriteLine(r.ToEncodedString()); + } + + [Fact] + public void ReadAll_ShouldReadSimpleArray() + { + var a = Generate.RangeOf(50, i => i); + var f = new JsonFormatter(); + var r = f.Serialize(a); + var x0 = JData.ReadAll(r, o => o.LeaveOpen = true).ToList(); + + for (var i = 0; i < 50; i++) + { + Assert.Equal(Convert.ChangeType(i, x0[i].Type), x0[i].Value); // should be int32 - but Newtonsoft resolves it as int64 + } + + TestOutput.WriteLine(r.ToEncodedString()); + } + + [Fact] + public void ReadAll_ShouldHaveOuterAndNestedExceptionsBothHierarchyAndFlattened() + { + var e = new OutOfMemoryException("First", new AggregateException(new AccessViolationException("I1"), new AbandonedMutexException("I2"), new ArithmeticException("I3"))); + var f = new JsonFormatter(); + var r = f.Serialize(e); + var x0 = JData.ReadAll(r, o => o.LeaveOpen = true); + var x1 = x0.Last(r => r.Children.Any()).Children; + var x2 = x1.Last(r => r.Children.Any()).Children; + var x3 = x2.Last(r => r.Children.Any()).Children; + var x4 = x3.Last(r => r.Children.Any()).Children; + var xFlat = x0.Flatten().ToList(); + + Assert.Equal(3, x0.Count()); + Assert.Equal(3, x1.Count); + Assert.Equal(3, x2.Count); + Assert.Equal(4, x3.Count); + Assert.Equal(2, x4.Count); + + Assert.Equal("System.OutOfMemoryException", x0.Single(result => result.PropertyName == "Type").Value); + Assert.Equal("First", x0.Single(result => result.PropertyName == "Message").Value); + Assert.Equal("System.AggregateException", x1.Single(result => result.PropertyName == "Type").Value); + Assert.Equal("One or more errors occurred. (I1) (I2) (I3)", x1.Single(result => result.PropertyName == "Message").Value); + Assert.Equal("System.AccessViolationException", x2.Single(result => result.PropertyName == "Type").Value); + Assert.Equal("I1", x2.Single(result => result.PropertyName == "Message").Value); + Assert.Equal("System.Threading.AbandonedMutexException", x3.Single(result => result.PropertyName == "Type").Value); + Assert.Equal("I2", x3.Single(result => result.PropertyName == "Message").Value); + var x3jdr = x3.Single(result => result.PropertyName == "MutexIndex"); + Assert.Equal(Convert.ChangeType(-1, x3jdr.Type), x3jdr.Value); // should be int32 - but Newtonsoft resolves it as int64 + Assert.Equal("System.ArithmeticException", x4.Single(result => result.PropertyName == "Type").Value); + Assert.Equal("I3", x4.Single(result => result.PropertyName == "Message").Value); + + Assert.Equal("System.OutOfMemoryException", xFlat[0].Value); + Assert.Equal("First", xFlat[1].Value); + Assert.Equal("System.AggregateException", xFlat[3].Value); + Assert.Equal("One or more errors occurred. (I1) (I2) (I3)", xFlat[4].Value); + Assert.Equal("System.AccessViolationException", xFlat[6].Value); + Assert.Equal("I1", xFlat[7].Value); + Assert.Equal("System.Threading.AbandonedMutexException", xFlat[9].Value); + Assert.Equal("I2", xFlat[10].Value); + Assert.Equal(Convert.ChangeType(-1, xFlat[11].Type), xFlat[11].Value); // should be int32 - but Newtonsoft resolves it as int64 + Assert.Equal("System.ArithmeticException", xFlat[13].Value); + Assert.Equal("I3", xFlat[14].Value); + + TestOutput.WriteLine(DelimitedString.Create(xFlat, o => o.Delimiter = Environment.NewLine)); + TestOutput.WriteLine(""); + TestOutput.WriteLine(r.ToEncodedString()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Runtime.Caching.Tests/Assets/CountdownDependency.cs b/test/Cuemon.Extensions.Runtime.Caching.Tests/Assets/CountdownDependency.cs new file mode 100644 index 000000000..f55c5f198 --- /dev/null +++ b/test/Cuemon.Extensions.Runtime.Caching.Tests/Assets/CountdownDependency.cs @@ -0,0 +1,43 @@ +using System; +using System.Diagnostics; +using System.Threading; +using Cuemon.Runtime; +using Cuemon.Threading; + +namespace Cuemon.Extensions.Runtime.Caching.Assets +{ + public class CountdownDependency : Dependency, IDisposable + { + private Timer _handler; + private TimeSpan _timer; + private Stopwatch _sw = Stopwatch.StartNew(); + + public CountdownDependency(TimeSpan timer) + { + _timer = timer; + } + + private void OnCountdown() + { + _timer -= TimeSpan.FromSeconds(1); + if (_timer < TimeSpan.Zero) + { + _timer = TimeSpan.Zero; + _handler?.Dispose(); + _handler = null; + } + } + + public override bool HasChanged => _timer == TimeSpan.Zero; + + public override void Start() + { + _handler = TimerFactory.CreateNonCapturingTimer(state => ((CountdownDependency)state).OnCountdown(), this, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); + } + + public void Dispose() + { + _handler?.Dispose(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Runtime.Caching.Tests/CacheEnumerableExtensionsTest.cs b/test/Cuemon.Extensions.Runtime.Caching.Tests/CacheEnumerableExtensionsTest.cs new file mode 100644 index 000000000..6e2ba9477 --- /dev/null +++ b/test/Cuemon.Extensions.Runtime.Caching.Tests/CacheEnumerableExtensionsTest.cs @@ -0,0 +1,949 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Cuemon.Extensions.Runtime.Caching.Assets; +using Cuemon.Extensions.Xunit.Hosting; +using Cuemon.Runtime.Caching; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Runtime.Caching +{ + public class CacheEnumerableExtensionsTest : HostTest + { + private readonly SlimMemoryCache _cache; + private readonly SlimMemoryCacheOptions _cacheOptions = new SlimMemoryCacheOptions(); + + public CacheEnumerableExtensionsTest(HostFixture hostFixture, ITestOutputHelper output = null) : base(hostFixture, output) + { + _cache = hostFixture.ServiceProvider.GetRequiredService(); + } + + [Fact] + public void GetOrAdd_ShouldCacheAndReturnItemInOneGoUsingSlidingExpirationOfTenSeconds() + { + var items = 1000; + var expires = TimeSpan.FromSeconds(10); + var keys = Generate.RangeOf(items, i => Guid.NewGuid().ToString("N")).ToList(); + var bag = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.ForEach(keys, key => + { + bag.Add(_cacheOptions.KeyProvider(key, CacheEntry.NoScope)); + var value = Generate.RandomString(5); + Assert.Equal(value, _cache.GetOrAdd(key, expires, () => value)); + }); + + Assert.Equal(items, _cache.Count()); + Assert.True(bag.OrderBy(l => l).SequenceEqual(_cache.Where(pair => pair.Value.Namespace == CacheEntry.NoScope).Select(pair => pair.Key).OrderBy(l => l))); // insure thread safety validation + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count()); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateUsingSlidingExpirationOfTenSeconds() + { + var expires = TimeSpan.FromSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func(ExpensiveRandomString); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs()); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(17, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingOneParameterUsingSlidingExpirationOfTenSeconds() + { + var expires = TimeSpan.FromSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func(p1 => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(3)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(3, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingTwoParameterUsingSlidingExpirationOfTenSeconds() + { + var expires = TimeSpan.FromSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(2, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingThreeParameterUsingSlidingExpirationOfTenSeconds() + { + var expires = TimeSpan.FromSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(5, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingFourParameterUsingSlidingExpirationOfTenSeconds() + { + var expires = TimeSpan.FromSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3, p4) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3 + p4); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3, 2)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(7, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingFiveParameterUsingSlidingExpirationOfTenSeconds() + { + var expires = TimeSpan.FromSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3, p4, p5) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3 + p4 + p5); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3, 2, 4)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(11, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateUsingAbsoluteExpirationOfTenSeconds() + { + var expires = DateTime.UtcNow.AddSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func(ExpensiveRandomString); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs()); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(17, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingOneParameterUsingAbsoluteExpirationOfTenSeconds() + { + var expires = DateTime.UtcNow.AddSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func(p1 => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(3)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(3, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingTwoParameterUsingAbsoluteExpirationOfTenSeconds() + { + var expires = DateTime.UtcNow.AddSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(2, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingThreeParameterUsingAbsoluteExpirationOfTenSeconds() + { + var expires = DateTime.UtcNow.AddSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(5, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingFourParameterUsingAbsoluteExpirationOfTenSeconds() + { + var expires = DateTime.UtcNow.AddSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3, p4) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3 + p4); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3, 2)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(7, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingFiveParameterUsingAbsoluteExpirationOfTenSeconds() + { + var expires = DateTime.UtcNow.AddSeconds(10); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3, p4, p5) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3 + p4 + p5); + }); + var rs = _cache.Memoize(expires, value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3, 2, 4)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(11, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateUsingDependencyExpirationOfTenSeconds() + { + var expires = new Func(() => new CountdownDependency(TimeSpan.FromSeconds(10))); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func(ExpensiveRandomString); + var rs = _cache.Memoize(expires(), value); + var sw = Stopwatch.StartNew(); + values.Add(rs()); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(17, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingOneParameterUsingDependencyExpirationOfTenSeconds() + { + var expires = new Func(() => new CountdownDependency(TimeSpan.FromSeconds(10))); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func(p1 => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1); + }); + var rs = _cache.Memoize(expires(), value); + var sw = Stopwatch.StartNew(); + values.Add(rs(3)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(3, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingTwoParameterUsingDependencyExpirationOfTenSeconds() + { + var expires = new Func(() => new CountdownDependency(TimeSpan.FromSeconds(10))); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2); + }); + var rs = _cache.Memoize(expires(), value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(2, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingThreeParameterUsingDependencyExpirationOfTenSeconds() + { + var expires = new Func(() => new CountdownDependency(TimeSpan.FromSeconds(10))); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3); + }); + var rs = _cache.Memoize(expires(), value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(5, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingFourParameterUsingDependencyExpirationOfTenSeconds() + { + var expires = new Func(() => new CountdownDependency(TimeSpan.FromSeconds(10))); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3, p4) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3 + p4); + }); + var rs = _cache.Memoize(expires(), value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3, 2)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(7, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + [Fact] + public void Memoize_ShouldCacheAndReturnFunctionDelegateHavingFiveParameterUsingDependencyExpirationOfTenSeconds() + { + var expires = new Func(() => new CountdownDependency(TimeSpan.FromSeconds(10))); + var timeSpans = new ConcurrentBag(); + var values = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the extension method + Parallel.For(0, 1000, i => + { + var value = new Func((p1, p2, p3, p4, p5) => + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(p1 + p2 + p3 + p4 + p5); + }); + var rs = _cache.Memoize(expires(), value); + var sw = Stopwatch.StartNew(); + values.Add(rs(1, 1, 3, 2, 4)); + sw.Stop(); + timeSpans.Add(sw.Elapsed); + }); + + var s = Assert.Single(values.Distinct()); + Assert.Equal(11, s.Length); + Assert.True(Condition.IsPrime(s.Length)); + + var turtle = timeSpans.Where(ts => ts > TimeSpan.FromSeconds(1)).ToList(); + var rabbit = timeSpans.Where(ts => ts < TimeSpan.FromSeconds(1)).ToList(); + + foreach (var writeLockHit in turtle) + { + Assert.InRange(writeLockHit, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); + } + + TestOutput.WriteLine($"Suspected hit-rate of PadLock: {turtle.Count}."); + TestOutput.WriteLine($"The rest, {rabbit.Count}, had {rabbit.Count(ts => ts < TimeSpan.FromMilliseconds(25))} in expected range (<25ms)."); + TestOutput.WriteLine(s); + + foreach (var nonLockHit in rabbit) + { + Assert.InRange(nonLockHit, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + + Thread.Sleep(TimeSpan.FromSeconds(11)); + + Assert.Equal(0, _cache.Count(CacheEnumerableExtensions.MemoizationScope)); + } + + private string ExpensiveRandomString() + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + return Generate.RandomString(17); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Runtime.Caching.Tests/Cuemon.Extensions.Runtime.Caching.Tests.csproj b/test/Cuemon.Extensions.Runtime.Caching.Tests/Cuemon.Extensions.Runtime.Caching.Tests.csproj new file mode 100644 index 000000000..e9e35eb08 --- /dev/null +++ b/test/Cuemon.Extensions.Runtime.Caching.Tests/Cuemon.Extensions.Runtime.Caching.Tests.csproj @@ -0,0 +1,12 @@ + + + + Cuemon.Extensions.Runtime.Caching + + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xml.Tests/Namespace.xml b/test/Cuemon.Extensions.Xml.Tests/Assets/Namespace.xml similarity index 100% rename from test/Cuemon.Extensions.Xml.Tests/Namespace.xml rename to test/Cuemon.Extensions.Xml.Tests/Assets/Namespace.xml diff --git a/test/Cuemon.Extensions.Xml.Tests/Cuemon.Extensions.Xml.Tests.csproj b/test/Cuemon.Extensions.Xml.Tests/Cuemon.Extensions.Xml.Tests.csproj index c070c845b..d0573d643 100644 --- a/test/Cuemon.Extensions.Xml.Tests/Cuemon.Extensions.Xml.Tests.csproj +++ b/test/Cuemon.Extensions.Xml.Tests/Cuemon.Extensions.Xml.Tests.csproj @@ -1,10 +1,6 @@ - netcoreapp3.1 - - false - Cuemon.Extensions.Xml @@ -13,27 +9,13 @@ - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs new file mode 100644 index 000000000..059e3bd1b --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading.Tasks; +using Cuemon.Extensions.IO; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Assets; +using Cuemon.Extensions.Xunit.Hosting.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore +{ + public class AspNetCoreHostTestTest : AspNetCoreHostTest + { + private readonly IServiceProvider _provider; + private readonly IApplicationBuilder _pipeline; + + public AspNetCoreHostTestTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _pipeline = hostFixture.Application; + _provider = hostFixture.ServiceProvider; + } + + [Fact] + public async Task ShouldHaveResultOfBoolMiddlewareInBody() + { + var context = _provider.GetRequiredService().HttpContext; + var options = _provider.GetRequiredService>(); + var pipeline = _pipeline.Build(); + + Assert.Equal("Hello awesome developers!", context.Response.Body.ToEncodedString(o => o.LeaveOpen = true)); + + await pipeline(context); + + Assert.Equal("A:True, B:False, C:True, D:False, E:True, F:False", context.Response.Body.ToEncodedString()); + + Assert.True(options.Value.A); + Assert.False(options.Value.B); + Assert.True(options.Value.C); + Assert.False(options.Value.D); + Assert.True(options.Value.E); + Assert.False(options.Value.F); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseMiddleware(); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddTransient(); + services.Configure(o => + { + o.A = true; + o.C = true; + o.E = true; + }); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/BoolMiddleware.cs b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/BoolMiddleware.cs new file mode 100644 index 000000000..59431861d --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/BoolMiddleware.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; +using Cuemon.AspNetCore; +using Cuemon.AspNetCore.Http.Headers; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Assets +{ + public class BoolMiddleware : ConfigurableMiddleware + { + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The which need to be configured. + public BoolMiddleware(RequestDelegate next, IOptions setup) : base(next, setup) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The delegate of the request pipeline to invoke. + /// The which need to be configured. + public BoolMiddleware(RequestDelegate next, Action setup) : base(next, setup) + { + } + + /// + /// Executes the . + /// + /// The context of the current request. + /// A task that represents the execution of this middleware. + public override async Task InvokeAsync(HttpContext context) + { + await context.Response.WriteAsync($"A:{Options.A}, B:{Options.B}, C:{Options.C}, D:{Options.D}, E:{Options.E}, F:{Options.F}"); + await Next(context); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/BoolOptions.cs b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/BoolOptions.cs new file mode 100644 index 000000000..ce60911aa --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/BoolOptions.cs @@ -0,0 +1,17 @@ +namespace Cuemon.Extensions.Xunit.Hosting.AspNetCore.Assets +{ + public class BoolOptions + { + public bool A { get; set; } + + public bool B { get; set; } + + public bool C { get; set; } + + public bool D { get; set; } + + public bool E { get; set; } + + public bool F { get; set; } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj new file mode 100644 index 000000000..f5bd81dba --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj @@ -0,0 +1,12 @@ + + + + Cuemon.Extensions.Xunit.Hosting.AspNetCore + + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/Correlation.cs b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/Correlation.cs new file mode 100644 index 000000000..4d6706a2d --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/Correlation.cs @@ -0,0 +1,15 @@ +using System; +using Cuemon.Messaging; + +namespace Cuemon.Extensions.Xunit.Hosting.Assets +{ + public abstract class Correlation : ICorrelation + { + protected Correlation() + { + CorrelationId = Guid.NewGuid().ToString("N"); + } + + public string CorrelationId { get; } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/ScopedCorrelation.cs b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/ScopedCorrelation.cs new file mode 100644 index 000000000..0e2ba710e --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/ScopedCorrelation.cs @@ -0,0 +1,6 @@ +namespace Cuemon.Extensions.Xunit.Hosting.Assets +{ + public sealed class ScopedCorrelation : Correlation + { + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/SingletonCorrelation.cs b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/SingletonCorrelation.cs new file mode 100644 index 000000000..0ac794c95 --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/SingletonCorrelation.cs @@ -0,0 +1,6 @@ +namespace Cuemon.Extensions.Xunit.Hosting.Assets +{ + public sealed class SingletonCorrelation : Correlation + { + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/TransientCorrelation.cs b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/TransientCorrelation.cs new file mode 100644 index 000000000..f6728f356 --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Assets/TransientCorrelation.cs @@ -0,0 +1,6 @@ +namespace Cuemon.Extensions.Xunit.Hosting.Assets +{ + public sealed class TransientCorrelation : Correlation + { + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.Tests/Cuemon.Extensions.Xunit.Hosting.Tests.csproj b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Cuemon.Extensions.Xunit.Hosting.Tests.csproj new file mode 100644 index 000000000..79aeb8c97 --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.Tests/Cuemon.Extensions.Xunit.Hosting.Tests.csproj @@ -0,0 +1,25 @@ + + + + Cuemon.Extensions.Xunit.Hosting + + + + + + + + + Always + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.Tests/HostTestTest.cs b/test/Cuemon.Extensions.Xunit.Hosting.Tests/HostTestTest.cs new file mode 100644 index 000000000..950999400 --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.Tests/HostTestTest.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Cuemon.Extensions.Xunit.Hosting.Assets; +using Cuemon.Messaging; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; +using Xunit.Priority; + +namespace Cuemon.Extensions.Xunit.Hosting +{ + [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] + public class HostTestTest : HostTest + { + private readonly Func> _correlationsFactory; + private static readonly ConcurrentBag ScopedCorrelations = new ConcurrentBag(); + + public HostTestTest(HostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _correlationsFactory = () => hostFixture.ServiceProvider.GetServices().ToList(); + } + + [Fact, Priority(1)] + public void Test_SingletonShouldBeSame() + { + ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation)); + var c1 = _correlationsFactory().Single(c => c is SingletonCorrelation); + var c2 = _correlationsFactory().Single(c => c is SingletonCorrelation); + Assert.Equal(c1.CorrelationId, c2.CorrelationId); + } + + [Fact, Priority(2)] + public void Test_TransientShouldBeDifferent() + { + ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation)); + var c1 = _correlationsFactory().Single(c => c is TransientCorrelation); + var c2 = _correlationsFactory().Single(c => c is TransientCorrelation); + Assert.NotEqual(c1.CorrelationId, c2.CorrelationId); + } + + [Fact, Priority(3)] + public void Test_ScopedShouldBeSame() + { + ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation)); + var c1 = _correlationsFactory().Single(c => c is ScopedCorrelation); + var c2 = _correlationsFactory().Single(c => c is ScopedCorrelation); + Assert.Equal(c1.CorrelationId, c2.CorrelationId); + } + + [Fact] + public void Test_ScopedShouldBeSameInLastTestRun() + { + var c1 = _correlationsFactory().Single(c => c is ScopedCorrelation); + if (ScopedCorrelations.IsEmpty) { return; } + Assert.Equal(3, ScopedCorrelations.Count); + Assert.All(ScopedCorrelations, c => Assert.Equal(c1.CorrelationId, c.CorrelationId)); + } + + [Fact] + public void Test_ShouldHaveConfigurationEntry() + { + Assert.Equal("xUnit", Configuration.GetSection("unitTestTool").Value); + } + + [Fact] + public void Test_ShouldHaveEnvironmentOfDevelopment() + { + Assert.Equal("Development", HostingEnvironment.EnvironmentName); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddTransient(); + services.AddScoped(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Hosting.Tests/appsettings.json b/test/Cuemon.Extensions.Xunit.Hosting.Tests/appsettings.json new file mode 100644 index 000000000..ebbc38c92 --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Hosting.Tests/appsettings.json @@ -0,0 +1,3 @@ +{ + "unitTestTool": "xUnit" +} \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Tests/Cuemon.Extensions.Xunit.Tests.csproj b/test/Cuemon.Extensions.Xunit.Tests/Cuemon.Extensions.Xunit.Tests.csproj new file mode 100644 index 000000000..5960bf3ea --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Tests/Cuemon.Extensions.Xunit.Tests.csproj @@ -0,0 +1,7 @@ + + + + Cuemon.Extensions.Xunit + + + \ No newline at end of file diff --git a/test/Cuemon.Extensions.Xunit.Tests/TestTest.cs b/test/Cuemon.Extensions.Xunit.Tests/TestTest.cs new file mode 100644 index 000000000..a9deab856 --- /dev/null +++ b/test/Cuemon.Extensions.Xunit.Tests/TestTest.cs @@ -0,0 +1,48 @@ +using System; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Cuemon.Extensions.Xunit +{ + public class TestTest : Test + { + private const string ExpectedStringValue = "AllIsGood"; + private bool _onDisposeManagedResourcesCalled; + + public TestTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Test_ShouldHaveTestOutput() + { + Assert.True(HasTestOutput); + Assert.IsAssignableFrom(TestOutput); + Assert.IsType(TestOutput); + } + + [Fact] + public void Test_ShouldInvokeDispose() + { + Assert.Equal(ExpectedStringValue, DisposeSensitiveMethod()); + Assert.False(_onDisposeManagedResourcesCalled); + Dispose(); + Assert.True(_onDisposeManagedResourcesCalled); + Assert.True(Disposed); + Assert.Throws(DisposeSensitiveMethod); + } + + public string DisposeSensitiveMethod() + { + if (Disposed) { throw new ObjectDisposedException(GetType().FullName); } + return ExpectedStringValue; + } + + protected override void OnDisposeManagedResources() + { + _onDisposeManagedResourcesCalled = true; + base.OnDisposeManagedResources(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.IO.Tests/Cuemon.IO.Tests.csproj b/test/Cuemon.IO.Tests/Cuemon.IO.Tests.csproj index 620288fd5..cbbf40002 100644 --- a/test/Cuemon.IO.Tests/Cuemon.IO.Tests.csproj +++ b/test/Cuemon.IO.Tests/Cuemon.IO.Tests.csproj @@ -1,28 +1,10 @@ - netcoreapp3.1 - - false - Cuemon.IO - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - diff --git a/test/Cuemon.IO.Tests/GlobalSuppressions.cs b/test/Cuemon.IO.Tests/GlobalSuppressions.cs deleted file mode 100644 index c42e28595..000000000 --- a/test/Cuemon.IO.Tests/GlobalSuppressions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.IO.StreamDecoratorExtensionsTest.CompressBrotliAsync_ShouldCompressAndDecompress~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.IO.StreamDecoratorExtensionsTest.CompressGZipAsync_ShouldCompressAndDecompress~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.IO.StreamDecoratorExtensionsTest.CompressGZipAsync_ShouldThrowTaskCanceledException~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.IO.StreamDecoratorExtensionsTest.ToByteArrayAsync_ShouldConvertStreamToByteArrayWithDefaultOptions~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.IO.StreamDecoratorExtensionsTest.ToEncodedStringAsync_ShouldConvertStreamToString~System.Threading.Tasks.Task")] diff --git a/test/Cuemon.IO.Tests/StreamDecoratorExtensionsTest.cs b/test/Cuemon.IO.Tests/StreamDecoratorExtensionsTest.cs index 694796fdd..0f7fe835c 100644 --- a/test/Cuemon.IO.Tests/StreamDecoratorExtensionsTest.cs +++ b/test/Cuemon.IO.Tests/StreamDecoratorExtensionsTest.cs @@ -64,6 +64,39 @@ public async Task CompressBrotliAsync_ShouldCompressAndDecompress() TestOutput.WriteLine($"Decompressed ({ByteMultipleTable.FromBytes(dos.Length)}): {dosResult.Substring(0, 50)} ..."); } + [Fact] + public async Task CompressBrotliAsync_ShouldThrowTaskCanceledException() + { + var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(5)); + var size = 1024 * 1024; + var fs = Generate.RandomString(size); + var os = await Decorator.Enclose(fs).ToStreamAsync(); + await Assert.ThrowsAsync(async () => await Decorator.Enclose(os).CompressBrotliAsync(o => o.CancellationToken = ctsShouldFail.Token)); + } + + [Fact] + public void CompressGZip_ShouldCompressAndDecompress() + { + var size = 1024 * 1024; + var fs = Generate.RandomString(size); + var os = Decorator.Enclose(fs).ToStream(); + var cos = Decorator.Enclose(os).CompressGZip(); + var dos = Decorator.Enclose(cos).DecompressGZip(); + var osResult = Decorator.Enclose(os).ToEncodedString(o => o.LeaveOpen = true); + var cosResult = Decorator.Enclose(cos).ToEncodedString(o => o.LeaveOpen = true); + var dosResult = Decorator.Enclose(dos).ToEncodedString(o => o.LeaveOpen = true); + + Assert.Equal(size, os.Length); + Assert.NotEqual(os.Length, cos.Length); + Assert.True(os.Length > cos.Length); + Assert.Equal(os.Length, dos.Length); + Assert.Equal(osResult, dosResult); + Assert.NotEqual(osResult, cosResult); + + TestOutput.WriteLine($"Original ({ByteMultipleTable.FromBytes(os.Length)}): {osResult.Substring(0, 50)} ..."); + TestOutput.WriteLine($"Compressed ({ByteMultipleTable.FromBytes(cos.Length)}): {cosResult.Substring(0, 50)} ..."); + TestOutput.WriteLine($"Decompressed ({ByteMultipleTable.FromBytes(dos.Length)}): {dosResult.Substring(0, 50)} ..."); + } [Fact] public async Task CompressGZipAsync_ShouldCompressAndDecompress() @@ -96,7 +129,7 @@ public async Task CompressGZipAsync_ShouldThrowTaskCanceledException() var size = 1024 * 1024; var fs = Generate.RandomString(size); var os = await Decorator.Enclose(fs).ToStreamAsync(); - await Assert.ThrowsAsync(async () => await Decorator.Enclose(os).CompressGZipAsync(o => o.CancellationToken = ctsShouldFail.Token)); + await Assert.ThrowsAsync(async () => await Decorator.Enclose(os).CompressGZipAsync(o => o.CancellationToken = ctsShouldFail.Token)); } [Fact] @@ -124,16 +157,16 @@ public void CompressDeflate_ShouldCompressAndDecompress() } [Fact] - public void CompressGZip_ShouldCompressAndDecompress() + public async Task CompressDeflateAsync_ShouldCompressAndDecompress() { var size = 1024 * 1024; var fs = Generate.RandomString(size); - var os = Decorator.Enclose(fs).ToStream(); - var cos = Decorator.Enclose(os).CompressGZip(); - var dos = Decorator.Enclose(cos).DecompressGZip(); - var osResult = Decorator.Enclose(os).ToEncodedString(o => o.LeaveOpen = true); - var cosResult = Decorator.Enclose(cos).ToEncodedString(o => o.LeaveOpen = true); - var dosResult = Decorator.Enclose(dos).ToEncodedString(o => o.LeaveOpen = true); + var os = await Decorator.Enclose(fs).ToStreamAsync(); + var cos = await Decorator.Enclose(os).CompressDeflateAsync(); + var dos = await Decorator.Enclose(cos).DecompressDeflateAsync(); + var osResult = await Decorator.Enclose(os).ToEncodedStringAsync(o => o.LeaveOpen = true); + var cosResult = await Decorator.Enclose(cos).ToEncodedStringAsync(o => o.LeaveOpen = true); + var dosResult = await Decorator.Enclose(dos).ToEncodedStringAsync(o => o.LeaveOpen = true); Assert.Equal(size, os.Length); Assert.NotEqual(os.Length, cos.Length); @@ -147,6 +180,16 @@ public void CompressGZip_ShouldCompressAndDecompress() TestOutput.WriteLine($"Decompressed ({ByteMultipleTable.FromBytes(dos.Length)}): {dosResult.Substring(0, 50)} ..."); } + [Fact] + public async Task CompressDeflateAsync_ShouldThrowTaskCanceledException() + { + var ctsShouldFail = new CancellationTokenSource(TimeSpan.FromMilliseconds(5)); + var size = 1024 * 1024; + var fs = Generate.RandomString(size); + var os = await Decorator.Enclose(fs).ToStreamAsync(); + await Assert.ThrowsAsync(async () => await Decorator.Enclose(os).CompressDeflateAsync(o => o.CancellationToken = ctsShouldFail.Token)); + } + [Fact] public void ToByteArray_ShouldConvertStreamToByteArrayWithDefaultOptions() { diff --git a/test/Cuemon.Net.Tests/Cuemon.Net.Tests.csproj b/test/Cuemon.Net.Tests/Cuemon.Net.Tests.csproj index cce257874..98a5f894b 100644 --- a/test/Cuemon.Net.Tests/Cuemon.Net.Tests.csproj +++ b/test/Cuemon.Net.Tests/Cuemon.Net.Tests.csproj @@ -1,29 +1,11 @@ - netcoreapp3.1 - - false - Cuemon.Net - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + \ No newline at end of file diff --git a/test/Cuemon.Net.Tests/QueryStringCollectionTest.cs b/test/Cuemon.Net.Tests/QueryStringCollectionTest.cs index dc10f2ac1..63f2b2248 100644 --- a/test/Cuemon.Net.Tests/QueryStringCollectionTest.cs +++ b/test/Cuemon.Net.Tests/QueryStringCollectionTest.cs @@ -9,7 +9,7 @@ namespace Cuemon.Net { public class QueryStringCollectionTest : Test { - public QueryStringCollectionTest(ITestOutputHelper output = null) : base(output) + public QueryStringCollectionTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Resilience.Tests/Cuemon.Resilience.Tests.csproj b/test/Cuemon.Resilience.Tests/Cuemon.Resilience.Tests.csproj index 2777644cf..7369aecc4 100644 --- a/test/Cuemon.Resilience.Tests/Cuemon.Resilience.Tests.csproj +++ b/test/Cuemon.Resilience.Tests/Cuemon.Resilience.Tests.csproj @@ -1,30 +1,12 @@ - netcoreapp3.1 - - false - Cuemon.Resilience - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + \ No newline at end of file diff --git a/test/Cuemon.Resilience.Tests/ExceptionTest.cs b/test/Cuemon.Resilience.Tests/ExceptionTest.cs index 2175e606b..1963a91cd 100644 --- a/test/Cuemon.Resilience.Tests/ExceptionTest.cs +++ b/test/Cuemon.Resilience.Tests/ExceptionTest.cs @@ -11,7 +11,7 @@ namespace Cuemon.Resilience { public class ExceptionTest : Test { - public ExceptionTest(ITestOutputHelper output = null) : base(output) + public ExceptionTest(ITestOutputHelper output) : base(output) { } diff --git a/test/Cuemon.Resilience.Tests/GlobalSuppressions.cs b/test/Cuemon.Resilience.Tests/GlobalSuppressions.cs deleted file mode 100644 index d8d0612f9..000000000 --- a/test/Cuemon.Resilience.Tests/GlobalSuppressions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithActionAsync_ShouldTriggerInvalidOperationException~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithActionAsync_ShouldTriggerLatencyException~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithActionAsync_ShouldTriggerTransientFaultException~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithActionAsync_ShouldBypassTransientFaultHandling~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithActionAsync_ShouldTriggerRetryAndSucceed~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithFuncAsync_ShouldTriggerInvalidOperationException~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithFuncAsync_ShouldTriggerLatencyException~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithFuncAsync_ShouldTriggerRetryAndSucceedAsync~System.Threading.Tasks.Task")] -[assembly: SuppressMessage("ConfigureAwait", "ConfigureAwaitEnforcer:ConfigureAwaitEnforcer", Justification = "", Scope = "member", Target = "~M:Cuemon.Resilience.TransientOperationTest.WithFuncAsync_ShouldTriggerTransientFaultException~System.Threading.Tasks.Task")] diff --git a/test/Cuemon.Resilience.Tests/TransientOperationTest.cs b/test/Cuemon.Resilience.Tests/TransientOperationTest.cs index 37325ae9a..7c0fd6250 100644 --- a/test/Cuemon.Resilience.Tests/TransientOperationTest.cs +++ b/test/Cuemon.Resilience.Tests/TransientOperationTest.cs @@ -18,12 +18,13 @@ public class TransientOperationTest : Test private const string ExpectedResult = "OK"; private const int ExpectedRetryAttempts = 2; + private static readonly TimeSpan Jitter = TimeSpan.FromMilliseconds(1000); private const int NormalRunIncrement = 1; private const int DescriptiveExceptionCauseIncrement = 1; private static readonly TimeSpan ExpectedRecoveryWaitTime = TimeSpan.FromSeconds(1); private static readonly TimeSpan ExpectedMaximumAllowedLatency = TimeSpan.FromMilliseconds(250); - public TransientOperationTest(ITestOutputHelper output = null) : base(output) + public TransientOperationTest(ITestOutputHelper output) : base(output) { TransientOperation.FaultCallback = evidence => RetryTrackerCallback(evidence, _transientFaultTracker); TransientOperationOptionsCallback = o => @@ -78,7 +79,7 @@ public void WithFunc_ShouldTriggerRetryAndSucceed() var profiler = TimeMeasure.WithFunc(() => TransientOperation.WithFunc(FuncTransientOperation.FailUntilExpectedRetryAttemptsIsReached, id, ExpectedRetryAttempts, _retryTracker, TransientOperationOptionsCallback)); Assert.Equal(ExpectedResult, profiler.Result); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } @@ -96,7 +97,7 @@ public void WithFunc_ShouldTriggerTransientFaultException() }); var tfe = _transientFaultTracker.Single(pair => pair.Key == id).Value; - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)tfe.TotalRecoveryWaitTime.TotalSeconds); Assert.Equal((int)ExpectedRecoveryWaitTime.TotalSeconds, (int)tfe.RecoveryWaitTime.TotalSeconds); @@ -136,7 +137,7 @@ public void WithFunc_ShouldTriggerInvalidOperationException() TestOutput.WriteLine(aex.ToString()); }); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } @@ -160,7 +161,7 @@ public void WithAction_ShouldTriggerRetryAndSucceed() var profiler = TimeMeasure.WithAction(() => TransientOperation.WithAction(ActionTransientOperation.FailUntilExpectedRetryAttemptsIsReached, id, ExpectedRetryAttempts, _retryTracker, TransientOperationOptionsCallback)); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } @@ -178,7 +179,7 @@ public void WithAction_ShouldTriggerTransientFaultException() }); var tfe = _transientFaultTracker.Single(pair => pair.Key == id).Value; - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)tfe.TotalRecoveryWaitTime.TotalSeconds); Assert.Equal((int)ExpectedRecoveryWaitTime.TotalSeconds, (int)tfe.RecoveryWaitTime.TotalSeconds); @@ -218,7 +219,7 @@ public void WithAction_ShouldTriggerInvalidOperationException() TestOutput.WriteLine(aex.ToString()); }); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } @@ -242,7 +243,7 @@ public async Task WithActionAsync_ShouldTriggerRetryAndSucceed() var profiler = await TimeMeasure.WithActionAsync(ct => TransientOperation.WithActionAsync(AsyncActionTransientOperation.FailUntilExpectedRetryAttemptsIsReachedAsync, id, ExpectedRetryAttempts, _retryTracker, ct, TransientOperationOptionsCallback)); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } @@ -260,7 +261,7 @@ public async Task WithActionAsync_ShouldTriggerTransientFaultException() }); var tfe = _transientFaultTracker.Single(pair => pair.Key == id).Value; - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)tfe.TotalRecoveryWaitTime.TotalSeconds); Assert.Equal((int)ExpectedRecoveryWaitTime.TotalSeconds, (int)tfe.RecoveryWaitTime.TotalSeconds); @@ -278,8 +279,13 @@ public async Task WithActionAsync_ShouldTriggerLatencyException() var profiler = await TimeMeasure.WithActionAsync(async ct => { var aex = await Assert.ThrowsAsync(() => TransientOperation.WithActionAsync(AsyncActionTransientOperation.TriggerLatencyExceptionAsync, id, _retryTracker, ct, TransientOperationOptionsCallback)); + + TestOutput.WriteLine(aex.ToString()); + Assert.IsType(aex.InnerExceptions.First()); - Assert.Equal(NormalRunIncrement + DescriptiveExceptionCauseIncrement, aex.InnerExceptions.Count); + + var low = NormalRunIncrement + DescriptiveExceptionCauseIncrement; + Assert.InRange(aex.InnerExceptions.Count, low, low + 1); // expect 2 - allow 3 in rare cases TestOutput.WriteLine(aex.ToString()); }); @@ -300,7 +306,7 @@ public async Task WithActionAsync_ShouldTriggerInvalidOperationException() TestOutput.WriteLine(aex.ToString()); }); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } @@ -313,7 +319,7 @@ public async Task WithFuncAsync_ShouldTriggerRetryAndSucceedAsync() var profiler = await TimeMeasure.WithFuncAsync(ct => TransientOperation.WithFuncAsync(AsyncFuncTransientOperation.FailUntilExpectedRetryAttemptsIsReachedAsync, id, ExpectedRetryAttempts, _retryTracker, ct, TransientOperationOptionsCallback)); Assert.Equal(ExpectedResult, profiler.Result); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } @@ -331,7 +337,7 @@ public async Task WithFuncAsync_ShouldTriggerTransientFaultException() }); var tfe = _transientFaultTracker.Single(pair => pair.Key == id).Value; - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)tfe.TotalRecoveryWaitTime.TotalSeconds); Assert.Equal((int)ExpectedRecoveryWaitTime.TotalSeconds, (int)tfe.RecoveryWaitTime.TotalSeconds); @@ -371,7 +377,9 @@ public async Task WithFuncAsync_ShouldTriggerInvalidOperationException() TestOutput.WriteLine(aex.ToString()); }); - Assert.Equal((int)TimeSpan.FromSeconds(ExpectedRetryAttempts).TotalSeconds, (int)profiler.Elapsed.TotalSeconds); + TestOutput.WriteLine($"Profiler: {profiler.Elapsed.TotalSeconds} seconds."); + + Assert.InRange(profiler.Elapsed.TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) - Jitter).TotalSeconds, (TimeSpan.FromSeconds(ExpectedRetryAttempts) + Jitter).TotalSeconds); Assert.Equal(ExpectedRetryAttempts, _retryTracker[id]); } } diff --git a/test/Cuemon.Runtime.Caching.Tests/Assets/CountdownDependency.cs b/test/Cuemon.Runtime.Caching.Tests/Assets/CountdownDependency.cs new file mode 100644 index 000000000..2fead554c --- /dev/null +++ b/test/Cuemon.Runtime.Caching.Tests/Assets/CountdownDependency.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics; +using System.Threading; +using Cuemon.Threading; + +namespace Cuemon.Runtime.Caching.Assets +{ + public class CountdownDependency : Dependency, IDisposable + { + private Timer _handler; + private TimeSpan _timer; + private Stopwatch _sw = Stopwatch.StartNew(); + + public CountdownDependency(TimeSpan timer) + { + _timer = timer; + } + + private void OnCountdown() + { + _timer -= TimeSpan.FromSeconds(1); + if (_timer < TimeSpan.Zero) + { + _timer = TimeSpan.Zero; + _handler?.Dispose(); + _handler = null; + } + } + + public override bool HasChanged => _timer == TimeSpan.Zero; + + public override void Start() + { + _handler = TimerFactory.CreateNonCapturingTimer(state => ((CountdownDependency)state).OnCountdown(), this, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)); + } + + public void Dispose() + { + _handler?.Dispose(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Runtime.Caching.Tests/Cuemon.Runtime.Caching.Tests.csproj b/test/Cuemon.Runtime.Caching.Tests/Cuemon.Runtime.Caching.Tests.csproj new file mode 100644 index 000000000..8948c9dd6 --- /dev/null +++ b/test/Cuemon.Runtime.Caching.Tests/Cuemon.Runtime.Caching.Tests.csproj @@ -0,0 +1,16 @@ + + + + Cuemon.Runtime.Caching + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs b/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs new file mode 100644 index 000000000..1a8c70801 --- /dev/null +++ b/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Cuemon.Extensions.Xunit.Hosting; +using Cuemon.Runtime.Caching.Assets; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; +using Xunit.Priority; + +namespace Cuemon.Runtime.Caching +{ + [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] + public class SlimMemoryCacheTest : HostTest + { + private readonly SlimMemoryCache _cache; + private readonly SlimMemoryCacheOptions _cacheOptions = new SlimMemoryCacheOptions(); + + private const string Sliding30Namespace = "Sliding30"; + private const string Absolute30Namespace = "Absolute30"; + private const string Dependency30Namespace = "Dependency30"; + private const string Sliding60Namespace = "Sliding60"; + private const string Absolute60Namespace = "Absolute60"; + private const string Dependency60Namespace = "Dependency60"; + + private const int NumberOfItemsToCache = 1000; + + public SlimMemoryCacheTest(HostFixture hostFixture, ITestOutputHelper output = null) : base(hostFixture, output) + { + _cache = hostFixture.ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Add_ShouldBeThreadSafeWhenAddingSameKeyInParallel() + { + var items = NumberOfItemsToCache; + var key = "cuemon"; + Parallel.For(0, items, i => + { + var nw = Guid.NewGuid(); + if (_cache.Add(key, nw, DateTime.MaxValue)) + { + Assert.Equal(nw, _cache[key]); + } + else + { + Assert.NotEqual(nw, _cache[key]); + } + }); + + Assert.Equal(1, _cache.Count()); + Assert.Equal(1, _cache.ToList().Count); + + _cache.Remove(key); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(0, _cache.ToList().Count); + } + + [Fact] + public void Add_ShouldUpdateUsingPropertyIndexer() + { + var key = "cuemon"; + var expectedPriorToVersionSix = "Cuemon .NET Standard"; + var expectedFromVersionSix = "Cuemon for .NET"; + + _cache.Add(key, expectedPriorToVersionSix, DateTime.MaxValue); + + Assert.True(_cache.Contains(key)); + Assert.Equal(expectedPriorToVersionSix, _cache[key]); + + _cache[key] = expectedFromVersionSix; + + Assert.Equal(expectedFromVersionSix, _cache[key]); + + _cache.Remove(key); + + Assert.False(_cache.Contains(key)); + } + + [Fact, Priority(1)] + public void Clear_ShouldRemoveAllCacheEntriesBothLogicalAndActual() + { + var values = Enumerable.Range(0, 10000).ToList(); + + foreach (var value in values) + { + _cache.Add(Guid.NewGuid().ToString("N"), value, DateTime.MaxValue); + } + + Assert.Equal(values.Count, _cache.Count()); + Assert.Equal(values.Count, _cache.ToList().Count); + Assert.True(values.Count == _cache.Select(pair => pair.Value.CanExpire).Count(), "values.Count == _cache.Select(pair => pair.Value.CanExpire).Count()"); + + _cache.RemoveAll(); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(0, _cache.ToList().Count); + } + + [Fact, Priority(2)] + public void Add_ShouldHandleLargeLoadWithoutCollisionUsingSlidingExpirationOfOneMinute() + { + var items = NumberOfItemsToCache; + var expires = TimeSpan.FromMinutes(1); + var keys = Generate.RangeOf(items, i => Guid.NewGuid().ToString("N")).ToList(); + var bag = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the SlimMemoryCache + Parallel.ForEach(keys, key => + { + bag.Add(_cacheOptions.KeyProvider(key, Sliding60Namespace)); + _cache.Add(key, Generate.RandomString(5), expires, Sliding60Namespace); + }); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(items, _cache.Count(Sliding60Namespace)); + Assert.Equal(items, _cache.ToList().Count); + Assert.True(bag.OrderBy(l => l).SequenceEqual(_cache.Where(pair => pair.Value.Namespace == Sliding60Namespace).Select(pair => pair.Key).OrderBy(l => l))); // insure thread safety validation + } + + [Fact, Priority(3)] + public void Add_ShouldHandleLargeLoadWithoutCollisionUsingSlidingExpirationOfThirtySecondsWithNamespaceSpecification() + { + var items = NumberOfItemsToCache; + var expires = TimeSpan.FromSeconds(30); + var keys = Generate.RangeOf(items, i => Guid.NewGuid().ToString("N")).ToList(); + var bag = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the SlimMemoryCache + Parallel.ForEach(keys, key => + { + bag.Add(_cacheOptions.KeyProvider(key, Sliding30Namespace)); + _cache.Add(key, Generate.RandomString(5), expires, Sliding30Namespace); + }); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(keys.Count, _cache.Count(Sliding30Namespace)); + Assert.Equal(items * 2, _cache.ToList().Count); + Assert.True(bag.OrderBy(l => l).SequenceEqual(_cache.Where(pair => pair.Value.Namespace == Sliding30Namespace).Select(pair => pair.Key).OrderBy(l => l))); // insure thread safety validation + } + + [Fact, Priority(4)] + public void Add_ShouldHandleLargeLoadWithoutCollisionUsingAbsoluteExpirationOfOneMinute() + { + var items = NumberOfItemsToCache; + var expires = DateTime.UtcNow.AddMinutes(1); + var keys = Generate.RangeOf(items, i => Guid.NewGuid().ToString("N")).ToList(); + var bag = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the SlimMemoryCache + Parallel.ForEach(keys, key => + { + bag.Add(_cacheOptions.KeyProvider(key, Absolute60Namespace)); + _cache.Add(key, Generate.RandomString(5), expires, Absolute60Namespace); + }); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(items, _cache.Count(Absolute60Namespace)); + Assert.Equal(items * 3, _cache.ToList().Count); + Assert.True(bag.OrderBy(l => l).SequenceEqual(_cache.Where(pair => pair.Value.Namespace == Absolute60Namespace).Select(pair => pair.Key).OrderBy(l => l))); // insure thread safety validation + } + + [Fact, Priority(5)] + public void Add_ShouldHandleLargeLoadWithoutCollisionUsingAbsoluteExpirationOfThirtySecondsWithNamespaceSpecification() + { + var items = NumberOfItemsToCache; + var expires = DateTime.UtcNow.AddSeconds(30); + var keys = Generate.RangeOf(items, i => Guid.NewGuid().ToString("N")).ToList(); + var bag = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the SlimMemoryCache + Parallel.ForEach(keys, key => + { + bag.Add(_cacheOptions.KeyProvider(key, Absolute30Namespace)); + _cache.Add(key, Generate.RandomString(5), expires, Absolute30Namespace); + }); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(keys.Count, _cache.Count(Absolute30Namespace)); + Assert.Equal(items * 4, _cache.ToList().Count); + Assert.True(bag.OrderBy(l => l).SequenceEqual(_cache.Where(pair => pair.Value.Namespace == Absolute30Namespace).Select(pair => pair.Key).OrderBy(l => l))); // insure thread safety validation + } + + [Fact, Priority(6)] + public void Add_ShouldHandleLargeLoadWithoutCollisionUsingDependencyExpirationOfOneMinute() + { + var items = NumberOfItemsToCache; + var expires = new Func(() => new CountdownDependency(TimeSpan.FromMinutes(1))); + var keys = Generate.RangeOf(items, i => Guid.NewGuid().ToString("N")).ToList(); + var bag = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the SlimMemoryCache + Parallel.ForEach(keys, key => + { + bag.Add(_cacheOptions.KeyProvider(key, Dependency60Namespace)); + _cache.Add(key, Generate.RandomString(5), expires(), Dependency60Namespace); + }); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(items, _cache.Count(Dependency60Namespace)); + Assert.Equal(items * 5, _cache.ToList().Count); + Assert.True(bag.OrderBy(l => l).SequenceEqual(_cache.Where(pair => pair.Value.Namespace == Dependency60Namespace).Select(pair => pair.Key).OrderBy(l => l))); // insure thread safety validation + } + + [Fact, Priority(7)] + public void Add_ShouldHandleLargeLoadWithoutCollisionUsingDependencyExpirationOfThirtySecondsWithNamespaceSpecification() + { + var items = NumberOfItemsToCache; + var expires = new Func(() => new CountdownDependency(TimeSpan.FromSeconds(30))); + var keys = Generate.RangeOf(items, i => Guid.NewGuid().ToString("N")).ToList(); + var bag = new ConcurrentBag(); + + // we use Parallel because we want to assure thread safety of the SlimMemoryCache + Parallel.ForEach(keys, key => + { + bag.Add(_cacheOptions.KeyProvider(key, Dependency30Namespace)); + _cache.Add(key, Generate.RandomString(5), expires(), Dependency30Namespace); + }); + + Assert.Equal(0, _cache.Count()); + Assert.Equal(items, _cache.Count(Dependency30Namespace)); + Assert.Equal(items * 6, _cache.ToList().Count); + Assert.True(bag.OrderBy(l => l).SequenceEqual(_cache.Where(pair => pair.Value.Namespace == Dependency30Namespace).Select(pair => pair.Key).OrderBy(l => l))); // insure thread safety validation + } + + [Fact, Priority(8)] + public void Add_VerifyBothLogicalAndActualCacheRemovalUponExpirationForThirtySecondsNamespaceSpecification() + { + Thread.Sleep(TimeSpan.FromSeconds(30)); + + Assert.Equal(0, _cache.Count(Dependency30Namespace)); + Assert.Equal(0, _cache.Count(Sliding30Namespace)); + Assert.Equal(0, _cache.Count(Absolute30Namespace)); + + Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Dependency30Namespace).ToList().Count); + Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Sliding30Namespace).ToList().Count); + Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Absolute30Namespace).ToList().Count); + + Thread.Sleep(TimeSpan.FromSeconds(10)); + + Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Dependency30Namespace).ToList().Count); + Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Sliding30Namespace).ToList().Count); + Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Absolute30Namespace).ToList().Count); + } + + [Fact, Priority(9)] + public void Add_VerifyBothLogicalAndActualCacheRemovalUponExpirationForSixtySecondsNamespaceSpecification() + { + Thread.Sleep(TimeSpan.FromSeconds(20)); + + Assert.Equal(0, _cache.Count(Dependency60Namespace)); + Assert.Equal(0, _cache.Count(Sliding60Namespace)); + Assert.Equal(0, _cache.Count(Absolute60Namespace)); + + Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Dependency60Namespace).ToList().Count); + Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Sliding60Namespace).ToList().Count); + Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Absolute60Namespace).ToList().Count); + + Thread.Sleep(TimeSpan.FromSeconds(10)); + + Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Dependency60Namespace).ToList().Count); + Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Sliding60Namespace).ToList().Count); + Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Absolute60Namespace).ToList().Count); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddSingleton>(o => + { + o.FirstSweep = TimeSpan.FromSeconds(35); + o.SucceedingSweep = TimeSpan.FromSeconds(5); + }); + services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Security.Cryptography.Tests/AesCryptorTest.cs b/test/Cuemon.Security.Cryptography.Tests/AesCryptorTest.cs new file mode 100644 index 000000000..116cac04e --- /dev/null +++ b/test/Cuemon.Security.Cryptography.Tests/AesCryptorTest.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Security.Cryptography +{ + public class AesCryptorTest : Test + { + private readonly byte[] _secretKey; + private readonly byte[] _iv; + + public AesCryptorTest(ITestOutputHelper output) : base(output) + { + _secretKey = AesCryptor.GenerateKey(); + _iv = AesCryptor.GenerateInitializationVector(); + } + + [Fact] + public void AesCryptor_ShouldEncryptAndDecrypt() + { + var cryptor = new AesCryptor(_secretKey, _iv); + var secretMessage = Decorator.Enclose("This is my secret message that needs encryption!").ToByteArray(); + + Assert.True(_secretKey.SequenceEqual(cryptor.Key)); + Assert.True(_iv.SequenceEqual(cryptor.InitializationVector)); + + var enc = cryptor.Encrypt(secretMessage); + TestOutput.WriteLine(Convert.ToBase64String(enc)); + + var dec = cryptor.Decrypt(enc); + TestOutput.WriteLine(Convert.ToBase64String(dec)); + + Assert.True(dec.SequenceEqual(secretMessage)); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Security.Cryptography.Tests/Cuemon.Security.Cryptography.Tests.csproj b/test/Cuemon.Security.Cryptography.Tests/Cuemon.Security.Cryptography.Tests.csproj new file mode 100644 index 000000000..8f6f64287 --- /dev/null +++ b/test/Cuemon.Security.Cryptography.Tests/Cuemon.Security.Cryptography.Tests.csproj @@ -0,0 +1,11 @@ + + + + Cuemon.Security.Cryptography + + + + + + + \ No newline at end of file diff --git a/test/Cuemon.Security.Cryptography.Tests/KeyedHashFactoryTest.cs b/test/Cuemon.Security.Cryptography.Tests/KeyedHashFactoryTest.cs new file mode 100644 index 000000000..0f6177bab --- /dev/null +++ b/test/Cuemon.Security.Cryptography.Tests/KeyedHashFactoryTest.cs @@ -0,0 +1,58 @@ +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Security.Cryptography +{ + public class KeyedHashFactoryTest : Test + { + public KeyedHashFactoryTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void CreateHmacCryptoMd5_ShouldBeValidHashResult() + { + var h = KeyedHashFactory.CreateHmacCryptoMd5(Decorator.Enclose("unittest").ToByteArray()); + Assert.Equal("d0ee1decd115feac4608976f28e19e10", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("34bdd10e5c71eb6860294d19f2db8233", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("70adffcc026ac757db66de1bbb005e04", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); + } + + [Fact] + public void CreateHmacCryptoSha1_ShouldBeValidHashResult() + { + var h = KeyedHashFactory.CreateHmacCryptoSha1(Decorator.Enclose("unittest").ToByteArray()); + Assert.Equal("0c7d9d4461c66d2d6eafef7211689abd20b28b39", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("62ea258e7e4d0522fe5e2b06e1ab600051c542c4", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("bae9d2e09c9b5f65f9a7a3c5a55748b180ee65c1", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); + } + + [Fact] + public void CreateHmacCryptoSha256_ShouldBeValidHashResult() + { + var h = KeyedHashFactory.CreateHmacCryptoSha256(Decorator.Enclose("unittest").ToByteArray()); + Assert.Equal("c06272f221bcbfc6783c059e85d7ecdb412e80f2c2b704c27885d64d984d95e3", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("1fd4ff25f71fa49435bc6996bbbaeeb9abe2f47ed3256b8a9f44b8eea49a23b5", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("38367544d02183414967e4669cd298ca6d644db6c6bfa903afae497c6ab300fa", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); + } + + [Fact] + public void CreateHmacCryptoSha384_ShouldBeValidHashResult() + { + var h = KeyedHashFactory.CreateHmacCryptoSha384(Decorator.Enclose("unittest").ToByteArray()); + Assert.Equal("87d90ce62441fd39f81d327fb4d3a9902d80a0d8312a961fdfc9b1bcf756afd77f0f0596d30a868a3cda1c49abf1a3ee", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("7a90151a628de76431d11cf01200a7213f0dc491f7677aafbcd232e512521ca1ac3771d2b6204fa1ab6b54cf085ba2d3", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("3b00f1f207819422761a22b918c69a3e16ae0d5268c01ad331311184b6a809b927b324f75d8cb8f1cee0f849382558bd", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); + } + + [Fact] + public void CreateHmacCryptoSha512_ShouldBeValidHashResult() + { + var h = KeyedHashFactory.CreateHmacCryptoSha512(Decorator.Enclose("unittest").ToByteArray()); + Assert.Equal("db0087dde1ad907c11037243bf700a9f08430899edfc19fe1fadb7d4e6846182fa0100762cf42c64828f1bfe41493275b98f5c0c1a80300656e0d97f9f1d892e", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("7b674e1138c2ee91828fcf5318d356e72a9c9e4e533234181640b0d1728f305db656bb7e57c95e101c0efbed9290454ef0c37f514c8dfb85f003c483bf0f236a", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("33520bc89652184cf5a17600a78a513db44a0b7eb1fb001525ab0e77c94afcb45b7878b259cd37896ddbbc5dff76d2cbb8f57eaf44c4f4aac77695fe8fe28992", h.ComputeHash("what-a-feeling-#-¤-%-!-cover-from-dj-bobo-128379539285784289529893278278173981247983251311").ToHexadecimalString()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Security.Cryptography.Tests/UnkeyedHashFactoryTest.cs b/test/Cuemon.Security.Cryptography.Tests/UnkeyedHashFactoryTest.cs new file mode 100644 index 000000000..75e8396f3 --- /dev/null +++ b/test/Cuemon.Security.Cryptography.Tests/UnkeyedHashFactoryTest.cs @@ -0,0 +1,76 @@ +using System; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Security.Cryptography +{ + public class UnkeyedHashFactoryTest : Test + { + public UnkeyedHashFactoryTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void CreateCryptoSha512_ShouldBeValidHashResult() + { + var h = UnkeyedHashFactory.CreateCryptoSha512(); + Assert.Equal("1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("bb96c2fc40d2d54617d6f276febe571f623a8dadf0b734855299b0e107fda32cf6b69f2da32b36445d73690b93cbd0f7bfc20e0f7f28553d2a4428f23b716e90", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("0b6cbac838dfe7f47ea1bd0df00ec282fdf45510c92161072ccfb84035390c4da743d9c3b954eaa1b0f86fc9861b23cc6c8667ab232c11c686432ebb5c8c3f27", h.ComputeHash(Guid.Empty.ToByteArray()).ToHexadecimalString()); + } + + [Fact] + public void CreateCryptoSha384_ShouldBeValidHashResult() + { + var h = UnkeyedHashFactory.CreateCryptoSha384(); + Assert.Equal("1761336e3f7cbfe51deb137f026f89e01a448e3b1fafa64039c1464ee8732f11a5341a6f41e0c202294736ed64db1a84", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("90ae531f24e48697904a4d0286f354c50a350ebb6c2b9efcb22f71c96ceaeffc11c6095e9ca0df0ec30bf685dcf2e5e5", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("7210af19145ec2a8e250a7fe8e9eeeac1301e524daab82366c36be614dc35402a289101e48cad61c45337f2f32c14fdc", h.ComputeHash(VerticalDirection.Up).ToHexadecimalString()); + } + + [Fact] + public void CreateCryptoSha256_ShouldBeValidHashResult() + { + var h = UnkeyedHashFactory.CreateCryptoSha256(); + Assert.Equal("db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); + Assert.Equal("84d89877f0d4041efb6bf91a16f0248f2fd573e6af05c19f96bedb9f882f7882", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("53ab3a50f51855beeae9721ab68656312c7f105b9b34bbfa97875dbfda72dbc6", h.ComputeHash(DateTime.UnixEpoch).ToHexadecimalString()); + Assert.Equal("1f1a24c833be74a0f4f99007aa70a51e2456e41f745a5628721ea2b8e1c07641", h.ComputeHash(213, "fdfsfsf", 9999).ToHexadecimalString()); + } + + [Fact] + public void CreateCryptoSha1_ShouldBeValidHashResult() + { + var h = UnkeyedHashFactory.CreateCryptoSha1(); + Assert.Equal("761c457bf73b14d27e9e9265c46f4b4dda11f940", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("761c457bf73b14d27e9e9265c46f4b4dda11f940", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); + Assert.Equal("87acec17cd9dcd20a716cc2cf67417b71c8a7016", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("485bad9954c874513b8ff7b6d5ea459ac65dd075", h.ComputeHash(decimal.MinValue).ToHexadecimalString()); + Assert.Equal("ec9a4348d9ffcb19403f5b90e9eefe4cedcd6ee1", h.ComputeHash(43402934324).ToHexadecimalString()); + } + + [Fact] + public void CreateCryptoMd5_LittleEndian_ShouldBeValidHashResult() + { + var h = UnkeyedHashFactory.CreateCryptoMd5(o => o.ByteOrder = Endianness.LittleEndian); + Assert.Equal("193112cee1d1a35660f02e95a28bfea2", h.ComputeHash(32131535).ToHexadecimalString()); + Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); + Assert.Equal("781e5e245d69b566979b86e28d23f2c7", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("612f309087565745ca61c53fcaf6fa7d", h.ComputeHash(212).ToHexadecimalString()); + } + + [Fact] + public void CreateCryptoMd5_BigEndian_ShouldBeValidHashResult() + { + var h = UnkeyedHashFactory.CreateCryptoMd5(o => o.ByteOrder = Endianness.BigEndian); + Assert.Equal("d52654efa276b2ab12ca067dccaf953f", h.ComputeHash(32131535).ToHexadecimalString()); + Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString()); + Assert.Equal("d174ab98d277d9f5a5611c2c9f419d9f", h.ComputeHash(Decorator.Enclose(Alphanumeric.LettersAndNumbers).ToStream()).ToHexadecimalString()); + Assert.Equal("781e5e245d69b566979b86e28d23f2c7", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString()); + Assert.Equal("fb7b8d4f62e2be708ceca1114b439d5d", h.ComputeHash(212).ToHexadecimalString()); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Threading.Tests/Cuemon.Threading.Tests.csproj b/test/Cuemon.Threading.Tests/Cuemon.Threading.Tests.csproj index 0563a73cb..7d527d707 100644 --- a/test/Cuemon.Threading.Tests/Cuemon.Threading.Tests.csproj +++ b/test/Cuemon.Threading.Tests/Cuemon.Threading.Tests.csproj @@ -1,27 +1,11 @@ - netcoreapp3.1 - - false + Cuemon.Threading - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + \ No newline at end of file diff --git a/test/Cuemon.Threading.Tests/ParallelFactoryAsyncTest.cs b/test/Cuemon.Threading.Tests/ParallelFactoryAsyncTest.cs new file mode 100644 index 000000000..7776fbb58 --- /dev/null +++ b/test/Cuemon.Threading.Tests/ParallelFactoryAsyncTest.cs @@ -0,0 +1,544 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Threading +{ + public class ParallelFactoryAsyncTest : Test + { + private readonly TimeSpan _maxAllowedTestTime = TimeSpan.FromMinutes(1); + private readonly TimeSpan _longRunningTaskWaitTime = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? TimeSpan.FromMilliseconds(1) : TimeSpan.FromMilliseconds(10); + private readonly int _extremePartitionSize = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 512 : 4096; + + public ParallelFactoryAsyncTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task ForAsync_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + await ParallelFactory.ForAsync(0, count, async (i, ct) => + { + await Task.Delay(50, ct); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = 64; + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForAsync_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + + await Assert.ThrowsAnyAsync(async () => + { + await ParallelFactory.ForAsync(0, count, async (i, ct) => + { + if (i > 450) { cts.Cancel(); } + await Task.Delay(Generate.RandomNumber(25, 75), ct); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + }); + + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public async Task ForAsync_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + await ParallelFactory.ForAsync(0, count, async (i, ct) => + { + await Task.Delay(_longRunningTaskWaitTime, ct); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForAsync_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var count = short.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + await ParallelFactory.ForAsync(0, count, async (i, ct) => + { + await Task.Delay(100, ct); + cb.Add(i); + }, o => o.PartitionSize = _extremePartitionSize); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForResultAsync_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var cb = new ConcurrentBag(); + + var result = await ParallelFactory.ForResultAsync(0, count, async (i, ct) => + { + await Task.Delay(50, ct); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = 64; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForResultAsync_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + + await Assert.ThrowsAnyAsync(async () => + { + await ParallelFactory.ForResultAsync(0, count, async (i, ct) => + { + if (i > 450) { cts.Cancel(); } + await Task.Delay(Generate.RandomNumber(25, 75), ct); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + }); + + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public async Task ForResultAsync_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var cb = new ConcurrentBag(); + + var result = await ParallelFactory.ForResultAsync(0, count, async (i, ct) => + { + await Task.Delay(_longRunningTaskWaitTime, ct); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForResultAsync_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var cb = new ConcurrentBag(); + + var result = await ParallelFactory.ForResultAsync(0, count, async (i, ct) => + { + await Task.Delay(100, ct); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = _extremePartitionSize; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForEachAsync_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + await ParallelFactory.ForEachAsync(ic, async (i, ct) => + { + await Task.Delay(50, ct); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = 64; + }); + + Assert.Equal(count, cb.Count); + Assert.True(ic.SequenceEqual(cb.OrderBy(i => i)), "ic.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForEachAsync_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + + await Assert.ThrowsAnyAsync(async () => + { + await ParallelFactory.ForEachAsync(ic, async (i, ct) => + { + if (i > 450) { cts.Cancel(); } + await Task.Delay(Generate.RandomNumber(25, 75), ct); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + }); + + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public async Task ForEachAsync_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + await ParallelFactory.ForEachAsync(ic, async (i, ct) => + { + await Task.Delay(_longRunningTaskWaitTime, ct); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(ic.SequenceEqual(cb.OrderBy(i => i)), "ic.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForEachAsync_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + await ParallelFactory.ForEachAsync(ic, async (i, ct) => + { + await Task.Delay(100, ct); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = _extremePartitionSize; + }); + + Assert.Equal(count, cb.Count); + Assert.True(ic.SequenceEqual(cb.OrderBy(i => i)), "ic.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForEachResultAsync_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + var result = await ParallelFactory.ForEachResultAsync(ic, async (i, ct) => + { + await Task.Delay(50, ct); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = 64; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForEachResultAsync_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + + await Assert.ThrowsAnyAsync(async () => + { + await ParallelFactory.ForEachResultAsync(ic, async (i, ct) => + { + if (i > 450) { cts.Cancel(); } + await Task.Delay(Generate.RandomNumber(25, 75), ct); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + }); + + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public async Task ForEachResultAsync_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + var result = await ParallelFactory.ForEachResultAsync(ic, async (i, ct) => + { + await Task.Delay(_longRunningTaskWaitTime, ct); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task ForEachResultAsync_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + var result = await ParallelFactory.ForEachResultAsync(ic, async (i, ct) => + { + await Task.Delay(100, ct); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = _extremePartitionSize; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task WhileAsync_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + await AdvancedParallelFactory.WhileAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + await Task.Delay(50, ct); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = 64; + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task WhileAsync_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + + await Assert.ThrowsAnyAsync(async () => + { + await AdvancedParallelFactory.WhileAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + if (i > 450) { cts.Cancel(); } + await Task.Delay(Generate.RandomNumber(25, 75), ct); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + }); + + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public async Task WhileAsync_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + await AdvancedParallelFactory.WhileAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + await Task.Delay(_longRunningTaskWaitTime, ct); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task WhileAsync_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + await AdvancedParallelFactory.WhileAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + await Task.Delay(100, ct); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = _extremePartitionSize; + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task WhileResultAsync_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + var result = await AdvancedParallelFactory.WhileResultAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + await Task.Delay(50, ct); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = 64; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task WhileResultAsync_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + + await Assert.ThrowsAnyAsync(async () => + { + await AdvancedParallelFactory.WhileResultAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + if (i > 450) { cts.Cancel(); } + await Task.Delay(Generate.RandomNumber(25, 75), ct); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + }); + + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public async Task WhileResultAsync_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + var result = await AdvancedParallelFactory.WhileResultAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + await Task.Delay(_longRunningTaskWaitTime, ct); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public async Task WhileResultAsync_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + var result = await AdvancedParallelFactory.WhileResultAsync(ic, () => Task.FromResult(ic.TryPeek(out _)), intProvider => intProvider.Dequeue(), async (i, ct) => + { + await Task.Delay(100, ct); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = _extremePartitionSize; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + } +} \ No newline at end of file diff --git a/test/Cuemon.Threading.Tests/ParallelFactoryTest.cs b/test/Cuemon.Threading.Tests/ParallelFactoryTest.cs new file mode 100644 index 000000000..7b804d7b7 --- /dev/null +++ b/test/Cuemon.Threading.Tests/ParallelFactoryTest.cs @@ -0,0 +1,599 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Cuemon.Extensions.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Threading +{ + public class ParallelFactoryTest : Test + { + private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + private readonly TimeSpan _maxAllowedTestTime = TimeSpan.FromMinutes(1); + private readonly TimeSpan _longRunningTaskWaitTime = IsLinux ? TimeSpan.FromMilliseconds(1) : TimeSpan.FromMilliseconds(10); + private readonly int _extremePartitionSize = IsLinux ? 128 : 4096; + + public ParallelFactoryTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void For_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + ParallelFactory.For(0, count, i => + { + Thread.Sleep(50); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void For_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + var x = 0; + var ae = Assert.Throws(() => + { + ParallelFactory.For(0, count, i => + { + Interlocked.Increment(ref x); + if (i > 450) { cts.Cancel(); } + Thread.Sleep(Generate.RandomNumber(25, 75)); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + }); + + Assert.IsAssignableFrom(ae.InnerExceptions.FirstOrDefault(ex => ex.GetType().IsAssignableFrom(typeof(TaskCanceledException)))); + + TestOutput.WriteLine(x.ToString()); + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public void For_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + ParallelFactory.For(0, count, i => + { + Thread.Sleep(_longRunningTaskWaitTime); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void For_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + ParallelFactory.For(0, count, i => + { + Thread.Sleep(1); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = MaxThreadCount; + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForResult_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var cb = new ConcurrentBag(); + + var result = ParallelFactory.ForResult(0, count, i => + { + Thread.Sleep(50); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForResult_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + var x = 0; + var ae = Assert.Throws(() => + { + ParallelFactory.ForResult(0, count, i => + { + Interlocked.Increment(ref x); + if (i > 450) { cts.Cancel(); } + Thread.Sleep(Generate.RandomNumber(25, 75)); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + }); + + Assert.IsAssignableFrom(ae.InnerExceptions.FirstOrDefault(ex => ex.GetType().IsAssignableFrom(typeof(TaskCanceledException)))); + + TestOutput.WriteLine(x.ToString()); + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public void ForResult_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var cb = new ConcurrentBag(); + + var result = ParallelFactory.ForResult(0, count, i => + { + Thread.Sleep(_longRunningTaskWaitTime); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForResult_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var cb = new ConcurrentBag(); + + var result = ParallelFactory.ForResult(0, count, i => + { + Thread.Sleep(100); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = MaxThreadCount; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForEach_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + ParallelFactory.ForEach(ic, i => + { + Thread.Sleep(50); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + + Assert.Equal(count, cb.Count); + Assert.True(ic.SequenceEqual(cb.OrderBy(i => i)), "ic.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForEach_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + var x = 0; + var ae = Assert.Throws(() => + { + ParallelFactory.ForEach(ic, i => + { + Interlocked.Increment(ref x); + if (i > 450) { cts.Cancel(); } + Thread.Sleep(Generate.RandomNumber(25, 75)); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + }); + + Assert.IsAssignableFrom(ae.InnerExceptions.FirstOrDefault(ex => ex.GetType().IsAssignableFrom(typeof(TaskCanceledException)))); + + TestOutput.WriteLine(x.ToString()); + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public void ForEach_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + ParallelFactory.ForEach(ic, i => + { + Thread.Sleep(_longRunningTaskWaitTime); + cb.Add(i); + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(ic.SequenceEqual(cb.OrderBy(i => i)), "ic.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForEach_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + ParallelFactory.ForEach(ic, i => + { + Thread.Sleep(100); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = MaxThreadCount; + }); + + Assert.Equal(count, cb.Count); + Assert.True(ic.SequenceEqual(cb.OrderBy(i => i)), "ic.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForEachResult_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + var result = ParallelFactory.ForEachResult(ic, i => + { + Thread.Sleep(50); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForEachResult_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + var x = 0; + var ae = Assert.Throws(() => + { + ParallelFactory.ForEachResult(ic, i => + { + Interlocked.Increment(ref x); + if (i > 450) { cts.Cancel(); } + Thread.Sleep(Generate.RandomNumber(25, 75)); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + }); + + Assert.IsAssignableFrom(ae.InnerExceptions.FirstOrDefault(ex => ex.GetType().IsAssignableFrom(typeof(TaskCanceledException)))); + + TestOutput.WriteLine(x.ToString()); + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public void ForEachResult_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + var result = ParallelFactory.ForEachResult(ic, i => + { + Thread.Sleep(_longRunningTaskWaitTime); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void ForEachResult_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var ic = Generate.RangeOf(count, i => i); + var cb = new ConcurrentBag(); + + var result = ParallelFactory.ForEachResult(ic, i => + { + Thread.Sleep(100); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = MaxThreadCount; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void While_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + AdvancedParallelFactory.While(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Thread.Sleep(50); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void While_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + var x = 0; + var ae = Assert.Throws(() => + { + AdvancedParallelFactory.While(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Interlocked.Increment(ref x); + if (i > 450) { cts.Cancel(); } + Thread.Sleep(Generate.RandomNumber(25, 75)); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + }); + + Assert.IsAssignableFrom(ae.InnerExceptions.FirstOrDefault(ex => ex.GetType().IsAssignableFrom(typeof(TaskCanceledException)))); + + TestOutput.WriteLine(x.ToString()); + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public void While_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var count = sbyte.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + AdvancedParallelFactory.While(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Thread.Sleep(_longRunningTaskWaitTime); + cb.Add(i); + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void While_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + AdvancedParallelFactory.While(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Thread.Sleep(100); + cb.Add(i); + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = MaxThreadCount; + }); + + Assert.Equal(count, cb.Count); + Assert.True(expected.SequenceEqual(cb.OrderBy(i => i)), "expected.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void WhileResult_ShouldRunConcurrent() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + var result = AdvancedParallelFactory.WhileResult(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Thread.Sleep(50); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void WhileResult_ShouldRunConcurrent_IgniteCancellation() + { + var count = 1000; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + var cts = new CancellationTokenSource(); + var x = 0; + var ae = Assert.Throws(() => + { + AdvancedParallelFactory.WhileResult(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Interlocked.Increment(ref x); + if (i > 450) { cts.Cancel(); } + Thread.Sleep(Generate.RandomNumber(25, 75)); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.CreationOptions = TaskCreationOptions.None; + }); + }); + + Assert.IsAssignableFrom(ae.InnerExceptions.FirstOrDefault(ex => ex.GetType().IsAssignableFrom(typeof(TaskCanceledException)))); + + TestOutput.WriteLine(x.ToString()); + TestOutput.WriteLine($"Threads processed: {cb.Count}."); + + Assert.InRange(cb.Count, 200, 500); // most threads should have executed before cancellation + } + + [Fact] + public void WhileResult_ShouldRunConcurrent_LongRunning_SystemPartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = sbyte.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + var result = AdvancedParallelFactory.WhileResult(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Thread.Sleep(_longRunningTaskWaitTime); + cb.Add(i); + return i; + }, o => o.CancellationToken = cts.Token); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + [Fact] + public void WhileResult_ShouldRunConcurrent_LongRunning_ExtremePartition() + { + var cts = new CancellationTokenSource(_maxAllowedTestTime); + var count = short.MaxValue; + var expected = Generate.RangeOf(count, i => i); + var ic = new Queue(expected); + var cb = new ConcurrentBag(); + + var result = AdvancedParallelFactory.WhileResult(ic, () => ic.TryPeek(out _), intProvider => intProvider.Dequeue(), i => + { + Thread.Sleep(100); + cb.Add(i); + return i; + }, o => + { + o.CancellationToken = cts.Token; + o.PartitionSize = MaxThreadCount; + }); + + Assert.Equal(count, cb.Count); + Assert.True(result.SequenceEqual(cb.OrderBy(i => i)), "result.SequenceEqual(cb.OrderBy(i => i))"); + } + + private static int MaxThreadCount => IsLinux ? 1024 * 8 : 1024 * 32; + } +} \ No newline at end of file diff --git a/test/Cuemon.Xml.Tests/Cuemon.Xml.Tests.csproj b/test/Cuemon.Xml.Tests/Cuemon.Xml.Tests.csproj index f393fe0bf..43e5e8025 100644 --- a/test/Cuemon.Xml.Tests/Cuemon.Xml.Tests.csproj +++ b/test/Cuemon.Xml.Tests/Cuemon.Xml.Tests.csproj @@ -1,29 +1,12 @@ - netcoreapp3.1 - - false - Cuemon.Xml - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + - + \ No newline at end of file diff --git a/test/Cuemon.Xml.Tests/Serialization/Formatters/XmlFormatterOptionsTest.cs b/test/Cuemon.Xml.Tests/Serialization/Formatters/XmlFormatterOptionsTest.cs new file mode 100644 index 000000000..20a62c88e --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/Formatters/XmlFormatterOptionsTest.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Cuemon.Extensions.Xunit; +using Cuemon.Xml.Serialization.Converters; +using Xunit; +using Xunit.Abstractions; + +namespace Cuemon.Xml.Serialization.Formatters +{ + public class XmlFormatterOptionsTest : Test + { + public XmlFormatterOptionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void DefaultConverters_ShouldHaveSameAmountOfDefaultConverters() + { + var defaultConverters = new List(); + XmlFormatterOptions.DefaultConverters(defaultConverters); + + var x = new XmlFormatterOptions(); + var y = new XmlFormatterOptions(); + var bootstrapInvocationList = XmlFormatterOptions.DefaultConverters.GetInvocationList().Length; + + Assert.Equal(6, defaultConverters.Count); + Assert.Equal(1, bootstrapInvocationList); + Assert.Equal(1, x.Settings.Converters.Count - defaultConverters.Count); + Assert.Equal(1, y.Settings.Converters.Count - defaultConverters.Count); + + Assert.Equal(x.Settings.Converters.Count, y.Settings.Converters.Count); + } + } +} \ No newline at end of file diff --git a/version.json b/version.json new file mode 100644 index 000000000..9627c5d24 --- /dev/null +++ b/version.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "6.0.0-preview.{height}", + "assemblyVersion": + { + "precision": "minor" + }, + "publicReleaseRefSpec": [ + "^refs/heads/master$", + "^refs/heads/release$", + "^refs/heads/development$" + ], + "nugetPackageVersion": { + "semVer": 2 + } +} \ No newline at end of file