diff --git a/Masa.Framework.sln b/Masa.Framework.sln index d939f86d1..aadb4b9e3 100644 --- a/Masa.Framework.sln +++ b/Masa.Framework.sln @@ -17,10 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution .gitignore = .gitignore Directory.Build.props = Directory.Build.props LICENSE.txt = LICENSE.txt + Masa.Framework.sln.DotSettings = Masa.Framework.sln.DotSettings NuGet.Config = NuGet.Config README.md = README.md README.zh-CN.md = README.zh-CN.md - Masa.Framework.sln.DotSettings = Masa.Framework.sln.DotSettings EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E747043D-81E2-4A89-8B5B-1258ED45F941}" @@ -131,8 +131,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentic EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Caching.Memory", "src\Utils\Caching\Masa.Utils.Caching.Memory\Masa.Utils.Caching.Memory.csproj", "{015C4181-A8D9-4FA5-89B9-38A37FA9D31D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Exceptions", "src\Utils\Masa.Utils.Exceptions\Masa.Utils.Exceptions.csproj", "{7B0EDB07-D3AE-4077-9A23-35CA6556E791}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Configuration.Json", "src\Utils\Configuration\Masa.Utils.Configuration.Json\Masa.Utils.Configuration.Json.csproj", "{8AB652AF-3957-42F9-8F3E-FAFE56A44BE1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Data.DataAnnotations", "src\Utils\Data\Masa.Utils.Data.DataAnnotations\Masa.Utils.Data.DataAnnotations.csproj", "{766E3633-7B82-49CC-B012-CAE6264D4628}" @@ -555,6 +553,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentic EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Service.Caller.Authentication.OpenIdConnect", "src\Contrib\Service\Caller\Authentication\Masa.Contrib.Service.Caller.Authentication.OpenIdConnect\Masa.Contrib.Service.Caller.Authentication.OpenIdConnect.csproj", "{9BB5CC86-C2E8-4859-9610-50DB263605A3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Globalization", "Globalization", "{D10FC534-0091-42B4-809F-82C1E2164ED5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Globalization", "Globalization", "{5B129E6F-8CB7-4E2E-9F3E-C59CF22C9CA9}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8D7D3D21-86DB-4FCC-8FF0-6E1587ABD22A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Service.Caller.Authentication.OpenIdConnect.Tests", "src\Contrib\Service\Caller\Authentication\Tests\Masa.Contrib.Service.Caller.Authentication.OpenIdConnect.Tests\Masa.Contrib.Service.Caller.Authentication.OpenIdConnect.Tests.csproj", "{BEA8E5A5-E7BD-4165-80CD-D1F53ED82D02}" @@ -571,31 +573,55 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.RulesEn EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Development", "Development", "{43E0F1C0-7308-45C9-83CF-E6291ACE9F0F}" EndProject -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.BuildingBlocks.Development.DaprStarter", "src\BuildingBlocks\Development\Masa.BuildingBlocks.Development.DaprStarter\Masa.BuildingBlocks.Development.DaprStarter.csproj", "{77A9CADA-35D6-419B-87B3-AACC50FFAD0F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Development.DaprStarter", "src\BuildingBlocks\Development\Masa.BuildingBlocks.Development.DaprStarter\Masa.BuildingBlocks.Development.DaprStarter.csproj", "{77A9CADA-35D6-419B-87B3-AACC50FFAD0F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.RulesEngine.MicrosoftRulesEngine", "src\Contrib\RulesEngine\Masa.Contrib.RulesEngine.MicrosoftRulesEngine\Masa.Contrib.RulesEngine.MicrosoftRulesEngine.csproj", "{4E217EC9-0616-414B-82D9-9107F9826D6E}" EndProject -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Development.DaprStarter", "src\Contrib\Development\Masa.Contrib.Development.DaprStarter\Masa.Contrib.Development.DaprStarter.csproj", "{A6D4C293-E184-42C0-B9B7-3F856BE57EC0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Development.DaprStarter", "src\Contrib\Development\Masa.Contrib.Development.DaprStarter\Masa.Contrib.Development.DaprStarter.csproj", "{A6D4C293-E184-42C0-B9B7-3F856BE57EC0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3FADF704-2581-47AC-A1F7-07091B6328A1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.RulesEngine.MicrosoftRulesEngine.Tests", "src\Contrib\RulesEngine\Tests\Masa.Contrib.RulesEngine.MicrosoftRulesEngine.Tests\Masa.Contrib.RulesEngine.MicrosoftRulesEngine.Tests.csproj", "{EEB2D542-5A2C-4E18-A0E6-72844C359DAD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Development.DaprStarter.AspNetCore", "src\Contrib\Development\Masa.Contrib.Development.DaprStarter.AspNetCore\Masa.Contrib.Development.DaprStarter.AspNetCore.csproj", "{E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Development.DaprStarter.AspNetCore", "src\Contrib\Development\Masa.Contrib.Development.DaprStarter.AspNetCore\Masa.Contrib.Development.DaprStarter.AspNetCore.csproj", "{E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dcc", "Dcc", "{5E0E61DA-06FF-4F08-9C76-0A8856C90EF5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.StackSdks.Dcc", "src\BuildingBlocks\StackSdks\Dcc\Masa.BuildingBlocks.StackSdks.Dcc\Masa.BuildingBlocks.StackSdks.Dcc.csproj", "{356C5F47-025F-4ED6-8E8A-8745E181C455}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.BuildingBlocks.StackSdks.Dcc.Contracts", "src\BuildingBlocks\StackSdks\Dcc\Masa.BuildingBlocks.StackSdks.Dcc.Contracts\Masa.BuildingBlocks.StackSdks.Dcc.Contracts.csproj", "{C2E9EDAE-2558-4899-A486-917AE7A5D24F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.StackSdks.Dcc.Contracts", "src\BuildingBlocks\StackSdks\Dcc\Masa.BuildingBlocks.StackSdks.Dcc.Contracts\Masa.BuildingBlocks.StackSdks.Dcc.Contracts.csproj", "{C2E9EDAE-2558-4899-A486-917AE7A5D24F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8CDAF37E-3DB1-4573-B5AA-376C93A8D299}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Development.DaprStarter.Tests", "src\Contrib\Development\Tests\Masa.Contrib.Development.DaprStarter.Tests\Masa.Contrib.Development.DaprStarter.Tests.csproj", "{49B51B95-3DC5-45BD-B91A-3056FF5014B5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Development.DaprStarter.Tests", "src\Contrib\Development\Tests\Masa.Contrib.Development.DaprStarter.Tests\Masa.Contrib.Development.DaprStarter.Tests.csproj", "{49B51B95-3DC5-45BD-B91A-3056FF5014B5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Development.DaprStarter.AspNetCore.Tests", "src\Contrib\Development\Tests\Masa.Contrib.Development.DaprStarter.AspNetCore.Tests\Masa.Contrib.Development.DaprStarter.AspNetCore.Tests.csproj", "{DD899DC4-A2B7-4C7B-A644-EDB56A61145C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Globalization.I18n", "src\Contrib\Globalization\Masa.Contrib.Globalization.I18n\Masa.Contrib.Globalization.I18n.csproj", "{F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D2E598D5-5E44-4275-AF03-4BAD07A0E13B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Globalization.I18n.Tests", "src\Contrib\Globalization\Tests\Masa.Contrib.Globalization.I18n.Tests\Masa.Contrib.Globalization.I18n.Tests.csproj", "{6C732EBE-22CC-4160-B427-F4558C899024}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Globalization.I18n.Dcc", "src\Contrib\Globalization\Masa.Contrib.Globalization.I18n.Dcc\Masa.Contrib.Globalization.I18n.Dcc.csproj", "{B0EFDF9E-81FF-4880-992B-8A65A2008717}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Globalization.I18n.Dcc.Tests", "src\Contrib\Globalization\Tests\Masa.Contrib.Globalization.I18n.Dcc.Tests\Masa.Contrib.Globalization.I18n.Dcc.Tests.csproj", "{91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exception", "Exception", "{84F366D3-6E9A-4101-AEC0-287CBA6984A8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exception", "Exception", "{F17FFB29-A622-4430-B2EA-E9FF20E68122}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Exceptions", "src\BuildingBlocks\Exception\Masa.BuildingBlocks.Exceptions\Masa.BuildingBlocks.Exceptions.csproj", "{145400E0-7AC3-4D7B-88D1-F17847E4FA6D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Exceptions", "src\Contrib\Exception\Masa.Contrib.Exceptions\Masa.Contrib.Exceptions.csproj", "{C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Exceptions.Tests", "src\BuildingBlocks\Exception\Masa.BuildingBlocks.Exceptions.Tests\Masa.BuildingBlocks.Exceptions.Tests.csproj", "{921DCAAC-3146-4EDB-891C-D76089E4A451}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Globalization.I18n.AspNetCore", "src\Contrib\Globalization\Masa.Contrib.Globalization.I18n.AspNetCore\Masa.Contrib.Globalization.I18n.AspNetCore.csproj", "{B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Dispatcher.Events.FluentValidation", "src\Contrib\Dispatcher\Masa.Contrib.Dispatcher.Events.FluentValidation\Masa.Contrib.Dispatcher.Events.FluentValidation.csproj", "{66B1723A-D578-418C-B21C-2C3B5CBD1180}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Development.DaprStarter.AspNetCore.Tests", "src\Contrib\Development\Tests\Masa.Contrib.Development.DaprStarter.AspNetCore.Tests\Masa.Contrib.Development.DaprStarter.AspNetCore.Tests.csproj", "{DD899DC4-A2B7-4C7B-A644-EDB56A61145C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.BuildingBlocks.Globalization.I18n", "src\BuildingBlocks\Globalization\Masa.BuildingBlocks.Globalization.I18n\Masa.BuildingBlocks.Globalization.I18n.csproj", "{6088C3D3-E17E-41BC-A21F-F222F5123DF1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -845,14 +871,6 @@ Global {015C4181-A8D9-4FA5-89B9-38A37FA9D31D}.Release|Any CPU.Build.0 = Release|Any CPU {015C4181-A8D9-4FA5-89B9-38A37FA9D31D}.Release|x64.ActiveCfg = Release|Any CPU {015C4181-A8D9-4FA5-89B9-38A37FA9D31D}.Release|x64.Build.0 = Release|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Debug|x64.ActiveCfg = Debug|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Debug|x64.Build.0 = Debug|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Release|Any CPU.Build.0 = Release|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Release|x64.ActiveCfg = Release|Any CPU - {7B0EDB07-D3AE-4077-9A23-35CA6556E791}.Release|x64.Build.0 = Release|Any CPU {8AB652AF-3957-42F9-8F3E-FAFE56A44BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8AB652AF-3957-42F9-8F3E-FAFE56A44BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {8AB652AF-3957-42F9-8F3E-FAFE56A44BE1}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -2029,6 +2047,14 @@ Global {B03C329C-70F4-442A-B420-90DDF7E31847}.Release|Any CPU.Build.0 = Release|Any CPU {B03C329C-70F4-442A-B420-90DDF7E31847}.Release|x64.ActiveCfg = Release|Any CPU {B03C329C-70F4-442A-B420-90DDF7E31847}.Release|x64.Build.0 = Release|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|x64.ActiveCfg = Debug|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|x64.Build.0 = Debug|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|Any CPU.Build.0 = Release|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|x64.ActiveCfg = Release|Any CPU + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|x64.Build.0 = Release|Any CPU {4E217EC9-0616-414B-82D9-9107F9826D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E217EC9-0616-414B-82D9-9107F9826D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E217EC9-0616-414B-82D9-9107F9826D6E}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -2037,6 +2063,14 @@ Global {4E217EC9-0616-414B-82D9-9107F9826D6E}.Release|Any CPU.Build.0 = Release|Any CPU {4E217EC9-0616-414B-82D9-9107F9826D6E}.Release|x64.ActiveCfg = Release|Any CPU {4E217EC9-0616-414B-82D9-9107F9826D6E}.Release|x64.Build.0 = Release|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|x64.Build.0 = Debug|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|Any CPU.Build.0 = Release|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|x64.ActiveCfg = Release|Any CPU + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|x64.Build.0 = Release|Any CPU {EEB2D542-5A2C-4E18-A0E6-72844C359DAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EEB2D542-5A2C-4E18-A0E6-72844C359DAD}.Debug|Any CPU.Build.0 = Debug|Any CPU {EEB2D542-5A2C-4E18-A0E6-72844C359DAD}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -2045,6 +2079,14 @@ Global {EEB2D542-5A2C-4E18-A0E6-72844C359DAD}.Release|Any CPU.Build.0 = Release|Any CPU {EEB2D542-5A2C-4E18-A0E6-72844C359DAD}.Release|x64.ActiveCfg = Release|Any CPU {EEB2D542-5A2C-4E18-A0E6-72844C359DAD}.Release|x64.Build.0 = Release|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|x64.Build.0 = Debug|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|Any CPU.Build.0 = Release|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|x64.ActiveCfg = Release|Any CPU + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|x64.Build.0 = Release|Any CPU {356C5F47-025F-4ED6-8E8A-8745E181C455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {356C5F47-025F-4ED6-8E8A-8745E181C455}.Debug|Any CPU.Build.0 = Debug|Any CPU {356C5F47-025F-4ED6-8E8A-8745E181C455}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -2061,30 +2103,6 @@ Global {C2E9EDAE-2558-4899-A486-917AE7A5D24F}.Release|Any CPU.Build.0 = Release|Any CPU {C2E9EDAE-2558-4899-A486-917AE7A5D24F}.Release|x64.ActiveCfg = Release|Any CPU {C2E9EDAE-2558-4899-A486-917AE7A5D24F}.Release|x64.Build.0 = Release|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|x64.ActiveCfg = Debug|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Debug|x64.Build.0 = Debug|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|Any CPU.Build.0 = Release|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|x64.ActiveCfg = Release|Any CPU - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F}.Release|x64.Build.0 = Release|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|x64.ActiveCfg = Debug|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Debug|x64.Build.0 = Debug|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|Any CPU.Build.0 = Release|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|x64.ActiveCfg = Release|Any CPU - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0}.Release|x64.Build.0 = Release|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|x64.ActiveCfg = Debug|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Debug|x64.Build.0 = Debug|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|Any CPU.Build.0 = Release|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|x64.ActiveCfg = Release|Any CPU - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA}.Release|x64.Build.0 = Release|Any CPU {49B51B95-3DC5-45BD-B91A-3056FF5014B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {49B51B95-3DC5-45BD-B91A-3056FF5014B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {49B51B95-3DC5-45BD-B91A-3056FF5014B5}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -2101,6 +2119,86 @@ Global {DD899DC4-A2B7-4C7B-A644-EDB56A61145C}.Release|Any CPU.Build.0 = Release|Any CPU {DD899DC4-A2B7-4C7B-A644-EDB56A61145C}.Release|x64.ActiveCfg = Release|Any CPU {DD899DC4-A2B7-4C7B-A644-EDB56A61145C}.Release|x64.Build.0 = Release|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Debug|x64.ActiveCfg = Debug|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Debug|x64.Build.0 = Debug|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Release|Any CPU.Build.0 = Release|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Release|x64.ActiveCfg = Release|Any CPU + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B}.Release|x64.Build.0 = Release|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Debug|x64.ActiveCfg = Debug|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Debug|x64.Build.0 = Debug|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Release|Any CPU.Build.0 = Release|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Release|x64.ActiveCfg = Release|Any CPU + {6C732EBE-22CC-4160-B427-F4558C899024}.Release|x64.Build.0 = Release|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Debug|x64.ActiveCfg = Debug|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Debug|x64.Build.0 = Debug|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Release|Any CPU.Build.0 = Release|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Release|x64.ActiveCfg = Release|Any CPU + {B0EFDF9E-81FF-4880-992B-8A65A2008717}.Release|x64.Build.0 = Release|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Debug|x64.ActiveCfg = Debug|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Debug|x64.Build.0 = Debug|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Release|Any CPU.Build.0 = Release|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Release|x64.ActiveCfg = Release|Any CPU + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52}.Release|x64.Build.0 = Release|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Debug|x64.ActiveCfg = Debug|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Debug|x64.Build.0 = Debug|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Release|Any CPU.Build.0 = Release|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Release|x64.ActiveCfg = Release|Any CPU + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D}.Release|x64.Build.0 = Release|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Debug|x64.ActiveCfg = Debug|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Debug|x64.Build.0 = Debug|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Release|Any CPU.Build.0 = Release|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Release|x64.ActiveCfg = Release|Any CPU + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48}.Release|x64.Build.0 = Release|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Debug|Any CPU.Build.0 = Debug|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Debug|x64.ActiveCfg = Debug|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Debug|x64.Build.0 = Debug|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Release|Any CPU.ActiveCfg = Release|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Release|Any CPU.Build.0 = Release|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Release|x64.ActiveCfg = Release|Any CPU + {921DCAAC-3146-4EDB-891C-D76089E4A451}.Release|x64.Build.0 = Release|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Debug|x64.ActiveCfg = Debug|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Debug|x64.Build.0 = Debug|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Release|Any CPU.Build.0 = Release|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Release|x64.ActiveCfg = Release|Any CPU + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4}.Release|x64.Build.0 = Release|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Debug|x64.ActiveCfg = Debug|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Debug|x64.Build.0 = Debug|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Release|Any CPU.Build.0 = Release|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Release|x64.ActiveCfg = Release|Any CPU + {66B1723A-D578-418C-B21C-2C3B5CBD1180}.Release|x64.Build.0 = Release|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Debug|x64.ActiveCfg = Debug|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Debug|x64.Build.0 = Debug|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Release|Any CPU.Build.0 = Release|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Release|x64.ActiveCfg = Release|Any CPU + {6088C3D3-E17E-41BC-A21F-F222F5123DF1}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2162,7 +2260,6 @@ Global {2D07F9AD-9951-475D-B7C7-980B23FDE9BB} = {5944A182-13B8-4DA6-AEE2-0A01E64A9648} {534346EF-1D11-48DB-9909-700CEC99FF08} = {2D07F9AD-9951-475D-B7C7-980B23FDE9BB} {015C4181-A8D9-4FA5-89B9-38A37FA9D31D} = {AF921AB1-64D1-4478-AB49-192F3EE416FC} - {7B0EDB07-D3AE-4077-9A23-35CA6556E791} = {5944A182-13B8-4DA6-AEE2-0A01E64A9648} {8AB652AF-3957-42F9-8F3E-FAFE56A44BE1} = {56917740-626B-42D5-8BB8-6895F79FB7D2} {766E3633-7B82-49CC-B012-CAE6264D4628} = {706DA866-6226-430F-AB4D-98FEE7B0DDB0} {B7003322-212E-4568-9B61-95DB67CABC3F} = {706DA866-6226-430F-AB4D-98FEE7B0DDB0} @@ -2374,26 +2471,41 @@ Global {0FA5E9F9-F2D0-46A3-8EA0-809B219B88E8} = {59BC0E0F-ECEF-4D40-81EC-0EE51CCF9070} {FC4A0C94-47F7-4C9A-A7EC-138DE8EB842D} = {68491FED-7441-4B58-989D-DF6F198FC91A} {9BB5CC86-C2E8-4859-9610-50DB263605A3} = {FC4A0C94-47F7-4C9A-A7EC-138DE8EB842D} + {D10FC534-0091-42B4-809F-82C1E2164ED5} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {5B129E6F-8CB7-4E2E-9F3E-C59CF22C9CA9} = {950DA7D0-48C1-42BA-8E8F-F72C0DCE41C4} {8D7D3D21-86DB-4FCC-8FF0-6E1587ABD22A} = {FC4A0C94-47F7-4C9A-A7EC-138DE8EB842D} {BEA8E5A5-E7BD-4165-80CD-D1F53ED82D02} = {8D7D3D21-86DB-4FCC-8FF0-6E1587ABD22A} {03F476EF-022A-4370-B1C3-FEEDE3BB4E7F} = {41769FBF-91A8-48D1-B3BB-CAE4C814E7CD} {A7081ADB-DB21-4678-8510-A5367D470743} = {950DA7D0-48C1-42BA-8E8F-F72C0DCE41C4} + {64E37EF0-0C85-4F02-A430-6D3B1DBF13DF} = {950DA7D0-48C1-42BA-8E8F-F72C0DCE41C4} {8E74F3D4-9387-4F69-9A17-C793CE2CBAA9} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} {B03C329C-70F4-442A-B420-90DDF7E31847} = {8E74F3D4-9387-4F69-9A17-C793CE2CBAA9} + {43E0F1C0-7308-45C9-83CF-E6291ACE9F0F} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {77A9CADA-35D6-419B-87B3-AACC50FFAD0F} = {43E0F1C0-7308-45C9-83CF-E6291ACE9F0F} {4E217EC9-0616-414B-82D9-9107F9826D6E} = {A7081ADB-DB21-4678-8510-A5367D470743} + {A6D4C293-E184-42C0-B9B7-3F856BE57EC0} = {64E37EF0-0C85-4F02-A430-6D3B1DBF13DF} {3FADF704-2581-47AC-A1F7-07091B6328A1} = {A7081ADB-DB21-4678-8510-A5367D470743} {EEB2D542-5A2C-4E18-A0E6-72844C359DAD} = {3FADF704-2581-47AC-A1F7-07091B6328A1} - {64E37EF0-0C85-4F02-A430-6D3B1DBF13DF} = {950DA7D0-48C1-42BA-8E8F-F72C0DCE41C4} - {43E0F1C0-7308-45C9-83CF-E6291ACE9F0F} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {77A9CADA-35D6-419B-87B3-AACC50FFAD0F} = {43E0F1C0-7308-45C9-83CF-E6291ACE9F0F} + {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA} = {64E37EF0-0C85-4F02-A430-6D3B1DBF13DF} {5E0E61DA-06FF-4F08-9C76-0A8856C90EF5} = {8A9DBB76-6618-4982-87D7-6CBD8375EB15} {356C5F47-025F-4ED6-8E8A-8745E181C455} = {5E0E61DA-06FF-4F08-9C76-0A8856C90EF5} {C2E9EDAE-2558-4899-A486-917AE7A5D24F} = {5E0E61DA-06FF-4F08-9C76-0A8856C90EF5} - {A6D4C293-E184-42C0-B9B7-3F856BE57EC0} = {64E37EF0-0C85-4F02-A430-6D3B1DBF13DF} - {E47D82C7-7A1C-4BEF-ACA8-43971FD6C6BA} = {64E37EF0-0C85-4F02-A430-6D3B1DBF13DF} {8CDAF37E-3DB1-4573-B5AA-376C93A8D299} = {64E37EF0-0C85-4F02-A430-6D3B1DBF13DF} {49B51B95-3DC5-45BD-B91A-3056FF5014B5} = {8CDAF37E-3DB1-4573-B5AA-376C93A8D299} {DD899DC4-A2B7-4C7B-A644-EDB56A61145C} = {8CDAF37E-3DB1-4573-B5AA-376C93A8D299} + {F9E3EBD9-EE96-40F7-BDC7-818B88557D6B} = {5B129E6F-8CB7-4E2E-9F3E-C59CF22C9CA9} + {D2E598D5-5E44-4275-AF03-4BAD07A0E13B} = {5B129E6F-8CB7-4E2E-9F3E-C59CF22C9CA9} + {6C732EBE-22CC-4160-B427-F4558C899024} = {D2E598D5-5E44-4275-AF03-4BAD07A0E13B} + {B0EFDF9E-81FF-4880-992B-8A65A2008717} = {5B129E6F-8CB7-4E2E-9F3E-C59CF22C9CA9} + {91FE0D47-D6DB-4D2D-8C6F-15FCE974AB52} = {D2E598D5-5E44-4275-AF03-4BAD07A0E13B} + {84F366D3-6E9A-4101-AEC0-287CBA6984A8} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {F17FFB29-A622-4430-B2EA-E9FF20E68122} = {950DA7D0-48C1-42BA-8E8F-F72C0DCE41C4} + {145400E0-7AC3-4D7B-88D1-F17847E4FA6D} = {84F366D3-6E9A-4101-AEC0-287CBA6984A8} + {C99BD51C-A9B8-44FE-BB60-6FCC43E9DA48} = {F17FFB29-A622-4430-B2EA-E9FF20E68122} + {921DCAAC-3146-4EDB-891C-D76089E4A451} = {84F366D3-6E9A-4101-AEC0-287CBA6984A8} + {B7AEC624-6A40-42E7-BDB5-4487ACE48CE4} = {5B129E6F-8CB7-4E2E-9F3E-C59CF22C9CA9} + {66B1723A-D578-418C-B21C-2C3B5CBD1180} = {5BE84027-D1B4-4264-A7EC-E84658350CA7} + {6088C3D3-E17E-41BC-A21F-F222F5123DF1} = {D10FC534-0091-42B4-809F-82C1E2164ED5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} diff --git a/Masa.Framework.sln.DotSettings b/Masa.Framework.sln.DotSettings index ef0a25952..1e0e7b13e 100644 --- a/Masa.Framework.sln.DotSettings +++ b/Masa.Framework.sln.DotSettings @@ -1,4 +1,5 @@  True True + True True \ No newline at end of file diff --git a/src/BuildingBlocks/Configuration/Masa.BuildingBlocks.Configuration/Extensions/ConfigurationExtensions.cs b/src/BuildingBlocks/Configuration/Masa.BuildingBlocks.Configuration/Extensions/ConfigurationExtensions.cs new file mode 100644 index 000000000..49e8f9c03 --- /dev/null +++ b/src/BuildingBlocks/Configuration/Masa.BuildingBlocks.Configuration/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigurationExtensions +{ + public static Dictionary ConvertToDictionary(this IConfiguration configuration) + { + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); + GetData(configuration, configuration.GetChildren(), ref data); + return data; + } + + private static void GetData( + IConfiguration configuration, + IEnumerable configurationSections, + ref Dictionary dictionary) + { + foreach (var configurationSection in configurationSections) + { + var section = configuration.GetSection(configurationSection.Path); + + var childrenSections = section.GetChildren()?.ToList() ?? new List(); + + if (!section.Exists() || !childrenSections.Any()) + { + var key = section.Path; + if (!dictionary.ContainsKey(key)) + { + dictionary.Add(key, configuration[section.Path]); + } + } + else + { + GetData(configuration, childrenSections, ref dictionary); + } + } + } +} diff --git a/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Constants/ErrorCode.cs b/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Constants/ErrorCode.cs new file mode 100644 index 000000000..86cbd151d --- /dev/null +++ b/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Constants/ErrorCode.cs @@ -0,0 +1,251 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Data.Constants; + +public static class ErrorCode +{ + public const string FRAMEWORK_PREFIX = "MF"; + + #region Type + + /// + /// Internal service error + /// + private const string INTERNAL_SERVER = $"{FRAMEWORK_PREFIX}SVR"; + + /// + /// parameter validation error + /// + private const string ARGUMENT = $"{FRAMEWORK_PREFIX}ARG"; + + #endregion + + #region Argument Verify + + /// + /// '{PropertyName}' is not a valid email address. + /// + [Description("'{0}' is not a valid email address.")] + public const string EMAIL_VALIDATOR = $"{ARGUMENT}0001"; + + /// + /// '{PropertyName}' must be greater than or equal to '{ComparisonValue}'. + /// + [Description("'{0}' must be greater than or equal to '{1}'.")] + public const string GREATER_THAN_OR_EQUAL_VALIDATOR = $"{ARGUMENT}0002"; + + /// + /// '{PropertyName}' must be greater than '{ComparisonValue}'. + /// + [Description("'{0}' must be greater than '{1}'.")] + public const string GREATER_THAN_VALIDATOR = $"{ARGUMENT}0003"; + + /// + /// '{PropertyName}' must be between {MinLength} and {MaxLength} characters. You entered {TotalLength} characters. + /// + [Description("'{0}' must be between {1} and {2} characters. You entered {3} characters.")] + public const string LENGTH_VALIDATOR = $"{ARGUMENT}0004"; + + /// + /// The length of '{PropertyName}' must be at least {MinLength} characters. You entered {TotalLength} characters. + /// + [Description("The length of '{0}' must be at least {1} characters. You entered {2} characters.")] + public const string MINIMUM_LENGTH_VALIDATOR = $"{ARGUMENT}0005"; + + /// + /// The length of '{PropertyName}' must be {MaxLength} characters or fewer. You entered {TotalLength} characters. + /// + [Description("The length of '{0}' must be {1} characters or fewer. You entered {2} characters.")] + public const string MAXIMUM_LENGTH_VALIDATOR = $"{ARGUMENT}0006"; + + /// + /// '{PropertyName}' must be less than or equal to '{ComparisonValue}'. + /// + [Description("'{0}' must be less than or equal to '{1}'.")] + public const string LESS_THAN_OR_EQUAL_VALIDATOR = $"{ARGUMENT}0007"; + + /// + /// '{PropertyName}' must be less than '{ComparisonValue}'. + /// + [Description("'{0}' must be less than '{1}'.")] + public const string LESS_THAN_VALIDATOR = $"{ARGUMENT}0008"; + + /// + /// '{PropertyName}' must not be empty. + /// + [Description("'{0}' must not be empty.")] + public const string NOT_EMPTY_VALIDATOR = $"{ARGUMENT}0009"; + + /// + /// '{PropertyName}' must not be equal to '{ComparisonValue}'. + /// + [Description("'{0}' must not be equal to '{1}'.")] + public const string NOT_EQUAL_VALIDATOR = $"{ARGUMENT}0010"; + + /// + /// '{PropertyName}' must not be empty. + /// + [Description("'{0}' must not be empty.")] + public const string NOT_NULL_VALIDATOR = $"{ARGUMENT}0011"; + + /// + /// The specified condition was not met for '{0}'. + /// + [Description("The specified condition was not met for '{0}'.")] + public const string PREDICATE_VALIDATOR = $"{ARGUMENT}0012"; + + /// + /// The specified condition was not met for '{0}'. + /// + [Description("The specified condition was not met for '{0}'.")] + public const string ASYNC_PREDICATE_VALIDATOR = $"{ARGUMENT}0013"; + + /// + /// '{PropertyName}' is not in the correct format. + /// + [Description("'{0}' is not in the correct format.")] + public const string REGULAR_EXPRESSION_VALIDATOR = $"{ARGUMENT}0014"; + + /// + /// '{PropertyName}' must be equal to '{ComparisonValue}'. + /// + [Description("'{0}' must be equal to '{1}'.")] + public const string EQUAL_VALIDATOR = $"{ARGUMENT}0015"; + + /// + /// '{PropertyName}' must be {MaxLength} characters in length. You entered {TotalLength} characters. + /// + [Description("'{0}' must be {1} characters in length. You entered {2} characters.")] + public const string EXACT_LENGTH_VALIDATOR = $"{ARGUMENT}0016"; + + /// + /// '{PropertyName}' must be between {From} and {To}. You entered {PropertyValue}. + /// + [Description("'{0}' must be between {1} and {2}. You entered {3}.")] + public const string INCLUSIVE_BETWEEN_VALIDATOR = $"{ARGUMENT}0017"; + + /// + /// '{PropertyName}' must be between {From} and {To} (exclusive). You entered {PropertyValue}. + /// + [Description("'{0}' must be between {1} and {2} (exclusive). You entered {3}.")] + public const string EXCLUSIVE_BETWEEN_VALIDATOR = $"{ARGUMENT}0018"; + + /// + /// '{PropertyName}' cannot be null and empty. + /// + [Description("'{0}' cannot be null and empty.")] + public const string NOT_NULL_AND_EMPTY_VALIDATOR = $"{ARGUMENT}0019"; + + /// + /// '{PropertyName}' must not be more than {ExpectedPrecision} digits in total, with allowance for {ExpectedScale} decimals. {Digits} digits and {ActualScale} decimals were found. + /// + [Description("'{0}' must not be more than {1} digits in total, with allowance for {2} decimals. {3} digits and {4} decimals were found.")] + public const string SCALE_PRECISION_VALIDATOR = $"{ARGUMENT}0020"; + + /// + /// '{PropertyName}' must be empty. + /// + [Description("'{0}' must be empty.")] + public const string EMPTY_VALIDATOR = $"{ARGUMENT}0021"; + + /// + /// '{PropertyName}' must be empty. + /// + [Description("'{0}' must be empty.")] + public const string NULL_VALIDATOR = $"{ARGUMENT}0022"; + + /// + /// '{0}' has a range of values which does not include '{1}'. + /// + [Description("'{0}' has a range of values which does not include '{1}'.")] + public const string ENUM_VALIDATOR = $"{ARGUMENT}0023"; + + /// + /// '{PropertyName}' must be between {MinLength} and {MaxLength} characters. + /// + [Description("'{0}' must be between {1} and {2} characters.")] + public const string LENGTH_SIMPLE = $"{ARGUMENT}0024"; + + /// + /// The length of '{PropertyName}' must be at least {MinLength} characters. + /// + [Description("The length of '{0}' must be at least {1} characters.")] + public const string MINIMUM_LENGTH_SIMPLE = $"{ARGUMENT}0025"; + + /// + /// The length of '{0}' must be {1} characters or fewer. + /// + [Description("The length of '{PropertyName}' must be {MaxLength} characters or fewer.")] + public const string MAXIMUM_LENGTH_SIMPLE = $"{ARGUMENT}0026"; + + /// + /// '{0}' must be {1} characters in length. + /// + [Description("'{PropertyName}' must be {MaxLength} characters in length.")] + public const string EXACT_LENGTH_SIMPLE = $"{ARGUMENT}0027"; + + /// + /// '{0}' must be between {1} and {2}. + /// + [Description("'{PropertyName}' must be between {From} and {To}.")] + public const string INCLUSIVE_BETWEEN_SIMPLE = $"{ARGUMENT}0028"; + + /// + /// '{PropertyName}' cannot be Null or empty collection. + /// + [Description("'{0}' cannot be Null or empty collection.")] + public const string NOT_NULL_AND_EMPTY_COLLECTION_VALIDATOR = $"{ARGUMENT}0029"; + + /// + /// '{PropertyName}' cannot be Null or whitespace. + /// + [Description("'{0}' cannot be Null or whitespace.")] + public const string NOT_NULL_AND_WHITESPACE_VALIDATOR = $"{ARGUMENT}0030"; + + /// + /// '{PropertyName}' cannot contain {Content}. + /// + [Description("'{0}' cannot contain {1}.")] + public const string NOT_CONTAIN_VALIDATOR = $"{ARGUMENT}0031"; + + /// + /// '{PropertyName}' must be greater than or equal to '{min}' and less than or equal to '{max}'. + /// + [Description("'{0}' must be greater than or equal to '{1}' and less than or equal to '{2}'.")] + public const string OUT_OF_RANGE_VALIDATOR = $"{ARGUMENT}0032"; + + #endregion + + #region Other + + /// + /// Internal service error + /// + [Description("Internal service error")] + public const string INTERNAL_SERVER_ERROR = $"{INTERNAL_SERVER}0001"; + + #endregion + + private static readonly Dictionary _errorCodeMessageDictionary = new(); + + static ErrorCode() + { + var classType = typeof(ErrorCode); + var fields = classType.GetFields(BindingFlags.Static | BindingFlags.Public); + foreach (var field in fields) + { + var errorMessage = AttributeUtils.GetDescriptionByField(field); + + _errorCodeMessageDictionary.Add(field.GetRawConstantValue()!.ToString()!, errorMessage); + } + } + + public static string? GetErrorMessage(string errorCode) + { + if (_errorCodeMessageDictionary.TryGetValue(errorCode, out string? errorMessage)) + return errorMessage; + + return null; + } +} diff --git a/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Masa.BuildingBlocks.Data.csproj b/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Masa.BuildingBlocks.Data.csproj index ff0c10660..7bcbe02ba 100644 --- a/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Masa.BuildingBlocks.Data.csproj +++ b/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Masa.BuildingBlocks.Data.csproj @@ -11,6 +11,7 @@ + diff --git a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Masa.BuildingBlocks.Development.DaprStarter.csproj b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Masa.BuildingBlocks.Development.DaprStarter.csproj index f20f98800..d09170b61 100644 --- a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Masa.BuildingBlocks.Development.DaprStarter.csproj +++ b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Masa.BuildingBlocks.Development.DaprStarter.csproj @@ -7,7 +7,8 @@ - + + diff --git a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs index 92e1b341f..387ed3720 100644 --- a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs +++ b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs @@ -28,10 +28,7 @@ public string AppIdDelimiter get => _appIdDelimiter; set { - if (value == ".") - { - throw new NotSupportedException("AppIdDelimiter is not supported as ."); - } + MasaArgumentException.ThrowIfContain(value, ".", nameof(AppIdDelimiter)); _appIdDelimiter = value; } @@ -49,10 +46,7 @@ public string? AppIdSuffix get => _appIdSuffix; set { - if (value == ".") - { - throw new NotSupportedException("AppIdSuffix is not supported as ."); - } + MasaArgumentException.ThrowIfContain(value, ".", nameof(AppIdSuffix)); _appIdSuffix = value; } @@ -75,10 +69,8 @@ public int? MaxConcurrency get => _maxConcurrency; set { - if (value is <= 0) - { - throw new NotSupportedException($"{nameof(MaxConcurrency)} must be greater than 0 ."); - } + if (value != null) + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(MaxConcurrency)); _maxConcurrency = value; } @@ -95,8 +87,8 @@ public ushort? AppPort get => _appPort; set { - if (value is <= 0) - throw new NotSupportedException($"{nameof(AppPort)} must be greater than 0 ."); + if (value != null) + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(AppPort)); _appPort = value; } @@ -140,8 +132,8 @@ public ushort? DaprGrpcPort get => _daprGrpcPort; set { - if (value is <= 0) - throw new NotSupportedException($"{nameof(DaprGrpcPort)} must be greater than 0 ."); + if (value != null) + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(DaprGrpcPort)); _daprGrpcPort = value; } @@ -158,8 +150,8 @@ public ushort? DaprHttpPort get => _daprHttpPort; set { - if (value is <= 0) - throw new NotSupportedException($"{nameof(DaprHttpPort)} must be greater than 0 ."); + if (value != null) + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(DaprHttpPort)); _daprHttpPort = value; } @@ -220,8 +212,8 @@ public ushort? ProfilePort get => _profilePort; set { - if (value is <= 0) - throw new NotSupportedException($"{nameof(ProfilePort)} must be greater than 0 ."); + if (value != null) + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(ProfilePort)); _profilePort = value; } @@ -245,8 +237,8 @@ public int? DaprMaxRequestSize get => _daprMaxRequestSize; set { - if (value is <= 0) - throw new NotSupportedException($"{nameof(DaprMaxRequestSize)} must be greater than 0 ."); + if (value != null) + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(DaprMaxRequestSize)); _daprMaxRequestSize = value; } @@ -264,8 +256,7 @@ public int HeartBeatInterval get => _heartBeatInterval; set { - if (value < 0) - throw new NotSupportedException($"{nameof(DaprMaxRequestSize)} must be greater than or equal to 0 ."); + MasaArgumentException.ThrowIfLessThanOrEqual(value, (ushort)0, nameof(HeartBeatInterval)); _heartBeatInterval = value; } diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/Masa.BuildingBlocks.Exceptions.Tests.csproj b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/Masa.BuildingBlocks.Exceptions.Tests.csproj new file mode 100644 index 000000000..da55be1b8 --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/Masa.BuildingBlocks.Exceptions.Tests.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + false + + + + + + + + + + + + + + diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/MasaArgumentExceptionTest.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/MasaArgumentExceptionTest.cs new file mode 100644 index 000000000..eb72cda6a --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/MasaArgumentExceptionTest.cs @@ -0,0 +1,26 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Exceptions.Tests; + +[TestClass] +public class MasaArgumentExceptionTest +{ + [TestMethod] + public void TestThrowIfNull() + { + object? str = null; + + try + { + MasaValidatorException.ThrowIfNull(""); + + MasaArgumentException.ThrowIfNull(str); + } + catch (MasaArgumentException ex) + { + Assert.AreEqual(Data.Constants.ErrorCode.NOT_NULL_VALIDATOR, ex.ErrorCode); + Assert.AreEqual("'{0}' must not be empty.", ex.GetErrorMessage()); + } + } +} diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/_Imports.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/_Imports.cs new file mode 100644 index 000000000..fb23b4d18 --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions.Tests/_Imports.cs @@ -0,0 +1,7 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using System.Diagnostics.CodeAnalysis; +global using System.Runtime.Serialization; +global using System.Runtime.CompilerServices; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/Masa.BuildingBlocks.Exceptions.csproj b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/Masa.BuildingBlocks.Exceptions.csproj new file mode 100644 index 000000000..63c029362 --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/Masa.BuildingBlocks.Exceptions.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaArgumentException.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaArgumentException.cs new file mode 100644 index 000000000..b4ce32027 --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaArgumentException.cs @@ -0,0 +1,206 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +[Serializable] +public class MasaArgumentException : MasaException +{ + private string? _paramName; + protected string? ParamName => _paramName; + + public MasaArgumentException( + string message, + LogLevel? logLevel = null) + : base(message, logLevel) + { + } + + public MasaArgumentException( + string message, + string paramName, + LogLevel? logLevel = null) + : base(message, logLevel) + { + _paramName = paramName; + } + + public MasaArgumentException( + string? paramName, + string errorCode, + LogLevel? logLevel = null, + params object[] parameters) + : this((Exception?)null, errorCode, logLevel, parameters) + { + _paramName = paramName; + } + + public MasaArgumentException( + Exception? innerException, + string errorCode, + LogLevel? logLevel = null, + params object[] parameters) + : base(innerException, errorCode, logLevel, parameters) + { + } + + public MasaArgumentException(string message, Exception? innerException, LogLevel? logLevel = null) + : base(message, innerException, logLevel) + { + } + + protected MasaArgumentException(SerializationInfo serializationInfo, StreamingContext context) + : base(serializationInfo, context) + { + } + + public static void ThrowIfNullOrEmptyCollection(IEnumerable? arguments, + [CallerArgumentExpression("arguments")] + string? paramName = null) + { + ThrowIf(arguments is null || !arguments.Any(), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_AND_EMPTY_COLLECTION_VALIDATOR); + } + + public static void ThrowIfNull(object? argument, [CallerArgumentExpression("argument")] string? paramName = null) + { + ThrowIf(argument is null, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_VALIDATOR); + } + + public static void ThrowIfNullOrEmpty(object? argument, [CallerArgumentExpression("argument")] string? paramName = null) + { + ThrowIf(string.IsNullOrEmpty(argument?.ToString()), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_AND_EMPTY_VALIDATOR); + } + + public static void ThrowIfNullOrWhiteSpace(object? argument, [CallerArgumentExpression("argument")] string? paramName = null) + { + ThrowIf(string.IsNullOrWhiteSpace(argument?.ToString()), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_AND_WHITESPACE_VALIDATOR); + } + + public static void ThrowIfGreaterThan(T argument, + T maxValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(maxValue) > 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.LESS_THAN_OR_EQUAL_VALIDATOR, + null, + maxValue); + } + + public static void ThrowIfGreaterThanOrEqual(T argument, + T maxValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(maxValue) >= 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.LESS_THAN_VALIDATOR, + null, + maxValue); + } + + public static void ThrowIfLessThan(T argument, + T minValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(minValue) < 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.GREATER_THAN_OR_EQUAL_VALIDATOR, + null, + minValue); + } + + public static void ThrowIfLessThanOrEqual(T argument, + T minValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(minValue) <= 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.GREATER_THAN_VALIDATOR, + null, + minValue); + } + + public static void ThrowIfOutOfRange(T argument, + T minValue, + T maxValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(minValue) < 0 || argument.CompareTo(maxValue) > 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.OUT_OF_RANGE_VALIDATOR, + null, + minValue, + maxValue); + } + + public static void ThrowIfContain(string? argument, + string parameter, + [CallerArgumentExpression("argument")] string? paramName = null) + => ThrowIfContain(argument, parameter, StringComparison.OrdinalIgnoreCase, paramName); + + public static void ThrowIfContain(string? argument, + string parameter, + StringComparison stringComparison, + [CallerArgumentExpression("argument")] string? paramName = null) + { + if (argument != null) + ThrowIf(argument.Contains(parameter, stringComparison), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_CONTAIN_VALIDATOR + ); + } + + public static void ThrowIf( + bool condition, + string? paramName, + string errorCode, + LogLevel? logLevel = null, + params object[] parameters) + { + if (condition) + Throw(paramName, errorCode, Masa.BuildingBlocks.Data.Constants.ErrorCode.GetErrorMessage(errorCode), logLevel, parameters); + } + + public static void ThrowIf( + bool condition, + string? paramName, + string errorCode, + string? errorMessage, + LogLevel? logLevel = null, + params object[] parameters) + { + if (condition) Throw(paramName, errorCode, errorMessage, logLevel, parameters); + } + + [DoesNotReturn] + private static void Throw( + string? paramName, + string errorCode, + string? errorMessage, + LogLevel? logLevel, + params object[] parameters) => + throw new MasaArgumentException(paramName, errorCode, logLevel, parameters) + { + ErrorMessage = errorMessage + }; + + protected override object[] GetParameters() + { + var parameters = new List() + { + ParamName! + }; + parameters.AddRange(Parameters); + return parameters.ToArray(); + } +} diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaException.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaException.cs new file mode 100644 index 000000000..849480604 --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaException.cs @@ -0,0 +1,141 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +[Serializable] +public class MasaException : Exception +{ + private LogLevel? _logLevel; + public LogLevel? LogLevel => _logLevel; + + private string? _errorCode; + public string? ErrorCode => _errorCode; + + /// + /// Provides error message that I18n is not used + /// + protected string? ErrorMessage { get; set; } + + private object[] _parameters; + + public object[] Parameters => _parameters; + + private bool _initialize; + + private II18n? _i18n; + + internal II18n? I18n + { + get + { + TryInitialize(); + return _i18n; + } + } + + private II18n? _frameworkI18n; + + internal II18n? FrameworkI18n + { + get + { + TryInitialize(); + return _frameworkI18n; + } + } + + private bool _supportI18n; + + internal bool SupportI18n + { + get + { + TryInitialize(); + return _supportI18n; + } + } + + private void TryInitialize() + { + if (_initialize) + return; + + Initialize(); + } + + private void Initialize() + { + _frameworkI18n = MasaApp.GetService>(); + _i18n = MasaApp.GetService>(); + _supportI18n = _frameworkI18n != null; + _initialize = true; + } + + public MasaException() + { + } + + public MasaException(string message, LogLevel? logLevel = null) + : base(message) + { + _logLevel = logLevel; + } + + public MasaException(string errorCode, LogLevel? logLevel, params object[] parameters) + : this(null, errorCode, logLevel, parameters) + { + } + + public MasaException(Exception? innerException, string errorCode, LogLevel? logLevel = null, params object[] parameters) + : base(null, innerException) + { + _errorCode = errorCode; + _parameters = parameters; + _logLevel = logLevel; + } + + public MasaException(string message, Exception? innerException, LogLevel? logLevel = null) + : base(message, innerException) + { + _logLevel = logLevel; + } + + protected MasaException(SerializationInfo serializationInfo, StreamingContext context) + : base(serializationInfo, context) + { + } + + public string GetLocalizedMessage() + { + if (string.IsNullOrWhiteSpace(ErrorCode)) + return Message; + + return GetLocalizedMessageExecuting(); + } + + protected virtual string GetLocalizedMessageExecuting() + { + if (!SupportI18n) + { + if (string.IsNullOrWhiteSpace(ErrorMessage)) + return Message; + + return string.Format(ErrorMessage, GetParameters()); + } + + if (ErrorCode!.StartsWith(Masa.BuildingBlocks.Data.Constants.ErrorCode.FRAMEWORK_PREFIX)) + { + //The current framework frame exception + return FrameworkI18n!.T(ErrorCode!, false, GetParameters()) ?? Message; + } + + return I18n!.T(ErrorCode, false, GetParameters()) ?? Message; + } + + protected virtual object[] GetParameters() => Parameters; + + public string? GetErrorMessage() => ErrorMessage; +} diff --git a/src/Utils/Masa.Utils.Exceptions/MasaHttpStatusCode.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaHttpStatusCode.cs similarity index 57% rename from src/Utils/Masa.Utils.Exceptions/MasaHttpStatusCode.cs rename to src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaHttpStatusCode.cs index 9570e1afc..273eb0d29 100644 --- a/src/Utils/Masa.Utils.Exceptions/MasaHttpStatusCode.cs +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaHttpStatusCode.cs @@ -1,9 +1,15 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.Utils.Exceptions; +// ReSharper disable once CheckNamespace + +namespace System; public enum MasaHttpStatusCode { + /// + /// form validation exception + /// + ValidatorException = 298, UserFriendlyException = 299 } diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaValidatorException.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaValidatorException.cs new file mode 100644 index 000000000..3aaed325d --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/MasaValidatorException.cs @@ -0,0 +1,227 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +[Serializable] +public class MasaValidatorException : MasaArgumentException +{ + public MasaValidatorException(string message, LogLevel? logLevel = null) + : base(message, logLevel) + { + } + + public MasaValidatorException(string? paramName, string errorCode, LogLevel? logLevel = null, params object[] parameters) + : base(paramName, errorCode, logLevel, parameters) + { + + } + + public MasaValidatorException(Exception? innerException, string errorCode, LogLevel? logLevel = null, params object[] parameters) + : base(innerException, errorCode, logLevel, parameters) + { + } + + public MasaValidatorException(params ValidationModel[] validationModels) + : base(FormatMessage(validationModels), Microsoft.Extensions.Logging.LogLevel.Error) + { + + } + + public MasaValidatorException(LogLevel? logLevel = null, params ValidationModel[] validationModels) + : base(FormatMessage(validationModels), logLevel) + { + + } + + public MasaValidatorException(string message, Exception? innerException, LogLevel? logLevel = null) + : base(message, innerException, logLevel) + { + } + + protected MasaValidatorException(SerializationInfo serializationInfo, StreamingContext context) + : base(serializationInfo, context) + { + } + + private static string FormatMessage(params ValidationModel[] models) + => FormatMessage(models.ToList()); + + private static string FormatMessage(IEnumerable models) + { + var stringBuilder = new Text.StringBuilder(); + stringBuilder.AppendLine("Validation failed: "); + foreach (var model in models) + { + stringBuilder.AppendLine($"-- {model.Name}: {model.Message} Severity: {model.Level.ToString()}"); + } + return stringBuilder.ToString(); + } + + public new static void ThrowIfNullOrEmptyCollection(IEnumerable? arguments, + [CallerArgumentExpression("arguments")] + string? paramName = null) + { + ThrowIf(arguments is null || !arguments.Any(), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_AND_EMPTY_COLLECTION_VALIDATOR); + } + + public new static void ThrowIfNull( + object? argument, + [CallerArgumentExpression("argument")] string? paramName = null) + { + ThrowIf(argument is null, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_VALIDATOR); + } + + public new static void ThrowIfNullOrEmpty( + object? argument, + [CallerArgumentExpression("argument")] string? paramName = null) + { + ThrowIf(string.IsNullOrEmpty(argument?.ToString()), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_AND_EMPTY_VALIDATOR); + } + + public new static void ThrowIfNullOrWhiteSpace( + object? argument, + [CallerArgumentExpression("argument")] string? paramName = null) + { + ThrowIf(string.IsNullOrWhiteSpace(argument?.ToString()), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_NULL_AND_WHITESPACE_VALIDATOR); + } + + public new static void ThrowIfGreaterThan(T argument, + T maxValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(maxValue) > 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.LESS_THAN_OR_EQUAL_VALIDATOR, + null, + maxValue); + } + + public new static void ThrowIfGreaterThanOrEqual(T argument, + T maxValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(maxValue) >= 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.LESS_THAN_VALIDATOR, + null, + maxValue); + } + + public new static void ThrowIfLessThan(T argument, + T minValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(minValue) < 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.GREATER_THAN_OR_EQUAL_VALIDATOR, + null, + minValue); + } + + public new static void ThrowIfLessThanOrEqual(T argument, + T minValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(minValue) <= 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.GREATER_THAN_VALIDATOR, + null, + minValue); + } + + public new static void ThrowIfOutOfRange(T argument, + T minValue, + T maxValue, + [CallerArgumentExpression("argument")] string? paramName = null) where T : IComparable + { + ThrowIf(argument.CompareTo(minValue) < 0 || argument.CompareTo(maxValue) > 0, + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.OUT_OF_RANGE_VALIDATOR, + null, + minValue, + maxValue); + } + + public new static void ThrowIfContain(string? argument, + string parameter, + [CallerArgumentExpression("argument")] string? paramName = null) + => ThrowIfContain(argument, parameter, StringComparison.OrdinalIgnoreCase, paramName); + + public new static void ThrowIfContain(string? argument, + string parameter, + StringComparison stringComparison, + [CallerArgumentExpression("argument")] string? paramName = null) + { + if (argument != null) + ThrowIf(argument.Contains(parameter, stringComparison), + paramName, + Masa.BuildingBlocks.Data.Constants.ErrorCode.NOT_CONTAIN_VALIDATOR + ); + } + + public new static void ThrowIf( + bool condition, + string? paramName, + string errorCode, + LogLevel? logLevel = null, + params object[] parameters) + { + if (condition) + Throw(paramName, errorCode, Masa.BuildingBlocks.Data.Constants.ErrorCode.GetErrorMessage(errorCode), logLevel, parameters); + } + + public new static void ThrowIf( + bool condition, + string? paramName, + string errorCode, + string? errorMessage, + LogLevel? logLevel = null, + params object[] parameters) + { + if (condition) Throw(paramName, errorCode, errorMessage, logLevel, parameters); + } + + [DoesNotReturn] + private static void Throw( + string? paramName, + string errorCode, + string? errorMessage, + LogLevel? logLevel, + params object[] parameters) + => throw new MasaValidatorException(paramName, errorCode, logLevel, parameters) + { + ErrorMessage = errorMessage + }; + + protected override string GetLocalizedMessageExecuting() + { + string message; + if (!SupportI18n) + { + message = string.IsNullOrWhiteSpace(ErrorMessage) ? Message : string.Format(ErrorMessage, GetParameters()); + } + + else if (ErrorCode!.StartsWith(Masa.BuildingBlocks.Data.Constants.ErrorCode.FRAMEWORK_PREFIX)) + { + //The current framework frame exception + message = FrameworkI18n!.T(ErrorCode!, false, GetParameters()) ?? Message; + } + else + { + message = I18n!.T(ErrorCode, false, GetParameters()) ?? Message; + } + + return FormatMessage(new ValidationModel(ParamName!, message)); + } +} diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/UserFriendlyException.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/UserFriendlyException.cs new file mode 100644 index 000000000..88c13c01f --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/UserFriendlyException.cs @@ -0,0 +1,49 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +[Serializable] +public class UserFriendlyException : MasaException +{ + public UserFriendlyException(string message) + : base(message) + { + } + + public UserFriendlyException(string message, Exception? innerException) + : base(message, innerException) + { + } + + public UserFriendlyException( + string errorCode, + params object[] parameters) + : base(errorCode, null, parameters) + { + } + + public UserFriendlyException( + string errorCode, + LogLevel? logLevel, + params object[] parameters) + : base(errorCode, logLevel, parameters) + { + } + + public UserFriendlyException( + Exception? innerException, + string errorCode, + LogLevel? logLevel = null, + params object[] parameters) + : base(innerException, errorCode, logLevel, parameters) + { + } + + protected UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context) + : base(serializationInfo, context) + { + } +} diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/ValidationLevel.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/ValidationLevel.cs new file mode 100644 index 000000000..aceee4b08 --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/ValidationLevel.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +/// +/// Form validation error level +/// +public enum ValidationLevel +{ + Warning = 1, + Error = 2 +} diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/ValidationModel.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/ValidationModel.cs new file mode 100644 index 000000000..7beb06e4f --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/ValidationModel.cs @@ -0,0 +1,27 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +public class ValidationModel +{ + public string Name { get; set; } + + public string Message { get; set; } + + public ValidationLevel Level { get; set; } + + public ValidationModel(ValidationLevel level = ValidationLevel.Error) + { + Level = level; + } + + public ValidationModel(string name, string message, ValidationLevel level = ValidationLevel.Error) + : this(level) + { + Name = name; + Message = message; + } +} diff --git a/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/_Imports.cs b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/_Imports.cs new file mode 100644 index 000000000..fc7a5860b --- /dev/null +++ b/src/BuildingBlocks/Exception/Masa.BuildingBlocks.Exceptions/_Imports.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.BuildingBlocks.Data; +global using Masa.BuildingBlocks.Globalization.I18n; +global using Microsoft.Extensions.Logging; +global using System.Diagnostics.CodeAnalysis; +global using System.Runtime.CompilerServices; +global using System.Runtime.Serialization; diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Constant.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Constant.cs new file mode 100644 index 000000000..6d3df15d9 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Constant.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public static class Constant +{ + public const string DEFAULT_LOCAL_SECTION = "I18n"; +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/CultureModel.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/CultureModel.cs new file mode 100644 index 000000000..b2f22cf90 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/CultureModel.cs @@ -0,0 +1,20 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class CultureModel +{ + public string Culture { get; set; } + + public string? DisplayName { get; set; } + + public string Icon { get; set; } + + public CultureModel(string culture, string? displayName = null, string? icon = null) + { + Culture = culture; + DisplayName = displayName; + Icon = icon ?? string.Empty; + } +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/CultureSettings.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/CultureSettings.cs new file mode 100644 index 000000000..9e5627bea --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/CultureSettings.cs @@ -0,0 +1,21 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class CultureSettings +{ + public string? ResourcesDirectory { get; set; } + + public string? SupportCultureName { get; set; } + + public List SupportedCultures { get; set; } = new(); + + public void AddCulture(string culture, string displayName, string? icon = null) + => AddCulture(new CultureModel(culture, displayName, icon)); + + public void AddCulture(CultureModel cultureModel) + { + SupportedCultures.Add(cultureModel); + } +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/DefaultLanguageProvider.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/DefaultLanguageProvider.cs new file mode 100644 index 000000000..03e41063a --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/DefaultLanguageProvider.cs @@ -0,0 +1,50 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class DefaultLanguageProvider : ILanguageProvider +{ + private readonly II18n _i18n; + private readonly IOptions _options; + + private static readonly Dictionary _languages = new() + { + { "en-US", "English (United States)" }, + { "zh-CN", "中文 (简体)" } + }; + + public DefaultLanguageProvider(II18n i18n, IOptions options) + { + _i18n = i18n; + _options = options; + } + + public IReadOnlyList GetLanguages() + { + var list = new List(); + string cultureName = _i18n.GetCultureInfo().Name; + _options.Value.SupportedCultures.ForEach(culture => + { + string key = culture.Culture.Equals(cultureName, StringComparison.OrdinalIgnoreCase) ? + nameof(LanguageInfo.UIDisplayName) : + $"{culture.Culture}\\.{nameof(LanguageInfo.UIDisplayName)}"; + var uiDisplayName = _i18n.T(key); + var languageInfo = new LanguageInfo(culture.Culture, culture.DisplayName ?? GetDisplayName(cultureName), culture.Icon) + { + UIDisplayName = uiDisplayName + }; + + list.Add(languageInfo); + }); + return list; + } + + private static string GetDisplayName(string cultureName) + { + if (_languages.TryGetValue(cultureName, out string? displayName)) + return displayName; + + return string.Empty; + } +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18n.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18n.cs new file mode 100644 index 000000000..91f5d16ad --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18n.cs @@ -0,0 +1,85 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public static class I18n +{ + private static readonly IServiceProvider _serviceProvider = MasaApp.RootServiceProvider; + private static readonly II18n _i18n = InitI18n(); + private static readonly ILanguageProvider _languageProvider = InitLanguage(); + + static II18n InitI18n() => _serviceProvider.GetRequiredService(); + + static ILanguageProvider InitLanguage() => _serviceProvider.GetRequiredService(); + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// + public static string T(string name) => _i18n.T(name); + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// Return Key when key does not exist, default: true + /// + public static string? T(string name, bool returnKey) => _i18n.T(name, returnKey); + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The name of the string resource. + /// The values to format the string with. + public static string T(string name, params object[] arguments) => _i18n.T(name, arguments); + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The name of the string resource. + /// Return Key when key does not exist, default: true + /// The values to format the string with. + public static string? T(string name, bool returnKey, params object[] arguments) => _i18n.T(name, returnKey, arguments); + + public static CultureInfo GetCultureInfo() => _i18n.GetCultureInfo(); + + /// + /// Set the CultureName + /// Data used to define "regional options", standards, formats, etc + /// + /// A predefined name, of an existing , or Windows-only culture name. is not case-sensitive. + /// A Boolean that denotes whether to use the user-selected culture settings () or the default culture settings (). + public static void SetCulture(string cultureName, bool useUserOverride = true) => _i18n.SetCulture(cultureName, useUserOverride); + + /// + /// Set the CultureName + /// Data used to define "regional options", standards, formats, etc + /// + /// + public static void SetCulture(CultureInfo culture) => _i18n.SetCulture(culture); + + /// + /// get interface language + /// + /// + public static CultureInfo GetUiCultureInfo() => _i18n.GetUiCultureInfo(); + + /// + /// Set the CultureName for the current request + /// Used to set the interface language + /// + /// A predefined name, of an existing , or Windows-only culture name. is not case-sensitive. + /// A Boolean that denotes whether to use the user-selected culture settings () or the default culture settings (). + public static void SetUiCulture(string cultureName, bool useUserOverride = true) => _i18n.SetUiCulture(cultureName, useUserOverride); + + /// + /// Set the CultureName for the current request + /// Used to set the interface language + /// + /// + public static void SetUiCulture(CultureInfo culture) => _i18n.SetUiCulture(culture); + + public static IReadOnlyList GetLanguages() => _languageProvider.GetLanguages(); +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nOfT.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nOfT.cs new file mode 100644 index 000000000..67dcb15b8 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nOfT.cs @@ -0,0 +1,102 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class I18n : II18n +{ + private readonly I18nResource? _resource; + private readonly List _baseResources; + + public string this[string name] => T(name); + + public string? this[string name, bool returnKey] => T(name, returnKey); + + public string this[string name, params object[] arguments] => T(name, arguments); + + public string? this[string name, bool returnKey, params object[] arguments] => T(name, returnKey, arguments); + + public I18n() + { + _resource = I18nResourceResourceConfiguration.Resources.GetOrNull(); + + _baseResources = _resource?.BaseResourceTypes + .Select(resourceType => I18nResourceResourceConfiguration.Resources.GetOrNull(resourceType)) + .ToList() ?? new List(); + } + + public string T(string name) => T(name, true)!; + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// Return Key when key does not exist, default: true + /// + public string? T(string name, bool returnKey) => Core(name, returnKey, out _); + + public string T(string name, params object[] arguments) + => T(name, true, arguments)!; + + public string? T(string name, bool returnKey, params object[] arguments) + { + ArgumentNullException.ThrowIfNull(name); + + var value = Core(name, returnKey, out bool isExist); + + if (isExist) + return string.Format(GetCultureInfo(), value!, arguments); + + return returnKey ? name : null; + } + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// Return Key when key does not exist, default: true + /// does it exist + /// + public string? Core(string name, bool returnKey, out bool isExist) + { + isExist = true; + var value = GetOrNull(name); + if (value == null) + { + foreach (var resource in _baseResources) + { + value = GetOrNull(resource, name); + if (value != null) + return value; + } + isExist = false; + return returnKey ? name : null; + } + return value; + } + + public string? GetOrNull(string name) => GetOrNull(_resource, name); + + public string? GetOrNull(I18nResource? i18nResource, string name) + { + if (i18nResource == null) + return null; + + var resourceContributor = i18nResource.GetResourceContributor(GetUiCultureInfo()); + return resourceContributor?.GetOrNull(name); + } + + public CultureInfo GetCultureInfo() => CultureInfo.CurrentCulture; + + public void SetCulture(string cultureName, bool useUserOverride = true) + => SetCulture(new CultureInfo(cultureName, useUserOverride)); + + public void SetCulture(CultureInfo culture) => CultureInfo.CurrentCulture = culture; + + public CultureInfo GetUiCultureInfo() => CultureInfo.CurrentUICulture; + + public void SetUiCulture(string cultureName, bool useUserOverride = true) + => SetUiCulture(new CultureInfo(cultureName, useUserOverride)); + + public void SetUiCulture(CultureInfo culture) => CultureInfo.CurrentUICulture = culture; +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResource.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResource.cs new file mode 100644 index 000000000..233a97e0e --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResource.cs @@ -0,0 +1,52 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class I18nResource +{ + private readonly Dictionary _dictionary; + + public Type ResourceType { get; } + + public List BaseResourceTypes { get; private set; } + + public IEnumerable Assemblies { get; set; } = new List(); + + public I18nResource(Type resourceType, IEnumerable baseResourceTypes) + { + _dictionary = new(StringComparer.OrdinalIgnoreCase); + ResourceType = resourceType; + BaseResourceTypes = baseResourceTypes.ToList(); + } + + public void AddContributor(string cultureName, II18nResourceContributor localizationResourceContributor) + { + if (_dictionary.ContainsKey(cultureName)) + throw new ArgumentException($"The {cultureName} already exists with {ResourceType.FullName}"); + + _dictionary.Add(localizationResourceContributor.CultureName, localizationResourceContributor); + } + + public II18nResourceContributor? GetResourceContributor(CultureInfo? cultureInfo = null) + { + var cultureName = cultureInfo?.Name ?? CultureInfo.CurrentUICulture.Name; + + if (_dictionary.ContainsKey(cultureName)) + return _dictionary[cultureName]; + + return null; + } + + public void TryAddBaseResourceTypes() where TBaseResourceType : class + => TryAddBaseResourceTypes(typeof(TBaseResourceType)); + + public void TryAddBaseResourceTypes(params Type[] resourceTypes) + { + foreach (var type in resourceTypes) + { + if (!BaseResourceTypes.Contains(type)) + BaseResourceTypes.Add(type); + } + } +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResourceDictionary.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResourceDictionary.cs new file mode 100644 index 000000000..7d463085e --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResourceDictionary.cs @@ -0,0 +1,72 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +[Serializable] +public class I18nResourceDictionary : Dictionary +{ + public I18nResourceDictionary() + { + } + + protected I18nResourceDictionary(SerializationInfo info, StreamingContext context) : base(info, context){} + + public I18nResource Add(params Type[] baseResourceTypes) + { + return Add(typeof(TResource), baseResourceTypes); + } + + public I18nResource Add(Type resourceType, params Type[] baseResourceTypes) + { + List types = new List(baseResourceTypes); + + var customAttributes = resourceType.GetCustomAttributes(typeof(InheritResourceAttribute), true).FirstOrDefault(); + if (customAttributes is InheritResourceAttribute inheritResourceAttribute) + { + types.AddRange(inheritResourceAttribute.ResourceTypes); + } + + return this[resourceType] = new I18nResource(resourceType, types.Distinct()); + } + + public bool TryAdd(Action action, params Type[] baseResourceTypes) + { + return TryAdd(typeof(TResource), action, baseResourceTypes); + } + + public bool TryAdd(Type resourceType, Action action, params Type[] baseResourceTypes) + { + if (this.ContainsKey(resourceType)) + return false; + + var i18nResource = Add(resourceType, baseResourceTypes); + action.Invoke(i18nResource); + return true; + } + + public Task TryAddAsync(Func func, params Type[] baseResourceTypes) + { + return TryAddAsync(typeof(TResource), func, baseResourceTypes); + } + + public async Task TryAddAsync(Type resourceType, Func func, params Type[] baseResourceTypes) + { + if (this.ContainsKey(resourceType)) + return false; + + var i18nResource = Add(resourceType, baseResourceTypes); + await func.Invoke(i18nResource); + return true; + } + + public I18nResource? GetOrNull(Type resourceType) + { + if (!ContainsKey(resourceType)) + return null; + + return this[resourceType]; + } + + public I18nResource? GetOrNull() => GetOrNull(typeof(TResource)); +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResourceResourceConfiguration.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResourceResourceConfiguration.cs new file mode 100644 index 000000000..12caaae7a --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/I18nResourceResourceConfiguration.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public static class I18nResourceResourceConfiguration +{ + public static readonly string BaseDirectory = AppDomain.CurrentDomain.BaseDirectory; + + public static I18nResourceDictionary Resources { get; set; } = new(); +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18n.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18n.cs new file mode 100644 index 000000000..2a81c19d7 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18n.cs @@ -0,0 +1,100 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public interface II18n +{ + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// + string this[string name] { get; } + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// Return Key when key does not exist, default: true + /// + string? this[string name, bool returnKey] { get; } + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The name of the string resource. + /// The values to format the string with. + /// + string this[string name, params object[] arguments] { get; } + + string? this[string name, bool returnKey, params object[] arguments] => T(name, returnKey, arguments); + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// + string T(string name); + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// Return Key when key does not exist, default: true + /// + string? T(string name, bool returnKey); + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The name of the string resource. + /// The values to format the string with. + string T(string name, params object[] arguments); + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The name of the string resource. + /// Return Key when key does not exist, default: true + /// The values to format the string with. + string? T(string name, bool returnKey, params object[] arguments); + + CultureInfo GetCultureInfo(); + + /// + /// Set the CultureName + /// Data used to define "regional options", standards, formats, etc + /// + /// A predefined name, of an existing , or Windows-only culture name. is not case-sensitive. + /// A Boolean that denotes whether to use the user-selected culture settings () or the default culture settings (). + void SetCulture(string cultureName, bool useUserOverride = true); + + /// + /// Set the CultureName + /// Data used to define "regional options", standards, formats, etc + /// + /// + void SetCulture(CultureInfo culture); + + /// + /// get interface language + /// + /// + CultureInfo GetUiCultureInfo(); + + /// + /// Set the CultureName for the current request + /// Used to set the interface language + /// + /// A predefined name, of an existing , or Windows-only culture name. is not case-sensitive. + /// A Boolean that denotes whether to use the user-selected culture settings () or the default culture settings (). + void SetUiCulture(string cultureName, bool useUserOverride = true); + + /// + /// Set the CultureName for the current request + /// Used to set the interface language + /// + /// + void SetUiCulture(CultureInfo culture); +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18nOfT.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18nOfT.cs new file mode 100644 index 000000000..66511e8e5 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18nOfT.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public interface II18n : II18n +{ + +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18nResourceContributor.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18nResourceContributor.cs new file mode 100644 index 000000000..317b89537 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/II18nResourceContributor.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public interface II18nResourceContributor +{ + string CultureName { get; } + + string? GetOrNull(string name); +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/ILanguageProvider.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/ILanguageProvider.cs new file mode 100644 index 000000000..b50f27770 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/ILanguageProvider.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public interface ILanguageProvider +{ + IReadOnlyList GetLanguages(); +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/InheritResourceAttribute.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/InheritResourceAttribute.cs new file mode 100644 index 000000000..930a490c0 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/InheritResourceAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +[AttributeUsage(AttributeTargets.Class)] +public class InheritResourceAttribute : Attribute +{ + public Type[] ResourceTypes { get; } + + public InheritResourceAttribute(params Type[] resourceTypes) + { + ResourceTypes = resourceTypes; + } +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/LanguageInfo.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/LanguageInfo.cs new file mode 100644 index 000000000..260264ebb --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/LanguageInfo.cs @@ -0,0 +1,26 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class LanguageInfo +{ + public string Culture { get; set; } + + public string DisplayName { get; set; } + + public string Icon { get; set; } + + // ReSharper disable once InconsistentNaming + /// + /// Content displayed for the current language + /// + public string? UIDisplayName { get; set; } + + public LanguageInfo(string culture, string displayName, string? icon = null) + { + Culture = culture; + DisplayName = displayName; + Icon = icon ?? string.Empty; + } +} diff --git a/src/Utils/Masa.Utils.Exceptions/Masa.Utils.Exceptions.csproj b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Masa.BuildingBlocks.Globalization.I18n.csproj similarity index 66% rename from src/Utils/Masa.Utils.Exceptions/Masa.Utils.Exceptions.csproj rename to src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Masa.BuildingBlocks.Globalization.I18n.csproj index b73295abb..b3d41f5ba 100644 --- a/src/Utils/Masa.Utils.Exceptions/Masa.Utils.Exceptions.csproj +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Masa.BuildingBlocks.Globalization.I18n.csproj @@ -7,7 +7,7 @@ - + - \ No newline at end of file + diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Options/I18nOptions.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Options/I18nOptions.cs new file mode 100644 index 000000000..cd96c0e18 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Options/I18nOptions.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class I18nOptions +{ + public IServiceCollection Services { get; } + + public List SupportedCultures { get; } + + public I18nOptions(IServiceCollection services, List supportedCultures) + { + Services = services; + SupportedCultures = supportedCultures; + } +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Options/MasaI18nOptions.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Options/MasaI18nOptions.cs new file mode 100644 index 000000000..133931742 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Options/MasaI18nOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class MasaI18nOptions +{ + public I18nResourceDictionary Resources { get; } + + public MasaI18nOptions() + { + Resources = new(); + } +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/DefaultResource.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/DefaultResource.cs new file mode 100644 index 000000000..d3b5b7eac --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/DefaultResource.cs @@ -0,0 +1,12 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +[InheritResource(typeof(MasaFrameworkResource))] +public class DefaultResource +{ + +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaFrameworkResource.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaFrameworkResource.cs new file mode 100644 index 000000000..42bd4cf56 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaFrameworkResource.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +/// +/// Framework Multilingual Resources +/// +[InheritResource(typeof(MasaParameterValidationResource), typeof(MasaLanguageResource))] +public class MasaFrameworkResource +{ + +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaLanguageResource.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaLanguageResource.cs new file mode 100644 index 000000000..142aaba93 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaLanguageResource.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public class MasaLanguageResource +{ + +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaParameterValidationResource.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaParameterValidationResource.cs new file mode 100644 index 000000000..64498f44f --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/Resources/MasaParameterValidationResource.cs @@ -0,0 +1,14 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +/// +/// Framework Multilingual Resources +/// +public class MasaParameterValidationResource +{ + +} diff --git a/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/_Imports.cs b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/_Imports.cs new file mode 100644 index 000000000..b1d03ba60 --- /dev/null +++ b/src/BuildingBlocks/Globalization/Masa.BuildingBlocks.Globalization.I18n/_Imports.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.BuildingBlocks.Data; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using System.Globalization; +global using System.Reflection; +global using System.Runtime.Serialization; diff --git a/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/AutoCompleteDocument.cs b/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/AutoCompleteDocument.cs index 0a5e5f34b..863999d03 100644 --- a/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/AutoCompleteDocument.cs +++ b/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/AutoCompleteDocument.cs @@ -45,14 +45,17 @@ public string Id get { if (string.IsNullOrEmpty(_id)) - return Value?.ToString() ?? throw new ArgumentException($"{nameof(Id)} cannot be empty", nameof(Id)); + { + var val = Value.ToString(); + MasaArgumentException.ThrowIfNullOrEmpty(val, nameof(Id)); + return val!; + } return _id; } init { - if (string.IsNullOrEmpty(value)) - throw new ArgumentException($"{nameof(Id)} cannot be empty", nameof(Id)); + MasaArgumentException.ThrowIfNullOrEmpty(value, nameof(Id)); _id = value; } diff --git a/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Masa.BuildingBlocks.SearchEngine.AutoComplete.csproj b/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Masa.BuildingBlocks.SearchEngine.AutoComplete.csproj index ca62071d4..93622815f 100644 --- a/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Masa.BuildingBlocks.SearchEngine.AutoComplete.csproj +++ b/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Masa.BuildingBlocks.SearchEngine.AutoComplete.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Options/AutoCompleteOptions.cs b/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Options/AutoCompleteOptions.cs index 98e26886c..a0794c1f3 100644 --- a/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Options/AutoCompleteOptions.cs +++ b/src/BuildingBlocks/SearchEngine/Masa.BuildingBlocks.SearchEngine.AutoComplete/Options/AutoCompleteOptions.cs @@ -14,8 +14,7 @@ public int Page get => _page; set { - if (value <= 0) - throw new ArgumentException($"{nameof(Page)} must be greater than 0", nameof(Page)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(Page)); _page = value; } @@ -28,8 +27,7 @@ public int PageSize get => _pageSize; set { - if (value <= 0) - throw new ArgumentException($"{nameof(PageSize)} must be greater than 0", nameof(PageSize)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(PageSize)); _pageSize = value; } diff --git a/src/BuildingBlocks/Service/Masa.BuildingBlocks.Service.Caller/Masa.BuildingBlocks.Service.Caller.csproj b/src/BuildingBlocks/Service/Masa.BuildingBlocks.Service.Caller/Masa.BuildingBlocks.Service.Caller.csproj index 2bcff12c4..06ac63c9e 100644 --- a/src/BuildingBlocks/Service/Masa.BuildingBlocks.Service.Caller/Masa.BuildingBlocks.Service.Caller.csproj +++ b/src/BuildingBlocks/Service/Masa.BuildingBlocks.Service.Caller/Masa.BuildingBlocks.Service.Caller.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Contrib/Authentication/Identity/Masa.Contrib.Authentication.Identity.BlazorServer/Masa.Contrib.Authentication.Identity.BlazorServer.csproj b/src/Contrib/Authentication/Identity/Masa.Contrib.Authentication.Identity.BlazorServer/Masa.Contrib.Authentication.Identity.BlazorServer.csproj index de960bc75..ecf2a4d90 100644 --- a/src/Contrib/Authentication/Identity/Masa.Contrib.Authentication.Identity.BlazorServer/Masa.Contrib.Authentication.Identity.BlazorServer.csproj +++ b/src/Contrib/Authentication/Identity/Masa.Contrib.Authentication.Identity.BlazorServer/Masa.Contrib.Authentication.Identity.BlazorServer.csproj @@ -8,9 +8,9 @@ - + - + diff --git a/src/Contrib/Authentication/OpenIdConnect/Masa.Contrib.Authentication.OpenIdConnect.EFCore/Masa.Contrib.Authentication.OpenIdConnect.EFCore.csproj b/src/Contrib/Authentication/OpenIdConnect/Masa.Contrib.Authentication.OpenIdConnect.EFCore/Masa.Contrib.Authentication.OpenIdConnect.EFCore.csproj index fd6dac2ce..c00d4e4ae 100644 --- a/src/Contrib/Authentication/OpenIdConnect/Masa.Contrib.Authentication.OpenIdConnect.EFCore/Masa.Contrib.Authentication.OpenIdConnect.EFCore.csproj +++ b/src/Contrib/Authentication/OpenIdConnect/Masa.Contrib.Authentication.OpenIdConnect.EFCore/Masa.Contrib.Authentication.OpenIdConnect.EFCore.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/Masa.Contrib.Caching.Distributed.StackExchangeRedis.csproj b/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/Masa.Contrib.Caching.Distributed.StackExchangeRedis.csproj index 122f651b0..ca8d6d996 100644 --- a/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/Masa.Contrib.Caching.Distributed.StackExchangeRedis.csproj +++ b/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/Masa.Contrib.Caching.Distributed.StackExchangeRedis.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClient.cs b/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClient.cs index 464857a1f..93263c23d 100644 --- a/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClient.cs +++ b/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClient.cs @@ -412,7 +412,7 @@ public override async Task HashIncrementAsync( Action? action = null, CacheEntryOptions? options = null) { - CheckParametersByHashIncrementOrHashDecrement(value); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0L); var script = $@" local exist = redis.call('EXISTS', KEYS[1]) @@ -435,11 +435,6 @@ public override async Task HashIncrementAsync( return result; } - private static void CheckParametersByHashIncrementOrHashDecrement(long value = 1) - { - if (value < 1) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than 0"); - } - /// /// Descending Hash /// @@ -456,7 +451,7 @@ private static void CheckParametersByHashIncrementOrHashDecrement(long value = 1 Action? action = null, CacheEntryOptions? options = null) { - CheckParametersByHashIncrementOrHashDecrement(value); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0L); var script = $@" local exist = redis.call('EXISTS', KEYS[1]) diff --git a/src/Contrib/Caching/Distributed/Tests/Masa.Contrib.Caching.Distributed.StackExchangeRedis.Tests/DistributedCacheClientTest.cs b/src/Contrib/Caching/Distributed/Tests/Masa.Contrib.Caching.Distributed.StackExchangeRedis.Tests/DistributedCacheClientTest.cs index d802b67c4..d40a7f98c 100644 --- a/src/Contrib/Caching/Distributed/Tests/Masa.Contrib.Caching.Distributed.StackExchangeRedis.Tests/DistributedCacheClientTest.cs +++ b/src/Contrib/Caching/Distributed/Tests/Masa.Contrib.Caching.Distributed.StackExchangeRedis.Tests/DistributedCacheClientTest.cs @@ -528,7 +528,7 @@ public async Task TestHashIncrementAndValueLessThan1Async(string key, int value) { await _distributedCacheClient.RemoveAsync(key); - await Assert.ThrowsExceptionAsync(async () + await Assert.ThrowsExceptionAsync(async () => await _distributedCacheClient.HashIncrementAsync(key, value)); } @@ -555,7 +555,7 @@ public async Task TestHashDecrementAndValueLessThan1Async(string key, long value { await _distributedCacheClient.RemoveAsync(key); - await Assert.ThrowsExceptionAsync(async () + await Assert.ThrowsExceptionAsync(async () => await _distributedCacheClient.HashDecrementAsync(key, value)); } diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/ConfigurationApiExtensions.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/ConfigurationApiExtensions.cs index 602d1f3fa..cd6916244 100644 --- a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/ConfigurationApiExtensions.cs +++ b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/ConfigurationApiExtensions.cs @@ -1,17 +1,17 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.BuildingBlocks.Configuration; +namespace Masa.Contrib.Configuration.ConfigurationApi.Dcc; public static class ConfigurationApiExtensions { public static IConfiguration GetDefault(this IConfigurationApi configurationApi) { - return configurationApi.Get(StaticConfig.AppId); + return configurationApi.Get(DccConfig.AppId); } public static IConfiguration GetPublic(this IConfigurationApi configurationApi) { - return configurationApi.Get(StaticConfig.PublicId); + return configurationApi.Get(DccConfig.PublicId); } } diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/DccConfig.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/DccConfig.cs new file mode 100644 index 000000000..8334e4b5b --- /dev/null +++ b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/DccConfig.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Configuration.ConfigurationApi.Dcc; + +public static class DccConfig +{ + public static string AppId { get; internal set; } + + public static string PublicId { get; internal set; } +} diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Internal/Extensions/DistributedCacheClientExtensions.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Internal/Extensions/DistributedCacheClientExtensions.cs index f51e4c20f..0f3ad2fac 100644 --- a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Internal/Extensions/DistributedCacheClientExtensions.cs +++ b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Internal/Extensions/DistributedCacheClientExtensions.cs @@ -1,9 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ReSharper disable once CheckNamespace +namespace Masa.Contrib.Configuration.ConfigurationApi.Dcc.Internal.Extensions; -namespace Masa.BuildingBlocks.Caching; internal static class DistributedCacheClientExtensions { diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Internal/Model/StaticConfig.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Internal/Model/StaticConfig.cs deleted file mode 100644 index a4ec62030..000000000 --- a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Internal/Model/StaticConfig.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Masa.Contrib.Configuration.ConfigurationApi.Dcc.Internal.Model; - -internal static class StaticConfig -{ - public static string AppId { get; set; } - - public static string PublicId { get; set; } -} diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/MasaConfigurationExtensions.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/MasaConfigurationExtensions.cs index 11628ad8a..383e90b5c 100644 --- a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/MasaConfigurationExtensions.cs +++ b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/MasaConfigurationExtensions.cs @@ -154,8 +154,8 @@ public static DccConfigurationOptions ComplementAndCheckDccConfigurationOption( dccConfigurationOptions.ExpandSections.Add(publicSection); } - StaticConfig.AppId = dccConfigurationOptions.DefaultSection.AppId; - StaticConfig.PublicId = dccConfigurationOptions.PublicId; + DccConfig.AppId = dccConfigurationOptions.DefaultSection.AppId; + DccConfig.PublicId = dccConfigurationOptions.PublicId; if (dccConfigurationOptions.ExpandSections.Any(sectionOption => sectionOption.AppId == dccConfigurationOptions.DefaultSection.AppId)) @@ -180,19 +180,21 @@ private static IDistributedCacheClient GetDistributedCacheClient(this IServicePr private static void CheckDccConfigurationOptions(DccConfigurationOptions dccOptions) { - if (string.IsNullOrEmpty(dccOptions.ManageServiceAddress)) - throw new ArgumentNullException(nameof(dccOptions), "ManageServiceAddress cannot be empty"); + MasaArgumentException.ThrowIfNullOrWhiteSpace(dccOptions.ManageServiceAddress); - if (!dccOptions.RedisOptions.Servers.Any()) - throw new ArgumentException("The Redis configuration cannot be empty", nameof(dccOptions)); + MasaArgumentException.ThrowIfNullOrEmptyCollection(dccOptions.RedisOptions.Servers, nameof(dccOptions.RedisOptions)); - if (dccOptions.RedisOptions.Servers.Any(service => string.IsNullOrEmpty(service.Host) || service.Port <= 0)) - throw new ArgumentException( - "The Redis server address cannot be empty, and the Redis port must be grather than 0", - nameof(dccOptions)); + dccOptions.RedisOptions.Servers.ForEach(redisServerOption => + { + MasaArgumentException.ThrowIfNullOrWhiteSpace(redisServerOption.Host, "Redis Host"); + + MasaArgumentException.ThrowIfLessThanOrEqual(redisServerOption.Port, 0, "Redis Port"); + }); - if (dccOptions.ExpandSections.Any(dccSectionOptions => string.IsNullOrWhiteSpace(dccSectionOptions.AppId))) - throw new ArgumentException("sections with an empty AppId are not allowed", nameof(dccOptions)); + dccOptions.ExpandSections.ForEach(section => + { + MasaArgumentException.ThrowIfNullOrWhiteSpace(section.AppId); + }); if (dccOptions.ExpandSections.DistinctBy(dccSectionOptions => dccSectionOptions.AppId).Count() != dccOptions.ExpandSections.Count) throw new ArgumentException("AppId cannot be repeated", nameof(dccOptions)); diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/ConfigurationApiMasaConfigurationOptions.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/ConfigurationApiMasaConfigurationOptions.cs index 46f68e0ae..cc6203431 100644 --- a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/ConfigurationApiMasaConfigurationOptions.cs +++ b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/ConfigurationApiMasaConfigurationOptions.cs @@ -11,7 +11,7 @@ public abstract class ConfigurationApiMasaConfigurationOptions : MasaConfigurati [JsonIgnore] public sealed override string? ParentSection => AppId; - public virtual string AppId => StaticConfig.AppId; + public virtual string AppId => DccConfig.AppId; /// /// The section null means same as the class name, else load from the specify section diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/DccSectionOptions.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/DccSectionOptions.cs index da216b5b0..58f2afe54 100644 --- a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/DccSectionOptions.cs +++ b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/Options/DccSectionOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +using Masa.Contrib.Configuration.ConfigurationApi.Dcc.Internal.Extensions; + namespace Masa.Contrib.Configuration.ConfigurationApi.Dcc.Options; public class DccSectionOptions diff --git a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/_Imports.cs b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/_Imports.cs index 7130f297b..a8f49f1b9 100644 --- a/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/_Imports.cs +++ b/src/Contrib/Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/_Imports.cs @@ -10,6 +10,7 @@ global using Masa.BuildingBlocks.StackSdks.Dcc.Contracts.Enum; global using Masa.BuildingBlocks.StackSdks.Dcc.Contracts.Model; global using Masa.Contrib.Caching.Distributed.StackExchangeRedis; +global using Masa.Contrib.Configuration.ConfigurationApi.Dcc; global using Masa.Contrib.Configuration.ConfigurationApi.Dcc.Internal; global using Masa.Contrib.Configuration.ConfigurationApi.Dcc.Internal.Model; global using Masa.Contrib.Configuration.ConfigurationApi.Dcc.Internal.Parser; diff --git a/src/Contrib/Configuration/ConfigurationApi/Tests/Masa.Contrib.Configuration.ConfigurationApi.Dcc.Tests/DccTest.cs b/src/Contrib/Configuration/ConfigurationApi/Tests/Masa.Contrib.Configuration.ConfigurationApi.Dcc.Tests/DccTest.cs index c22a537cf..6ded36981 100644 --- a/src/Contrib/Configuration/ConfigurationApi/Tests/Masa.Contrib.Configuration.ConfigurationApi.Dcc.Tests/DccTest.cs +++ b/src/Contrib/Configuration/ConfigurationApi/Tests/Masa.Contrib.Configuration.ConfigurationApi.Dcc.Tests/DccTest.cs @@ -524,7 +524,7 @@ public void TestComplementAndCheckDccConfigurationOptionByCustomerDccOptions() public void TestComplementAndCheckDccConfigurationOptionByManageServiceAddressIsEmpty() { DccOptions dccOptions = new DccOptions(); - Assert.ThrowsException(() => + Assert.ThrowsException(() => { MasaConfigurationExtensions.ComplementAndCheckDccConfigurationOption(_masaConfigurationBuilder.Object, dccOptions); }); @@ -537,7 +537,7 @@ public void TestComplementAndCheckDccConfigurationOptionByRedisServersIsEmpty() { ManageServiceAddress = nameof(DccOptions.ManageServiceAddress), }; - Assert.ThrowsException(() => + Assert.ThrowsException(() => { MasaConfigurationExtensions.ComplementAndCheckDccConfigurationOption(_masaConfigurationBuilder.Object, dccOptions); }); @@ -599,7 +599,7 @@ public void TestComplementAndCheckDccConfigurationOptionByAppIdIsEmpty() } } }; - Assert.ThrowsException(() => + Assert.ThrowsException(() => { MasaConfigurationExtensions.ComplementAndCheckDccConfigurationOption(_masaConfigurationBuilder.Object, dccOptions); }); diff --git a/src/Contrib/Configuration/Masa.Contrib.Configuration/Extensions/ServiceCollectionExtensions.cs b/src/Contrib/Configuration/Masa.Contrib.Configuration/Extensions/ServiceCollectionExtensions.cs index c5bb62fb7..3457e293c 100644 --- a/src/Contrib/Configuration/Masa.Contrib.Configuration/Extensions/ServiceCollectionExtensions.cs +++ b/src/Contrib/Configuration/Masa.Contrib.Configuration/Extensions/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ReSharper disable once CheckNamespace + namespace Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionExtensions @@ -135,8 +136,7 @@ public static IServiceCollection AddMasaConfiguration( configurationBuilder.Sources.Clear(); configurationBuilder.AddConfiguration(masaConfiguration); - if (sourceConfiguration == null) - services.AddSingleton(_ => configurationBuilder.Build()); + if (sourceConfiguration == null) services.AddSingleton(_ => configurationBuilder.Build()); return services; } diff --git a/src/Contrib/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs b/src/Contrib/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs index 80478d62d..18d6b4e75 100644 --- a/src/Contrib/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs +++ b/src/Contrib/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs @@ -25,35 +25,10 @@ public LocalMasaConfigurationRepository( private void Initialize(IConfiguration configuration) { - Dictionary data = new(); - GetData(configuration, configuration.GetChildren(), ref data); + var data = configuration.ConvertToDictionary(); _data = new Properties(data); } - private void GetData(IConfiguration configuration, IEnumerable configurationSections, - ref Dictionary dictionary) - { - foreach (var configurationSection in configurationSections) - { - var section = configuration.GetSection(configurationSection.Path); - - var childrenSections = section.GetChildren()?.ToList() ?? new List(); - - if (!section.Exists() || !childrenSections.Any()) - { - var key = section.Path; - if (!dictionary.ContainsKey(key)) - { - dictionary.Add(key, configuration[section.Path]); - } - } - else - { - GetData(configuration, childrenSections, ref dictionary); - } - } - } - public override Properties Load() { return _data; diff --git a/src/Contrib/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj b/src/Contrib/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj index fbe7d581b..3f18f6c7b 100644 --- a/src/Contrib/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj +++ b/src/Contrib/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/ConfigurationOptions.cs b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/ConfigurationOptions.cs index 92debe9c6..ba17f9853 100644 --- a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/ConfigurationOptions.cs +++ b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/ConfigurationOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Masa.Contrib.Configuration; public class ConfigurationOptions diff --git a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/LocalMasaConfigurationOptions.cs b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/LocalMasaConfigurationOptions.cs index f77e56bbc..f3bb5d3f3 100644 --- a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/LocalMasaConfigurationOptions.cs +++ b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/LocalMasaConfigurationOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Masa.Contrib.Configuration; public abstract class LocalMasaConfigurationOptions : MasaConfigurationOptions diff --git a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaAppConfigureOptionsRelation.cs b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaAppConfigureOptionsRelation.cs index 43fc8def5..2a22559e8 100644 --- a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaAppConfigureOptionsRelation.cs +++ b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaAppConfigureOptionsRelation.cs @@ -1,7 +1,9 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.BuildingBlocks.Configuration.Options; +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Configuration; public class MasaAppConfigureOptionsRelation { @@ -29,14 +31,11 @@ public MasaAppConfigureOptionsRelation() public MasaAppConfigureOptionsRelation SetOptionsRelation(string key, string variable, string defaultValue) { - if (string.IsNullOrEmpty(key)) - throw new ArgumentException($"{nameof(key)} cannot be empty", nameof(key)); + MasaArgumentException.ThrowIfNullOrEmpty(key); - if (string.IsNullOrEmpty(variable)) - throw new ArgumentException($"{nameof(variable)} cannot be empty", nameof(variable)); + MasaArgumentException.ThrowIfNullOrEmpty(variable); - if (string.IsNullOrEmpty(defaultValue)) - throw new ArgumentException($"{nameof(defaultValue)} cannot be empty", nameof(defaultValue)); + MasaArgumentException.ThrowIfNullOrEmpty(defaultValue); Data[key] = (variable, defaultValue); return this; @@ -46,8 +45,7 @@ public MasaAppConfigureOptionsRelation SetOptionsRelation(string key, string var internal (string Variable, string DefaultValue) GetValue(string key) { - if (string.IsNullOrEmpty(key)) - throw new ArgumentException($"{nameof(key)} cannot be empty", nameof(key)); + MasaArgumentException.ThrowIfNullOrEmpty(key); return Data[key]; } diff --git a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaConfigurationRelationOptions.cs b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaConfigurationRelationOptions.cs index eeae8c588..464861b9c 100644 --- a/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaConfigurationRelationOptions.cs +++ b/src/Contrib/Configuration/Masa.Contrib.Configuration/Options/MasaConfigurationRelationOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Masa.Contrib.Configuration; public class MasaConfigurationRelationOptions diff --git a/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/DefaultLocalDistributedLock.cs b/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/DefaultLocalDistributedLock.cs index 9d87952e2..c21525434 100644 --- a/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/DefaultLocalDistributedLock.cs +++ b/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/DefaultLocalDistributedLock.cs @@ -33,7 +33,8 @@ public class DefaultLocalDistributedLock : IDistributedLock private SemaphoreSlim GetSemaphoreSlim(string key) { - ArgumentNullOrWhiteSpaceException.ThrowIfNullOrWhiteSpace(key); + MasaArgumentException.ThrowIfNullOrWhiteSpace(key); + return _localObjects.GetOrAdd(key, _ => new SemaphoreSlim(1, 1)); } } diff --git a/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/Internal/ArgumentNullOrWhiteSpaceException.cs b/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/Internal/ArgumentNullOrWhiteSpaceException.cs deleted file mode 100644 index 6246faf3a..000000000 --- a/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/Internal/ArgumentNullOrWhiteSpaceException.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Masa.Contrib.Data.DistributedLock.Local.Internal; - -internal static class ArgumentNullOrWhiteSpaceException -{ - public static void ThrowIfNullOrWhiteSpace(string? argument, string? paramName = null) - { - paramName ??= nameof(argument); - - if (string.IsNullOrWhiteSpace(argument)) - throw new ArgumentException($"{paramName} can not be null, empty or white space!", paramName); - } -} diff --git a/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/Masa.Contrib.Data.DistributedLock.Local.csproj b/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/Masa.Contrib.Data.DistributedLock.Local.csproj index a02b6a725..b3a8081c6 100644 --- a/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/Masa.Contrib.Data.DistributedLock.Local.csproj +++ b/src/Contrib/Data/DistributedLock/Masa.Contrib.Data.DistributedLock.Local/Masa.Contrib.Data.DistributedLock.Local.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/DefaultMedallionDistributedLock.cs b/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/DefaultMedallionDistributedLock.cs index f8cd492fa..61b1aad8d 100644 --- a/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/DefaultMedallionDistributedLock.cs +++ b/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/DefaultMedallionDistributedLock.cs @@ -12,7 +12,7 @@ public DefaultMedallionDistributedLock(IDistributedLockProvider distributedLockP public IDisposable? TryGet(string key, TimeSpan timeout = default) { - ArgumentNullOrWhiteSpaceException.ThrowIfNullOrWhiteSpace(key); + MasaArgumentException.ThrowIfNullOrWhiteSpace(key); var handle = _distributedLockProvider.TryAcquireLock(key, timeout); if (handle == null) return null; @@ -22,7 +22,7 @@ public DefaultMedallionDistributedLock(IDistributedLockProvider distributedLockP public async Task TryGetAsync(string key, TimeSpan timeout = default, CancellationToken cancellationToken = default) { - ArgumentNullOrWhiteSpaceException.ThrowIfNullOrWhiteSpace(key); + MasaArgumentException.ThrowIfNullOrWhiteSpace(key); var handle = await _distributedLockProvider.TryAcquireLockAsync(key, timeout, cancellationToken); if (handle == null) return null; diff --git a/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/Internal/ArgumentNullOrWhiteSpaceException.cs b/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/Internal/ArgumentNullOrWhiteSpaceException.cs deleted file mode 100644 index 95687b703..000000000 --- a/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/Internal/ArgumentNullOrWhiteSpaceException.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Masa.Contrib.Data.DistributedLock.Medallion.Internal; - -internal static class ArgumentNullOrWhiteSpaceException -{ - public static void ThrowIfNullOrWhiteSpace(string? argument, string? paramName = null) - { - paramName ??= nameof(argument); - - if (string.IsNullOrWhiteSpace(argument)) - throw new ArgumentException($"{paramName} can not be null, empty or white space!", paramName); - } -} diff --git a/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/Masa.Contrib.Data.DistributedLock.Medallion.csproj b/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/Masa.Contrib.Data.DistributedLock.Medallion.csproj index fa0c30832..c803b56fb 100644 --- a/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/Masa.Contrib.Data.DistributedLock.Medallion.csproj +++ b/src/Contrib/Data/DistributedLock/Medallion/Masa.Contrib.Data.DistributedLock.Medallion/Masa.Contrib.Data.DistributedLock.Medallion.csproj @@ -12,8 +12,8 @@ + - diff --git a/src/Contrib/Data/IdGenerator/Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake.csproj b/src/Contrib/Data/IdGenerator/Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake.csproj index 0f3f82acd..99f8caf01 100644 --- a/src/Contrib/Data/IdGenerator/Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake.csproj +++ b/src/Contrib/Data/IdGenerator/Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake/Masa.Contrib.Data.IdGenerator.Snowflake.csproj @@ -5,11 +5,15 @@ enable enable - + + + + + - - - + + + diff --git a/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/Masa.Contrib.Data.Mapping.Mapster.csproj b/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/Masa.Contrib.Data.Mapping.Mapster.csproj index a089c9f1c..bf37ed582 100644 --- a/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/Masa.Contrib.Data.Mapping.Mapster.csproj +++ b/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/Masa.Contrib.Data.Mapping.Mapster.csproj @@ -14,8 +14,8 @@ + - diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Masa.Contrib.Data.EFCore.csproj b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Masa.Contrib.Data.EFCore.csproj index af940e0d0..86e43173e 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Masa.Contrib.Data.EFCore.csproj +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/Masa.Contrib.Data.EFCore.csproj @@ -19,8 +19,8 @@ + - diff --git a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs index 863702333..7b90a73c3 100644 --- a/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs +++ b/src/Contrib/Data/Orm/EFCore/Masa.Contrib.Data.EFCore/MasaDbContext.cs @@ -136,14 +136,14 @@ public sealed override int SaveChanges(bool acceptAllChangesOnSuccess) protected virtual void OnBeforeSaveChanges() { - UpdateRowVesion(ChangeTracker); + UpdateRowVersion(ChangeTracker); OnBeforeSaveChangesByFilters(); DomainEventEnqueueAsync(ChangeTracker).ConfigureAwait(false).GetAwaiter().GetResult(); } protected virtual async Task OnBeforeSaveChangesAsync() { - UpdateRowVesion(ChangeTracker); + UpdateRowVersion(ChangeTracker); OnBeforeSaveChangesByFilters(); await DomainEventEnqueueAsync(ChangeTracker); } @@ -184,7 +184,7 @@ protected virtual async Task DomainEventEnqueueAsync(ChangeTracker changeTracker await DomainEventBus.Enqueue(domainEvent); } - protected virtual void UpdateRowVesion(ChangeTracker changeTracker) + protected virtual void UpdateRowVersion(ChangeTracker changeTracker) { if (ConcurrencyStampProvider == null) return; diff --git a/src/Contrib/Ddd/Domain/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj b/src/Contrib/Ddd/Domain/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj index 0e5827090..e7e6e0a15 100644 --- a/src/Contrib/Ddd/Domain/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj +++ b/src/Contrib/Ddd/Domain/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/Masa.Contrib.Development.DaprStarter.AspNetCore.csproj b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/Masa.Contrib.Development.DaprStarter.AspNetCore.csproj index d8f8a6f19..b570e442a 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/Masa.Contrib.Development.DaprStarter.AspNetCore.csproj +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/Masa.Contrib.Development.DaprStarter.AspNetCore.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Masa.Contrib.Development.DaprStarter.csproj b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Masa.Contrib.Development.DaprStarter.csproj index 470886adf..737ab2543 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Masa.Contrib.Development.DaprStarter.csproj +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Masa.Contrib.Development.DaprStarter.csproj @@ -18,7 +18,6 @@ - diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj index be70feff1..a7d567ad5 100644 --- a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj @@ -8,10 +8,11 @@ - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,7 +24,7 @@ - + diff --git a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj index d93a20e6a..cbcd1d650 100644 --- a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj +++ b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore.csproj b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore.csproj index aff15520a..a3b8312d3 100644 --- a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore.csproj +++ b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EFCore.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.csproj b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.csproj index 18fe319d2..d88f6947c 100644 --- a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.csproj +++ b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents.csproj @@ -7,13 +7,14 @@ - + + - - + + diff --git a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Options/DispatcherOptions.cs b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Options/DispatcherOptions.cs index 383659973..4851be2d3 100644 --- a/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Options/DispatcherOptions.cs +++ b/src/Contrib/Dispatcher/IntegrationEvents/Masa.Contrib.Dispatcher.IntegrationEvents/Options/DispatcherOptions.cs @@ -19,8 +19,7 @@ public int LocalRetryTimes get => _localRetryTimes; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(LocalRetryTimes)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(LocalRetryTimes)); _localRetryTimes = value; } @@ -37,8 +36,7 @@ public int MaxRetryTimes get => _maxRetryTimes; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(MaxRetryTimes)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(MaxRetryTimes)); _maxRetryTimes = value; } @@ -56,8 +54,7 @@ public int FailedRetryInterval get => _failedRetryInterval; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(FailedRetryInterval)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(FailedRetryInterval)); _failedRetryInterval = value; } @@ -74,8 +71,7 @@ public int MinimumRetryInterval get => _minimumRetryInterval; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(MinimumRetryInterval)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(MinimumRetryInterval)); _minimumRetryInterval = value; } @@ -94,8 +90,7 @@ public int LocalFailedRetryInterval get => _localFailedRetryInterval; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(LocalFailedRetryInterval)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(LocalFailedRetryInterval)); _localFailedRetryInterval = value; } @@ -111,8 +106,7 @@ public int RetryBatchSize get => _retryBatchSize; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(RetryBatchSize)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(RetryBatchSize)); _retryBatchSize = value; } @@ -130,8 +124,7 @@ public int CleaningLocalQueueExpireInterval get => _cleaningLocalQueueExpireInterval; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(CleaningLocalQueueExpireInterval)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(CleaningLocalQueueExpireInterval)); _cleaningLocalQueueExpireInterval = value; } @@ -149,8 +142,7 @@ public int CleaningExpireInterval get => _cleaningExpireInterval; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(CleaningExpireInterval)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(CleaningExpireInterval)); _cleaningExpireInterval = value; } @@ -167,8 +159,7 @@ public long PublishedExpireTime get => _publishedExpireTime; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(PublishedExpireTime)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(PublishedExpireTime)); _publishedExpireTime = value; } @@ -184,8 +175,7 @@ public int DeleteBatchCount get => _deleteBatchCount; set { - if (value <= 0) - throw new ArgumentException("must be greater than 0", nameof(DeleteBatchCount)); + MasaArgumentException.ThrowIfLessThanOrEqual(value, 0, nameof(DeleteBatchCount)); _deleteBatchCount = value; } @@ -200,8 +190,7 @@ public int DeleteBatchCount public DispatcherOptions(IServiceCollection services, Assembly[] assemblies) : this(services) { - if (assemblies == null || assemblies.Length == 0) - throw new ArgumentException(nameof(assemblies)); + MasaArgumentException.ThrowIfNullOrEmptyCollection(assemblies); Assemblies = assemblies; AllEventTypes = assemblies diff --git a/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs b/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs index d1a7101e9..0a54e9bed 100644 --- a/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs +++ b/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs @@ -44,11 +44,11 @@ public void TestDispatcherOption() var services = new ServiceCollection(); DispatcherOptions options; - Assert.ThrowsException(() => + Assert.ThrowsException(() => { options = new DispatcherOptions(services, null!); }); - Assert.ThrowsException(() => + Assert.ThrowsException(() => { options = new DispatcherOptions(services, Array.Empty()); }); diff --git a/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/DispatcherOptionTest.cs b/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/DispatcherOptionTest.cs index 79a51335e..0005cacb2 100644 --- a/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/DispatcherOptionTest.cs +++ b/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/DispatcherOptionTest.cs @@ -22,7 +22,7 @@ public void TestSetLocalRetryTimes() _options.LocalRetryTimes = 5; Assert.IsTrue(_options.LocalRetryTimes == 5); - Assert.ThrowsException(() => _options.LocalRetryTimes = 0); + Assert.ThrowsException(() => _options.LocalRetryTimes = 0); } [TestMethod] @@ -32,7 +32,7 @@ public void TestSetMaxRetryTimes() _options.MaxRetryTimes = 5; Assert.IsTrue(_options.MaxRetryTimes == 5); - Assert.ThrowsException(() => _options.MaxRetryTimes = 0); + Assert.ThrowsException(() => _options.MaxRetryTimes = 0); } [TestMethod] @@ -42,7 +42,7 @@ public void TestSetFailedRetryInterval() _options.FailedRetryInterval = 5; Assert.IsTrue(_options.FailedRetryInterval == 5); - Assert.ThrowsException(() => _options.FailedRetryInterval = 0); + Assert.ThrowsException(() => _options.FailedRetryInterval = 0); } [TestMethod] @@ -52,7 +52,7 @@ public void TestSetMinimumRetryInterval() _options.MinimumRetryInterval = 5; Assert.IsTrue(_options.MinimumRetryInterval == 5); - Assert.ThrowsException(() => _options.MinimumRetryInterval = 0); + Assert.ThrowsException(() => _options.MinimumRetryInterval = 0); } [TestMethod] @@ -62,7 +62,7 @@ public void TestSetLocalFailedRetryInterval() _options.LocalFailedRetryInterval = 5; Assert.IsTrue(_options.LocalFailedRetryInterval == 5); - Assert.ThrowsException(() => _options.LocalFailedRetryInterval = -1); + Assert.ThrowsException(() => _options.LocalFailedRetryInterval = -1); } [TestMethod] @@ -72,7 +72,7 @@ public void TestSetRetryBatchSize() _options.RetryBatchSize = 5; Assert.IsTrue(_options.RetryBatchSize == 5); - Assert.ThrowsException(() => _options.RetryBatchSize = -1); + Assert.ThrowsException(() => _options.RetryBatchSize = -1); } [TestMethod] @@ -82,7 +82,7 @@ public void TestSetCleaningLocalQueueExpireInterval() _options.CleaningLocalQueueExpireInterval = 5; Assert.IsTrue(_options.CleaningLocalQueueExpireInterval == 5); - Assert.ThrowsException(() => _options.CleaningLocalQueueExpireInterval = 0); + Assert.ThrowsException(() => _options.CleaningLocalQueueExpireInterval = 0); } [TestMethod] @@ -92,7 +92,7 @@ public void TestSetCleaningExpireInterval() _options.CleaningExpireInterval = 5; Assert.IsTrue(_options.CleaningExpireInterval == 5); - Assert.ThrowsException(() => _options.CleaningExpireInterval = 0); + Assert.ThrowsException(() => _options.CleaningExpireInterval = 0); } [TestMethod] @@ -102,7 +102,7 @@ public void TestSetPublishedExpireTime() _options.PublishedExpireTime = 24 * 3 * 3600; Assert.IsTrue(_options.PublishedExpireTime == 24 * 3 * 3600); - Assert.ThrowsException(() => _options.PublishedExpireTime = 0); + Assert.ThrowsException(() => _options.PublishedExpireTime = 0); } [TestMethod] @@ -112,7 +112,7 @@ public void TestSetDeleteBatchCount() _options.DeleteBatchCount = 100; Assert.IsTrue(_options.DeleteBatchCount == 100); - Assert.ThrowsException(() => _options.DeleteBatchCount = 0); + Assert.ThrowsException(() => _options.DeleteBatchCount = 0); } [TestMethod] diff --git a/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/IntegrationEventBusTest.cs b/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/IntegrationEventBusTest.cs index d411abea5..6249ecfb0 100644 --- a/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/IntegrationEventBusTest.cs +++ b/src/Contrib/Dispatcher/IntegrationEvents/Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Tests/IntegrationEventBusTest.cs @@ -49,11 +49,11 @@ public void TestDispatcherOption() var services = new ServiceCollection(); DispatcherOptions options; - Assert.ThrowsException(() => + Assert.ThrowsException(() => { options = new DispatcherOptions(services, null!); }); - Assert.ThrowsException(() => + Assert.ThrowsException(() => { options = new DispatcherOptions(services, Array.Empty()); }); diff --git a/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/Masa.Contrib.Dispatcher.Events.FluentValidation.csproj b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/Masa.Contrib.Dispatcher.Events.FluentValidation.csproj new file mode 100644 index 000000000..24fd9c642 --- /dev/null +++ b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/Masa.Contrib.Dispatcher.Events.FluentValidation.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/Middleware/ValidatorMiddleware.cs b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/Middleware/ValidatorMiddleware.cs new file mode 100644 index 000000000..368ce74cc --- /dev/null +++ b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/Middleware/ValidatorMiddleware.cs @@ -0,0 +1,45 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.Contrib.Dispatcher.Events; + +public class ValidatorMiddleware : Middleware + where TEvent : IEvent +{ + private readonly ILogger>? _logger; + private readonly IEnumerable> _validators; + + public ValidatorMiddleware(IEnumerable> validators, ILogger>? logger = null) + { + _validators = validators; + _logger = logger; + } + + public override async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + var typeName = @event.GetType().FullName; + + _logger?.LogDebug("----- Validating command {CommandType}", typeName); + + var failures = _validators + .Select(v => v.Validate(@event)) + .SelectMany(result => result.Errors) + .Where(error => error != null) + .ToList(); + + if (failures.Any()) + { + _logger?.LogError("Validation errors - {CommandType} - Event: {@Command} - Errors: {@ValidationErrors}", + typeName, + @event, + failures); + + var validationException = new ValidationException("Validation exception", failures); + throw new MasaValidatorException(validationException.Message); + } + + await next(); + } +} diff --git a/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/_Imports.cs b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/_Imports.cs new file mode 100644 index 000000000..7dfdb7c26 --- /dev/null +++ b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events.FluentValidation/_Imports.cs @@ -0,0 +1,6 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using FluentValidation; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Microsoft.Extensions.Logging; diff --git a/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj index 2495aa4b6..090a8e918 100644 --- a/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj +++ b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj @@ -16,8 +16,8 @@ + - diff --git a/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs index d0067205a..a1eed5285 100644 --- a/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs +++ b/src/Contrib/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs @@ -21,8 +21,7 @@ private bool IsSupportUnitOfWork(Type eventType) public DispatcherOptions(IServiceCollection services, Assembly[] assemblies) : this(services) { - if (assemblies == null || assemblies.Length == 0) - throw new ArgumentException(nameof(assemblies)); + MasaArgumentException.ThrowIfNullOrEmptyCollection(assemblies); Assemblies = assemblies; AllEventTypes = assemblies diff --git a/src/Contrib/Dispatcher/Tests/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs b/src/Contrib/Dispatcher/Tests/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs index 104ff7d80..d2ca1889f 100644 --- a/src/Contrib/Dispatcher/Tests/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs +++ b/src/Contrib/Dispatcher/Tests/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs @@ -24,7 +24,7 @@ public void TestAddNullAssembly() { var services = new ServiceCollection(); services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - Assert.ThrowsException(() => + Assert.ThrowsException(() => { Assembly[] assemblies = null!; services.AddEventBus(assemblies!); @@ -36,7 +36,7 @@ public void TestAddEmptyAssembly() { var services = new ServiceCollection(); services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - Assert.ThrowsException(() => + Assert.ThrowsException(() => { services.AddEventBus(Array.Empty()); }); @@ -47,7 +47,7 @@ public void TestEventBusByAddNullAssembly() { var services = new ServiceCollection(); services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - Assert.ThrowsException(() => + Assert.ThrowsException(() => { services.AddTestEventBus(null!, ServiceLifetime.Scoped); }); diff --git a/src/Contrib/Exception/Masa.Contrib.Exceptions/Extensions/ApplicationBuilderExtensions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..d2f2bb05b --- /dev/null +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.AspNetCore.Builder; + +[ExcludeFromCodeCoverage] +public static class ApplicationBuilderExtensions +{ + /// + /// Use I18n + /// + /// + /// + /// + /// + public static IApplicationBuilder UseMasaExceptionHandler( + this IApplicationBuilder app, + Action? exceptionHandlingOptions = null, + string? defaultCulture = null) + { + var option = new MasaExceptionHandlerOptions(); + exceptionHandlingOptions?.Invoke(option); + + app.UseI18n(defaultCulture); + + app.UseMiddleware(Options.Create(option)); + + return app; + } +} diff --git a/src/Utils/Masa.Utils.Exceptions/Extensions/MvcBuilderExtensions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Extensions/MvcBuilderExtensions.cs similarity index 70% rename from src/Utils/Masa.Utils.Exceptions/Extensions/MvcBuilderExtensions.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Extensions/MvcBuilderExtensions.cs index 56145ce8e..2c78c5618 100644 --- a/src/Utils/Masa.Utils.Exceptions/Extensions/MvcBuilderExtensions.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Extensions/MvcBuilderExtensions.cs @@ -2,20 +2,25 @@ // Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ReSharper disable once CheckNamespace + namespace Microsoft.Extensions.DependencyInjection; +[ExcludeFromCodeCoverage] public static class MvcBuilderExtensions { public static IMvcBuilder AddMasaExceptionHandler(this IMvcBuilder builder) { - return builder.AddMasaExceptionHandler(_ => { }); + return builder.AddMasaExceptionHandler(_ => + { + }); } public static IMvcBuilder AddMasaExceptionHandler(this IMvcBuilder builder, Action action) { - builder.Services.AddLocalization(); - - builder.Services.Configure(options => { options.Filters.Add(); }); + builder.Services.Configure(options => + { + options.Filters.Add(); + }); builder.Services.Configure(action); diff --git a/src/Utils/Masa.Utils.Exceptions/Handlers/ExceptionHandlerMiddleware.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Handlers/ExceptionHandlerMiddleware.cs similarity index 67% rename from src/Utils/Masa.Utils.Exceptions/Handlers/ExceptionHandlerMiddleware.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Handlers/ExceptionHandlerMiddleware.cs index 77d10fe8c..b52ea6fcf 100644 --- a/src/Utils/Masa.Utils.Exceptions/Handlers/ExceptionHandlerMiddleware.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Handlers/ExceptionHandlerMiddleware.cs @@ -1,8 +1,11 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Microsoft.AspNetCore.Builder; +[ExcludeFromCodeCoverage] public class ExceptionHandlerMiddleware { private readonly RequestDelegate _next; @@ -24,6 +27,7 @@ public ExceptionHandlerMiddleware( public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceProvider) { + var frameworkI18n = serviceProvider.GetService>(); try { await _next(httpContext); @@ -37,10 +41,7 @@ public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceP } else { - var masaExceptionHandler = - Masa.Utils.Exceptions.Internal.ExceptionHandlerExtensions.GetMasaExceptionHandler( - serviceProvider, - _options.MasaExceptionHandlerType); + var masaExceptionHandler = serviceProvider.GetMasaExceptionHandler(_options.MasaExceptionHandlerType); masaExceptionHandler?.OnException(masaExceptionContext); } @@ -56,19 +57,19 @@ await httpContext.Response.WriteTextAsync( return; } - _logger?.WriteLog(masaExceptionContext.Exception, - masaExceptionContext.Exception is UserFriendlyException ? LogLevel.Information : LogLevel.Error, - _logRelationOptions); + _logger?.WriteLog(masaExceptionContext.Exception, _logRelationOptions); + + var httpStatusCode = masaExceptionContext.Exception.GetHttpStatusCode(); - if (masaExceptionContext.Exception is UserFriendlyException) + if (masaExceptionContext.Exception is MasaException masaException) { - await httpContext.Response.WriteTextAsync((int)MasaHttpStatusCode.UserFriendlyException, - masaExceptionContext.Exception.Message); + await httpContext.Response.WriteTextAsync(httpStatusCode, masaException.GetLocalizedMessage()); } - else if (masaExceptionContext.Exception is MasaException || _options.CatchAllException) + else if (_options.CatchAllException) { - var message = Constant.DEFAULT_EXCEPTION_MESSAGE; - await httpContext.Response.WriteTextAsync((int)HttpStatusCode.InternalServerError, message); + string message = frameworkI18n == null ? ErrorCode.GetErrorMessage(ErrorCode.INTERNAL_SERVER_ERROR)! : + frameworkI18n[ErrorCode.INTERNAL_SERVER_ERROR]; + await httpContext.Response.WriteTextAsync(httpStatusCode, message); } else { diff --git a/src/Utils/Masa.Utils.Exceptions/Handlers/GlobalExceptionFilter.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Handlers/MvcGlobalExceptionFilter.cs similarity index 56% rename from src/Utils/Masa.Utils.Exceptions/Handlers/GlobalExceptionFilter.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Handlers/MvcGlobalExceptionFilter.cs index d74c7ffa5..e9dd2a078 100644 --- a/src/Utils/Masa.Utils.Exceptions/Handlers/GlobalExceptionFilter.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Handlers/MvcGlobalExceptionFilter.cs @@ -1,28 +1,34 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Microsoft.AspNetCore.Mvc.Filters; /// /// Mvc pipeline exception filter to catch global exception /// -public class MvcGlobalExcetionFilter : IExceptionFilter +[ExcludeFromCodeCoverage] +public class MvcGlobalExceptionFilter : IExceptionFilter { private readonly IServiceProvider _serviceProvider; private readonly IMasaExceptionHandler? _masaExceptionHandler; private readonly MasaExceptionHandlerOptions _options; private readonly MasaExceptionLogRelationOptions _logRelationOptions; - private readonly ILogger? _logger; + private readonly I18n? _frameworkI18n; + private readonly ILogger? _logger; - public MvcGlobalExcetionFilter(IServiceProvider serviceProvider, + public MvcGlobalExceptionFilter(IServiceProvider serviceProvider, IOptions options, IOptions logRelationOptions, - ILogger? logger = null) + I18n? frameworkI18n = null, + ILogger? logger = null) { _serviceProvider = serviceProvider; _options = options.Value; - _masaExceptionHandler = ExceptionHandlerExtensions.GetMasaExceptionHandler(serviceProvider, _options.MasaExceptionHandlerType); + _masaExceptionHandler = serviceProvider.GetMasaExceptionHandler(_options.MasaExceptionHandlerType); _logRelationOptions = logRelationOptions.Value; + _frameworkI18n = frameworkI18n; _logger = logger; } @@ -45,26 +51,33 @@ public void OnException(ExceptionContext context) { context.ExceptionHandled = true; context.Result = new DefaultExceptionResult( - masaExceptionContext.Message, + masaExceptionContext.Message!, masaExceptionContext.StatusCode, masaExceptionContext.ContentType); return; } - _logger?.WriteLog(masaExceptionContext.Exception, - masaExceptionContext.Exception is UserFriendlyException ? LogLevel.Information : LogLevel.Error, - _logRelationOptions); + _logger?.WriteLog(masaExceptionContext.Exception, _logRelationOptions); + + var httpStatusCode = masaExceptionContext.Exception.GetHttpStatusCode(); - if (masaExceptionContext.Exception is UserFriendlyException userFriendlyException) + if (masaExceptionContext.Exception is MasaException masaException) { context.ExceptionHandled = true; - context.Result = new UserFriendlyExceptionResult(userFriendlyException.Message); + context.Result = new DefaultExceptionResult(masaException.GetLocalizedMessage(), + httpStatusCode, + masaExceptionContext.ContentType); return; } - if (masaExceptionContext.Exception is MasaException || _options.CatchAllException) + if (_options.CatchAllException) { context.ExceptionHandled = true; - context.Result = new InternalServerErrorObjectResult(Constant.DEFAULT_EXCEPTION_MESSAGE); + + string message = _frameworkI18n == null ? ErrorCode.GetErrorMessage(ErrorCode.INTERNAL_SERVER_ERROR)! : + _frameworkI18n[ErrorCode.INTERNAL_SERVER_ERROR]; + context.Result = new DefaultExceptionResult(message, + httpStatusCode, + masaExceptionContext.ContentType); return; } } diff --git a/src/Utils/Masa.Utils.Exceptions/IMasaExceptionHandler.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/IMasaExceptionHandler.cs similarity index 86% rename from src/Utils/Masa.Utils.Exceptions/IMasaExceptionHandler.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/IMasaExceptionHandler.cs index 173753b75..da293c371 100644 --- a/src/Utils/Masa.Utils.Exceptions/IMasaExceptionHandler.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/IMasaExceptionHandler.cs @@ -1,6 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; public interface IMasaExceptionHandler diff --git a/src/Utils/Masa.Utils.Exceptions/Internal/Constant.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/Constant.cs similarity index 66% rename from src/Utils/Masa.Utils.Exceptions/Internal/Constant.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/Constant.cs index 365fdc907..c67692ad4 100644 --- a/src/Utils/Masa.Utils.Exceptions/Internal/Constant.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/Constant.cs @@ -1,11 +1,9 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.Utils.Exceptions.Internal; +namespace Masa.Contrib.Exceptions.Internal; internal static class Constant { public const string DEFAULT_HTTP_CONTENT_TYPE = "text/plain; charset=utf-8"; - - public const string DEFAULT_EXCEPTION_MESSAGE = "An error occur in masa framework."; } diff --git a/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/ExceptionExtensions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/ExceptionExtensions.cs new file mode 100644 index 000000000..254435594 --- /dev/null +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/ExceptionExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Exceptions.Internal; + +[ExcludeFromCodeCoverage] +internal static class ExceptionExtensions +{ + public static int GetHttpStatusCode(this Exception exception) + { + if (exception is UserFriendlyException) + return (int)MasaHttpStatusCode.UserFriendlyException; + + if (exception is MasaValidatorException) + return (int)MasaHttpStatusCode.ValidatorException; + + return (int)HttpStatusCode.InternalServerError; + } +} diff --git a/src/Utils/Masa.Utils.Exceptions/Internal/HttpResponseExtensions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/HttpResponseExtensions.cs similarity index 92% rename from src/Utils/Masa.Utils.Exceptions/Internal/HttpResponseExtensions.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/HttpResponseExtensions.cs index 8909aab1c..136266ae8 100644 --- a/src/Utils/Masa.Utils.Exceptions/Internal/HttpResponseExtensions.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/HttpResponseExtensions.cs @@ -1,8 +1,9 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.Utils.Exceptions.Internal; +namespace Masa.Contrib.Exceptions.Internal; +[ExcludeFromCodeCoverage] internal static class HttpResponseExtensions { /// diff --git a/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/LoggerExtensions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/LoggerExtensions.cs new file mode 100644 index 000000000..9e8e50ed0 --- /dev/null +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/LoggerExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +[ExcludeFromCodeCoverage] +internal static class LoggerExtensions +{ + public static void WriteLog( + this ILogger logger, + Exception exception, + MasaExceptionLogRelationOptions logRelationOptions, + string? message = null) + { + LogLevel defaultLogLevel = LogLevel.Error; + if (exception is MasaException masaException) + { + if (masaException.LogLevel != null) + { + logger.Log(masaException.LogLevel!.Value, exception, "{Message}", message); + return; + } + if (masaException is UserFriendlyException) + defaultLogLevel = LogLevel.Information; + } + var logLevel = logRelationOptions.GetLogLevel(exception, defaultLogLevel); + logger.Log(logLevel, exception, "{Message}", message); + } +} diff --git a/src/Utils/Masa.Utils.Exceptions/Internal/ExceptionHandlerExtensions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/ServiceProviderExtensions.cs similarity index 85% rename from src/Utils/Masa.Utils.Exceptions/Internal/ExceptionHandlerExtensions.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/ServiceProviderExtensions.cs index 7d409a73f..6aa173246 100644 --- a/src/Utils/Masa.Utils.Exceptions/Internal/ExceptionHandlerExtensions.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Internal/ServiceProviderExtensions.cs @@ -1,11 +1,12 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.Utils.Exceptions.Internal; +namespace Masa.Contrib.Exceptions.Internal; -internal static class ExceptionHandlerExtensions +[ExcludeFromCodeCoverage] +internal static class ServiceProviderExtensions { - public static IMasaExceptionHandler? GetMasaExceptionHandler(IServiceProvider serviceProvider, Type? masaExceptionHandlerType) + public static IMasaExceptionHandler? GetMasaExceptionHandler(this IServiceProvider serviceProvider, Type? masaExceptionHandlerType) { var exceptionHandler = serviceProvider.GetService(); if (exceptionHandler != null) diff --git a/src/Contrib/Exception/Masa.Contrib.Exceptions/Masa.Contrib.Exceptions.csproj b/src/Contrib/Exception/Masa.Contrib.Exceptions/Masa.Contrib.Exceptions.csproj new file mode 100644 index 000000000..bb1f4fc8c --- /dev/null +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Masa.Contrib.Exceptions.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/Utils/Masa.Utils.Exceptions/MasaExceptionContext.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/MasaExceptionContext.cs similarity index 83% rename from src/Utils/Masa.Utils.Exceptions/MasaExceptionContext.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/MasaExceptionContext.cs index 7775958fa..22f4cba33 100644 --- a/src/Utils/Masa.Utils.Exceptions/MasaExceptionContext.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/MasaExceptionContext.cs @@ -1,8 +1,11 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; +[ExcludeFromCodeCoverage] public class MasaExceptionContext { public IServiceProvider ServiceProvider { get; set; } @@ -33,14 +36,14 @@ internal MasaExceptionContext(Exception exception, HttpContext httpContext, ISer HttpContext = httpContext; StatusCode = (int)MasaHttpStatusCode.UserFriendlyException; ExceptionHandled = false; - ContentType = Constant.DEFAULT_HTTP_CONTENT_TYPE; + ContentType = Masa.Contrib.Exceptions.Internal.Constant.DEFAULT_HTTP_CONTENT_TYPE; ServiceProvider = serviceProvider; } public void ToResult( string message, int statusCode = (int)MasaHttpStatusCode.UserFriendlyException, - string contentType = Constant.DEFAULT_HTTP_CONTENT_TYPE) + string contentType = Masa.Contrib.Exceptions.Internal.Constant.DEFAULT_HTTP_CONTENT_TYPE) { Message = message; StatusCode = statusCode; diff --git a/src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionHandlerOptions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Options/MasaExceptionHandlerOptions.cs similarity index 64% rename from src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionHandlerOptions.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Options/MasaExceptionHandlerOptions.cs index 7f75056d0..3690f0ab0 100644 --- a/src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionHandlerOptions.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Options/MasaExceptionHandlerOptions.cs @@ -1,8 +1,11 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; +[ExcludeFromCodeCoverage] public class MasaExceptionHandlerOptions { public bool CatchAllException { get; set; } = true; @@ -11,8 +14,8 @@ public class MasaExceptionHandlerOptions internal Type? MasaExceptionHandlerType { get; private set; } - public void UseExceptionHanlder() where TExceptionHanlder : IMasaExceptionHandler + public void UseExceptionHandler() where TExceptionHandler : IMasaExceptionHandler { - MasaExceptionHandlerType = typeof(TExceptionHanlder); + MasaExceptionHandlerType = typeof(TExceptionHandler); } } diff --git a/src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionLogRelationOptions.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Options/MasaExceptionLogRelationOptions.cs similarity index 82% rename from src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionLogRelationOptions.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Options/MasaExceptionLogRelationOptions.cs index 06b7d4d1f..fe7ac8811 100644 --- a/src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionLogRelationOptions.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Options/MasaExceptionLogRelationOptions.cs @@ -1,11 +1,14 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; +[ExcludeFromCodeCoverage] public class MasaExceptionLogRelationOptions { - internal Dictionary Mappings { get; } = new(); + private Dictionary Mappings { get; } = new(); public MasaExceptionLogRelationOptions MapLogLevel(LogLevel logLevel) where TException : Exception { diff --git a/src/Utils/Masa.Utils.Exceptions/Results/DefaultExceptionResult.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Results/DefaultExceptionResult.cs similarity index 72% rename from src/Utils/Masa.Utils.Exceptions/Results/DefaultExceptionResult.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Results/DefaultExceptionResult.cs index 06999a1eb..2ea4f9c99 100644 --- a/src/Utils/Masa.Utils.Exceptions/Results/DefaultExceptionResult.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Results/DefaultExceptionResult.cs @@ -1,17 +1,20 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Microsoft.AspNetCore.Mvc; +[ExcludeFromCodeCoverage] public class DefaultExceptionResult : IActionResult { - public string? Message { get; set; } + public string Message { get; set; } public int StatusCode { get; set; } public string ContentType { get; set; } - public DefaultExceptionResult(string? message, int statusCode, string contentType) + public DefaultExceptionResult(string message, int statusCode, string contentType) { Message = message; StatusCode = statusCode; @@ -20,6 +23,6 @@ public DefaultExceptionResult(string? message, int statusCode, string contentTyp public async Task ExecuteResultAsync(ActionContext context) { - await context.HttpContext.Response.WriteTextAsync(StatusCode, Message ?? Constant.DEFAULT_EXCEPTION_MESSAGE, ContentType); + await context.HttpContext.Response.WriteTextAsync(StatusCode, Message, ContentType); } } diff --git a/src/Utils/Masa.Utils.Exceptions/Results/InternalServerErrorObjectResult.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Results/InternalServerErrorObjectResult.cs similarity index 85% rename from src/Utils/Masa.Utils.Exceptions/Results/InternalServerErrorObjectResult.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Results/InternalServerErrorObjectResult.cs index 3540a1ef0..fef783dff 100644 --- a/src/Utils/Masa.Utils.Exceptions/Results/InternalServerErrorObjectResult.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Results/InternalServerErrorObjectResult.cs @@ -1,8 +1,11 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Microsoft.AspNetCore.Mvc; +[ExcludeFromCodeCoverage] public class InternalServerErrorObjectResult : ObjectResult { public InternalServerErrorObjectResult(object obj) diff --git a/src/Utils/Masa.Utils.Exceptions/Results/UserFriendlyExceptionResult.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/Results/UserFriendlyExceptionResult.cs similarity index 89% rename from src/Utils/Masa.Utils.Exceptions/Results/UserFriendlyExceptionResult.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/Results/UserFriendlyExceptionResult.cs index 20429d2ab..34092fff2 100644 --- a/src/Utils/Masa.Utils.Exceptions/Results/UserFriendlyExceptionResult.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/Results/UserFriendlyExceptionResult.cs @@ -1,8 +1,11 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace Microsoft.AspNetCore.Mvc; +[ExcludeFromCodeCoverage] public class UserFriendlyExceptionResult : IActionResult { public string Message { get; set; } diff --git a/src/Utils/Masa.Utils.Exceptions/_Imports.cs b/src/Contrib/Exception/Masa.Contrib.Exceptions/_Imports.cs similarity index 76% rename from src/Utils/Masa.Utils.Exceptions/_Imports.cs rename to src/Contrib/Exception/Masa.Contrib.Exceptions/_Imports.cs index e2062f7ee..735a071c2 100644 --- a/src/Utils/Masa.Utils.Exceptions/_Imports.cs +++ b/src/Contrib/Exception/Masa.Contrib.Exceptions/_Imports.cs @@ -1,8 +1,9 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -global using Masa.Utils.Exceptions; -global using Masa.Utils.Exceptions.Internal; +global using Masa.BuildingBlocks.Data.Constants; +global using Masa.BuildingBlocks.Globalization.I18n; +global using Masa.Contrib.Exceptions.Internal; global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc.Filters; @@ -12,6 +13,4 @@ global using System.Diagnostics.CodeAnalysis; global using System.Net; global using System.Reflection; -global using System.Runtime.CompilerServices; -global using System.Runtime.Serialization; global using System.Text; diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/Extensions/ApplicationBuilderExtensions.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 000000000..677420d9b --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.AspNetCore.Builder; + +[ExcludeFromCodeCoverage] +public static class ApplicationBuilderExtensions +{ + private static bool _isInitialize; + + public static IApplicationBuilder UseI18n(this IApplicationBuilder app, string? defaultCulture = null) + { + if (_isInitialize) + return app; + + _isInitialize = true; + var settings = app.ApplicationServices.GetRequiredService>().Value; + + var requestLocalization = new RequestLocalizationOptions(); + + var cultures = settings.SupportedCultures.Select(x => x.Culture).ToArray(); + requestLocalization + .AddSupportedCultures(cultures) + .AddSupportedUICultures(cultures); + requestLocalization.SetDefaultCulture(!string.IsNullOrWhiteSpace(defaultCulture) ? defaultCulture : cultures.FirstOrDefault()!); + + requestLocalization.ApplyCurrentCultureToResponseHeaders = true; + app.UseRequestLocalization(requestLocalization); + return app; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/Masa.Contrib.Globalization.I18n.AspNetCore.csproj b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/Masa.Contrib.Globalization.I18n.AspNetCore.csproj new file mode 100644 index 000000000..ef26fdad6 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/Masa.Contrib.Globalization.I18n.AspNetCore.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.md b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.md new file mode 100644 index 000000000..bb745ec80 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.md @@ -0,0 +1,98 @@ +[中](README.zh-CN.md) | EN + +Provides the ability to parse and obtain Culture, and use it with [I18n](../Masa.Contrib.Globalization.I18n/README.md). Currently, there are three ways to switch languages: + +* URL parameter method: ?culture=en-US, this method has the highest priority, the format is: culture=region code +* Cookies method: call L.SetCulture (region code) method to switch +* Client browser language automatic matching: If neither of the previous two methods are set, it supports automatic matching according to the client browser language. + +## Masa.Contrib.Globalization.I18n.AspNetCore + +Example: + +``` powershell +Install-Package Masa.Contrib.Globalization.I18n.AspNetCore +``` + +### getting Started + +1. Default resource folder structure + +``` structure +- Resources + - I18n + - en-US.json + - zh-CN.json + - supportedCultures.json +``` + +* en-US.json + +``` en-US.json +{ + "Home":"Home", + "Docs":"Docs", + "Blog":"Blog", + "Team":"Team", + "Search":"Search" + "User":{ + "Name":"Name" + } +} +``` + +* zh-CN.json + +``` zh-CN.json +{ + "Home":"Home", + "Docs":"Documents", + "Blog":"Blog", + "Team":"Team", + "Search":"Search", + "User":{ + "Name":"Name" + } +} +``` + +* supportedCultures.json + +``` supportedCultures.json +[ + { + "Culture":"zh-CN", + "DisplayName":"Simplified Chinese", + "Icon": "{Replace-Your-Icon}" + }, + { + "Culture":"en-US", + "DisplayName":"English (United States)", + "Icon": "{Replace-Your-Icon}" + } +] +``` + +2. Register to use I18n, modify `Program.cs` + +``` C# +services.AddI18n(); +``` + +3. Use `Masa.Contrib.Globalization.I18n.AspNetCore` to provide the ability to parse Culture + +``` C# +app.UseI18n(); +``` + +4. How to use I18n + +* Get `II18n` from DI (**II18n** is the interface, supports getting from DI) +* Use `I18n` (**I18n** is a static class) + +Take `I18n` as an example: + +```` C# +var home = I18n.T("Home"); //Get the value of the language corresponding to the key value Home, this method call will return "Home"; +var name = I18n.T("User.Name");//Output: name (support nesting) +```` \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.zh-CN.md b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.zh-CN.md new file mode 100644 index 000000000..ef54926b6 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/README.zh-CN.md @@ -0,0 +1,98 @@ +中 | [EN](README.md) + +提供解析获取Culture的能力,配合[I18n](../Masa.Contrib.Globalization.I18n/README.zh-CN.md)来使用,目前支持三种方式进行切换语言: + +* URL 参数 方式: ?culture=en-US,此方式优先级最高,格式为:culture=区域码 +* Cookies 方式:调用 L.SetCulture(区域码) 方式切换 +* 客户端浏览器语言自动匹配:如果前面两种方式都没有设置,支持自动根据客户端浏览器语言进行匹配。 + +## Masa.Contrib.Globalization.I18n.AspNetCore + +用例: + +``` powershell +Install-Package Masa.Contrib.Globalization.I18n.AspNetCore +``` + +### 入门 + +1. 默认资源文件夹结构 + +``` structure +- Resources + - I18n + - en-US.json + - zh-CN.json + - supportedCultures.json +``` + +* en-US.json + +``` en-US.json +{ + "Home":"Home", + "Docs":"Docs", + "Blog":"Blog", + "Team":"Team", + "Search":"Search" + "User":{ + "Name":"Name" + } +} +``` + +* zh-CN.json + +``` zh-CN.json +{ + "Home":"首页", + "Docs":"文档", + "Blog":"博客", + "Team":"团队", + "Search":"搜索", + "User":{ + "Name":"名称" + } +} +``` + +* supportedCultures.json + +``` supportedCultures.json +[ + { + "Culture":"zh-CN", + "DisplayName":"中文简体", + "Icon": "{Replace-Your-Icon}" + }, + { + "Culture":"en-US", + "DisplayName":"English (United States)", + "Icon": "{Replace-Your-Icon}" + } +] +``` + +2. 注册使用I18n, 修改`Program.cs` + +``` C# +services.AddI18n(); +``` + +3. 使用`Masa.Contrib.Globalization.I18n.AspNetCore`提供解析Culture的能力 + +``` C# +app.UseI18n(); +``` + +4. 如何使用I18n + +* 从DI获取`II18n` (**II18n**是接口,支持从DI获取) +* 使用`I18n` (**I18n**是静态类) + +以`I18n`为例: + +``` C# +var home = I18n.T("Home"); //获取键值Home对应语言的值,此方法调用将返回"首页"; +var name = I18n.T("User.Name");//输出:名称(支持嵌套) +``` \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/_Imports.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/_Imports.cs new file mode 100644 index 000000000..a0039cc9f --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.AspNetCore/_Imports.cs @@ -0,0 +1,7 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.BuildingBlocks.Globalization.I18n; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using System.Diagnostics.CodeAnalysis; diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/DccI18nResourceContributor.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/DccI18nResourceContributor.cs new file mode 100644 index 000000000..4a34afd19 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/DccI18nResourceContributor.cs @@ -0,0 +1,31 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Dcc; + +public class DccI18nResourceContributor : II18nResourceContributor +{ + private readonly IConfigurationSection _configurationSection; + + public string CultureName { get; } + + public DccI18nResourceContributor( + string appId, + string configObjectPrefix, + string cultureName, + IMasaConfiguration masaConfiguration) + { + CultureName = cultureName; + + _configurationSection = masaConfiguration.ConfigurationApi.Get(appId).GetSection($"{configObjectPrefix}.{cultureName}"); + } + + public string? GetOrNull(string name) + { + if (_configurationSection.Exists()) + { + return _configurationSection.GetValue(name); + } + return null; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/DccResource.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/DccResource.cs new file mode 100644 index 000000000..032c37d79 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/DccResource.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Dcc; + +public class DccResource +{ + +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Extensions/I18nOptionsExtensions.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Extensions/I18nOptionsExtensions.cs new file mode 100644 index 000000000..b098babf2 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Extensions/I18nOptionsExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.Contrib.Globalization.I18n; + +public static class I18nOptionsExtensions +{ + [ExcludeFromCodeCoverage] + public static void UseDcc(this I18nOptions i18nOptions) + => i18nOptions.UseDcc(DccConstant.CULTURES_NAME_PREFIX); + + [ExcludeFromCodeCoverage] + public static void UseDcc( + this I18nOptions i18nOptions, + string configObjectPrefix) + => i18nOptions.UseDcc(DccConfig.AppId, configObjectPrefix); + + public static void UseDcc( + this I18nOptions i18nOptions, + string appId, + string configObjectPrefix) + { + i18nOptions.Services.Configure(options => + { + options.Resources.TryAdd(resource => + { + resource.UseDcc(appId, configObjectPrefix, i18nOptions.SupportedCultures); + }); + options.Resources.GetOrNull()?.TryAddBaseResourceTypes(); + }); + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Extensions/I18nResourceExtensions.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Extensions/I18nResourceExtensions.cs new file mode 100644 index 000000000..5ad159c6f --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Extensions/I18nResourceExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public static class I18nResourceExtensions +{ + public static I18nResource UseDcc( + this I18nResource i18nResource, + string appId, + string configObjectPrefix, + List supportedCultures) + { + var serviceProvider = MasaApp.GetServices().BuildServiceProvider(); + var masaConfiguration = serviceProvider.GetRequiredService(); + var contributors = supportedCultures + .Select(supportedCulture => new DccI18nResourceContributor(appId, configObjectPrefix, supportedCulture.Culture, masaConfiguration)).ToList(); + foreach (var contributor in contributors) + { + i18nResource.AddContributor(contributor.CultureName, contributor); + } + return i18nResource; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Internal/Constant.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Internal/Constant.cs new file mode 100644 index 000000000..375837e3e --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Internal/Constant.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Dcc.Internal; + +internal static class Constant +{ + internal const string CULTURES_NAME_PREFIX = "Culture"; +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Masa.Contrib.Globalization.I18n.Dcc.csproj b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Masa.Contrib.Globalization.I18n.Dcc.csproj new file mode 100644 index 000000000..719e86fcb --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/Masa.Contrib.Globalization.I18n.Dcc.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/_Imports.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/_Imports.cs new file mode 100644 index 000000000..28615d06c --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n.Dcc/_Imports.cs @@ -0,0 +1,12 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.BuildingBlocks.Configuration; +global using Masa.BuildingBlocks.Data; +global using Masa.BuildingBlocks.Globalization.I18n; +global using Masa.Contrib.Configuration.ConfigurationApi.Dcc; +global using Masa.Contrib.Globalization.I18n.Dcc; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using System.Diagnostics.CodeAnalysis; +global using DccConstant = Masa.Contrib.Globalization.I18n.Dcc.Internal.Constant; diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Constant.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Constant.cs new file mode 100644 index 000000000..6a283f0b8 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Constant.cs @@ -0,0 +1,21 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n; + +public static class Constant +{ + public const string SUPPORTED_CULTURES_NAME = "supportedCultures.json"; + + public static readonly string DefaultResourcePath + = Path.Combine("Resources", "I18n"); + + internal static readonly string DefaultFrameworkResourcePath + = Path.Combine(DefaultResourcePath, "Framework"); + + internal static readonly string DefaultFrameworkParameterValidationResourcePath = + Path.Combine(DefaultFrameworkResourcePath, "ParameterValidations"); + + internal static readonly string DefaultFrameworkLanguageResourcePath + = Path.Combine(DefaultFrameworkResourcePath, "Languages"); +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/EmbeddedResourceUtils.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/EmbeddedResourceUtils.cs new file mode 100644 index 000000000..0d531fd75 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/EmbeddedResourceUtils.cs @@ -0,0 +1,52 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n; + +public class EmbeddedResourceUtils +{ + private readonly List> _list; + + public EmbeddedResourceUtils(params Assembly[] assemblies) : this(assemblies.ToList()) + { + } + + public EmbeddedResourceUtils(IEnumerable assemblies) + { + _list = assemblies + .Select(assembly => new KeyValuePair(assembly, assembly.GetManifestResourceNames())) + .ToList(); + } + + public List> GetResources(string resourcesDirectory) + { + var list = new List>(); + foreach (var item in _list) + { + var data = item.Value.Where(resourceName + => resourceName.Contains(FormatResourcesDirectory(resourcesDirectory), StringComparison.OrdinalIgnoreCase)).ToArray(); + if (data.Length > 0) + { + list.Add(new KeyValuePair(item.Key, data)); + } + } + return list; + } + + public static Stream? GetStream(Assembly assembly, string fileName) => assembly.GetManifestResourceStream(fileName); + + public static string? GetCulture(string resourcesDirectory, string fileName) + { + var formatResourcesDirectory = FormatResourcesDirectory(resourcesDirectory); + var index = fileName.IndexOf(formatResourcesDirectory, StringComparison.OrdinalIgnoreCase); + if (index >= 0) + return fileName.Substring(index).Replace(formatResourcesDirectory + ".", "").Replace(".json", ""); + + return null; + } + + private static string FormatResourcesDirectory(string resourcesDirectory) + { + return resourcesDirectory.Replace(Path.DirectorySeparatorChar.ToString(), "."); + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Extensions/I18nResourceExtensions.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Extensions/I18nResourceExtensions.cs new file mode 100644 index 000000000..f046152ab --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Extensions/I18nResourceExtensions.cs @@ -0,0 +1,159 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Globalization.I18n; + +public static class I18nResourceExtensions +{ + private static IConfiguration? _configuration; + + private static IMasaConfiguration? _masaConfiguration; + + public static I18nResource AddJson( + this I18nResource resource, + string resourcesDirectory, + params CultureModel[] supportedCultures) + => resource.AddJson(resourcesDirectory, supportedCultures.ToList()); + + public static I18nResource AddJson( + this I18nResource resource, + string resourcesDirectory, + IEnumerable supportedCultures) + { + if (!resource.Assemblies.Any() && !PathUtils.ParseResourcesDirectory(ref resourcesDirectory)) + return resource; + + var resourceContributors = GetResourceContributors( + resource, + resourcesDirectory, + supportedCultures); + foreach (var resourceContributor in resourceContributors) + { + resource.AddContributor(resourceContributor.CultureName, resourceContributor); + } + return resource; + } + + private static List GetResourceContributors( + I18nResource resource, + string resourcesDirectory, + IEnumerable supportedCultures) + { + _configuration ??= MasaApp.GetServices().BuildServiceProvider().GetService(); + _masaConfiguration ??= + MasaApp.GetServices().BuildServiceProvider().GetService(); + + var services = MasaApp.GetServices(); + var useMasaConfiguration = _masaConfiguration != null; + var configuration = !resource.Assemblies.Any() ? AddJsonConfigurationSource( + services, + resourcesDirectory, + supportedCultures, + resource.ResourceType, + _configuration, + useMasaConfiguration) : + AddJsonConfigurationSourceByEmbeddedResource( + resource.Assemblies, + services, + resourcesDirectory, + supportedCultures, + resource.ResourceType, + _configuration, + useMasaConfiguration); + _configuration = configuration; + + return supportedCultures.Select(supportedCulture => (II18nResourceContributor)new LocalI18nResourceContributor + ( + resource.ResourceType, + supportedCulture.Culture, + useMasaConfiguration ? _configuration.GetSection(SectionTypes.Local.ToString()) : _configuration! + ) + ) + .ToList(); + } + + private static IConfiguration AddJsonConfigurationSource( + IServiceCollection services, + string resourcesDirectory, + IEnumerable supportedCultures, + Type resourceType, + IConfiguration? configuration, + bool useMasaConfiguration) + { + return AddJsonConfigurationSourceCore( + services, + configuration, + () => new List() + { + new MasaJsonConfigurationSource(resourceType, resourcesDirectory, supportedCultures.Select(c => c.Culture), + useMasaConfiguration) + }); + } + + private static IConfiguration AddJsonConfigurationSourceByEmbeddedResource( + IEnumerable assemblies, + IServiceCollection services, + string resourcesDirectory, + IEnumerable supportedCultures, + Type resourceType, + IConfiguration? configuration, + bool useMasaConfiguration) + { + return AddJsonConfigurationSourceCore(services, + configuration, + () => + { + var list = new List(); + var embeddedResourceUtils = new EmbeddedResourceUtils(assemblies); + var resourceData = embeddedResourceUtils.GetResources(resourcesDirectory); + foreach (var item in resourceData) + { + foreach (var fileName in item.Value) + { + var stream = EmbeddedResourceUtils.GetStream(item.Key, fileName); + if (stream == null) continue; + + var culture = EmbeddedResourceUtils.GetCulture(resourcesDirectory, fileName); + if (culture != null && + supportedCultures.Any(cul => cul.Culture.Equals(culture, StringComparison.OrdinalIgnoreCase))) + list.Add(new JsonConfigurationSourceByEmbedded(resourceType, stream, culture, useMasaConfiguration)); + } + } + return list; + }); + } + + private static IConfiguration AddJsonConfigurationSourceCore( + IServiceCollection services, + IConfiguration? configuration, + Func> func) + { + ConfigurationManager configurationManager = new(); + if (configuration == null) + { + configuration = configurationManager; + services.AddSingleton(configurationManager); + } + else if (configuration is not ConfigurationManager) + { + configurationManager.AddConfiguration(configuration); + } + else if (configuration is ConfigurationManager configurationManagerTemp) + { + configurationManager = configurationManagerTemp; + } + var configurationBuilder = new ConfigurationBuilder(); + + var jsonLocalizationConfigurationSources = func.Invoke(); + foreach (var source in jsonLocalizationConfigurationSources) + { + configurationBuilder.Add(source); + } + + var localizationConfiguration = configurationBuilder.Build(); + configurationManager.AddConfiguration(localizationConfiguration); + return configuration; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Extensions/ServiceCollectionExtensions.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..5cfe6b094 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,161 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddI18n( + this IServiceCollection services, + string languageDirectory, + string? supportCultureName = null, + Action? action = null) + => services.AddI18nByEmbedded(Array.Empty(), languageDirectory, supportCultureName, action); + + public static IServiceCollection AddI18n( + this IServiceCollection services, + Action? settingsAction = null, + Action? action = null) + => services.AddI18nByEmbedded(Array.Empty(), settingsAction, action); + + public static IServiceCollection AddI18nByEmbedded( + this IServiceCollection services, + IEnumerable assemblies, + string languageDirectory, + string? supportCultureName = null, + Action? action = null) + { + return services.AddI18nByEmbedded(assemblies, + settings => + { + settings.ResourcesDirectory = languageDirectory; + settings.SupportCultureName = supportCultureName.IsNullOrWhiteSpace() ? ContribI18nConstant.SUPPORTED_CULTURES_NAME : + supportCultureName; + }, action); + } + + public static IServiceCollection AddI18nByEmbedded( + this IServiceCollection services, + IEnumerable assemblies, + Action? settingsAction = null, + Action? action = null) + { + MasaApp.TrySetServiceCollection(services); + + var cultureSettings = AddAndGetCultureSettings(services, settingsAction); + return services + .AddI18nByFramework(cultureSettings) + .AddI18nCore(action, assemblies, cultureSettings); + } + + public static IServiceCollection TestAddI18n( + this IServiceCollection services, + string languageDirectory, + string? supportCultureName = null, + Action? action = null) + { + return services.TestAddI18n(settings => + { + settings.ResourcesDirectory = languageDirectory; + settings.SupportCultureName = supportCultureName.IsNullOrWhiteSpace() ? ContribI18nConstant.SUPPORTED_CULTURES_NAME : + supportCultureName; + settings.SupportedCultures = CultureUtils.GetSupportedCultures(settings.ResourcesDirectory, settings.SupportCultureName!); + }, action); + } + + public static IServiceCollection TestAddI18n( + this IServiceCollection services, + Action? settingsAction = null, + Action? action = null) + { + MasaApp.SetServiceCollection(services); + var cultureSettings = AddAndGetCultureSettings(services, settingsAction); + return services + .AddI18nByFramework(cultureSettings) + .AddI18nCore(action, Array.Empty(), cultureSettings); + } + + private static IServiceCollection AddI18nByFramework(this IServiceCollection services, CultureSettings languageSettings) + { + services.Configure(options => + { + var assembly = typeof(EmbeddedResourceUtils).Assembly; + options.Resources.TryAdd(resource => + { + resource.Assemblies = new[] { assembly }; + resource.AddJson(ContribI18nConstant.DefaultFrameworkResourcePath, + languageSettings.SupportedCultures); + }); + + options.Resources.TryAdd(resource => + { + resource.Assemblies = new[] { assembly }; + resource.AddJson(ContribI18nConstant.DefaultFrameworkParameterValidationResourcePath, + languageSettings.SupportedCultures); + }); + + options.Resources.TryAdd(resource => + { + resource.Assemblies = new[] { assembly }; + resource.AddJson(ContribI18nConstant.DefaultFrameworkLanguageResourcePath, + languageSettings.SupportedCultures); + }); + }); + return services; + } + + private static CultureSettings AddAndGetCultureSettings( + this IServiceCollection services, + Action? settingsAction) + { + services.Configure(settings => + { + settingsAction?.Invoke(settings); + + if (string.IsNullOrWhiteSpace(settings.ResourcesDirectory)) + settings.ResourcesDirectory = ContribI18nConstant.DefaultResourcePath; + + if (string.IsNullOrWhiteSpace(settings.SupportCultureName)) + settings.SupportCultureName = ContribI18nConstant.SUPPORTED_CULTURES_NAME; + + if (!settings.SupportedCultures.Any()) + settings.SupportedCultures = + CultureUtils.GetSupportedCultures(settings.ResourcesDirectory, settings.SupportCultureName); + }); + var serviceProvider = services.BuildServiceProvider(); + return serviceProvider.GetRequiredService>().Value; + } + + private static IServiceCollection AddI18nCore( + this IServiceCollection services, + Action? action, + IEnumerable assemblies, + CultureSettings cultureSettings) where TResource : class + { + services.AddOptions(); + services.TryAddTransient(typeof(II18n<>), typeof(I18n<>)); + services.TryAddSingleton(); + services.TryAddTransient(serviceProvider => (II18n)serviceProvider.GetRequiredService>()); + + services.Configure(options => + { + var localLanguageSettings = cultureSettings; + options.Resources.TryAdd(resource => + { + if (assemblies.Any()) resource.Assemblies = assemblies; + resource.AddJson(localLanguageSettings.ResourcesDirectory ?? ContribI18nConstant.DefaultResourcePath, + localLanguageSettings.SupportedCultures); + }); + }); + + action?.Invoke(new I18nOptions(services, cultureSettings.SupportedCultures)); + + var i18nOptions = services.BuildServiceProvider().GetRequiredService>(); + foreach (var resource in i18nOptions.Value.Resources) + I18nResourceResourceConfiguration.Resources[resource.Key] = resource.Value; + + return services; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/FileConfigurationProvider.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/FileConfigurationProvider.cs new file mode 100644 index 000000000..6871166ee --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/FileConfigurationProvider.cs @@ -0,0 +1,82 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n; + +public abstract class FileConfigurationProvider : ConfigurationProvider +{ + private readonly string _resourceTypeName; + private readonly string _languageDirectory; + private readonly IEnumerable _cultureNames; + private readonly bool _useMasaConfiguration; + private readonly Dictionary> _dictionary; + + protected FileConfigurationProvider(MasaJsonConfigurationSource configurationSource) + { + _resourceTypeName = configurationSource.ResourceType.Name; + _languageDirectory = configurationSource.LanguageDirectory; + _cultureNames = configurationSource.CultureNames; + _useMasaConfiguration = configurationSource.UseMasaConfiguration; + _dictionary = new(StringComparer.OrdinalIgnoreCase); + } + + public override void Load() + { + foreach (var cultureName in _cultureNames) + { + var configuration = InitializeConfiguration(_languageDirectory, cultureName); + _dictionary[FormatKey(cultureName)] = configuration.ConvertToDictionary(); + } + + Data = FormatData(); + } + + private string FormatKey(string cultureName) + { + string localSection = BuildingBlocks.Globalization.I18n.Constant.DEFAULT_LOCAL_SECTION; + var list = _useMasaConfiguration ? + new List + { + SectionTypes.Local.ToString(), + localSection, + _resourceTypeName, + cultureName + } : + new List + { + localSection, + _resourceTypeName, + cultureName + }; + return string.Join(ConfigurationPath.KeyDelimiter, list); + } + + private IConfiguration InitializeConfiguration(string basePath, string cultureName) + => AddFile(new ConfigurationBuilder(), basePath, cultureName).Build(); + + private Dictionary FormatData() + { + var data = new Dictionary(); + foreach (var item in _dictionary) + { + foreach (var resource in item.Value) + { + data[$"{item.Key}{ConfigurationPath.KeyDelimiter}{resource.Key}"] = resource.Value; + } + } + return data; + } + + protected abstract IConfigurationBuilder AddFile(IConfigurationBuilder configurationBuilder, string basePath, string cultureName); + + public void Initialize() + { + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); + foreach (var cultureName in _cultureNames) + { + configurationBuilder = AddFile(configurationBuilder, _languageDirectory, cultureName); + } + var configuration = configurationBuilder.Build(); + ChangeToken.OnChange(() => configuration.GetReloadToken(), Load); + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Internal/CultureUtils.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Internal/CultureUtils.cs new file mode 100644 index 000000000..581197b6b --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Internal/CultureUtils.cs @@ -0,0 +1,43 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Internal; + +internal static class CultureUtils +{ + public static List GetSupportedCultures( + string languageDirectory, + string supportCultureName) + { + int retry = 0; + int maxRetry = 10; + while (retry < maxRetry) + { + try + { + var supportCultureFilePath = Path.Combine(languageDirectory, supportCultureName); + var content = File.ReadAllText(supportCultureFilePath); + return System.Text.Json.JsonSerializer.Deserialize>(content)!; + } + catch (FileNotFoundException) + { + return new List() + { + new("en-us", "English") + }; + } + catch (IOException) + { + if (retry == maxRetry - 1) throw; + } + finally + { + retry++; + } + } + return new List() + { + new("en-us", "English") + }; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Internal/PathUtils.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Internal/PathUtils.cs new file mode 100644 index 000000000..0eb66ca72 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Internal/PathUtils.cs @@ -0,0 +1,16 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.Contrib.Globalization.I18n; + +internal static class PathUtils +{ + internal static bool ParseResourcesDirectory(ref string resourcesPath) + { + resourcesPath.CheckIsNullOrWhiteSpace(); + resourcesPath = Path.Combine(I18nResourceResourceConfiguration.BaseDirectory, resourcesPath.TrimStart("/")); + return Directory.Exists(resourcesPath); + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationProvider.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationProvider.cs new file mode 100644 index 000000000..77c3a5879 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationProvider.cs @@ -0,0 +1,24 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Json; + +public class JsonConfigurationProvider : FileConfigurationProvider +{ + public JsonConfigurationProvider(JsonConfigurationSource configurationSource) + : base(configurationSource) + { + + } + + protected override IConfigurationBuilder AddFile(IConfigurationBuilder configurationBuilder, string basePath, string cultureName) + { + var filePath = Path.Combine(basePath, cultureName + ".json"); + if (!File.Exists(filePath)) + return configurationBuilder; + + return configurationBuilder + .SetBasePath(basePath) + .AddJsonFile(cultureName + ".json", false, true); + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationProviderByEmbedded.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationProviderByEmbedded.cs new file mode 100644 index 000000000..472ceca79 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationProviderByEmbedded.cs @@ -0,0 +1,67 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Json; + +public class JsonConfigurationProviderByEmbedded : ConfigurationProvider +{ + private readonly string _resourceTypeName; + private readonly Stream _stream; + private readonly bool _useMasaConfiguration; + private Dictionary _dictionary; + private readonly ConfigurationBuilder _configurationBuilder; + private readonly string _prefix; + + public JsonConfigurationProviderByEmbedded(JsonConfigurationSourceByEmbedded sourceByEmbedded) + { + _resourceTypeName = sourceByEmbedded.ResourceType.Name; + _stream = sourceByEmbedded.Stream; + _useMasaConfiguration = sourceByEmbedded.UseMasaConfiguration; + var culture = sourceByEmbedded.Culture; + _dictionary = new(StringComparer.OrdinalIgnoreCase); + _configurationBuilder = new ConfigurationBuilder(); + _prefix = FormatKey(culture); + } + + public override void Load() + { + var configuration = new JsonStreamConfigurationSource() + { + Stream = _stream + }; + _configurationBuilder.Add(configuration); + _dictionary = _configurationBuilder.Build().ConvertToDictionary(); + + Data = FormatData(); + } + + private string FormatKey(string cultureName) + { + string localSection = BuildingBlocks.Globalization.I18n.Constant.DEFAULT_LOCAL_SECTION; + var list = _useMasaConfiguration ? + new List + { + SectionTypes.Local.ToString(), + localSection, + _resourceTypeName, + cultureName + } : + new List + { + localSection, + _resourceTypeName, + cultureName + }; + return string.Join(ConfigurationPath.KeyDelimiter, list); + } + + private Dictionary FormatData() + { + var data = new Dictionary(); + foreach (var resource in _dictionary) + { + data[$"{_prefix}{ConfigurationPath.KeyDelimiter}{resource.Key}"] = resource.Value; + } + return data; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationSource.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationSource.cs new file mode 100644 index 000000000..98430e83e --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationSource.cs @@ -0,0 +1,34 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Json; + +public class JsonConfigurationSource : IConfigurationSource +{ + public Type ResourceType { get; } + + public string LanguageDirectory { get; } + + public IEnumerable CultureNames { get; } + + public bool UseMasaConfiguration { get; } + + public JsonConfigurationSource( + Type resourceType, + string languageDirectory, + IEnumerable cultureNames, + bool useMasaConfiguration) + { + ResourceType = resourceType; + LanguageDirectory = languageDirectory; + CultureNames = cultureNames; + UseMasaConfiguration = useMasaConfiguration; + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + var configurationProvider = new JsonConfigurationProvider(this); + configurationProvider.Initialize(); + return configurationProvider; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationSourceByEmbedded.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationSourceByEmbedded.cs new file mode 100644 index 000000000..074e1277b --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Json/JsonConfigurationSourceByEmbedded.cs @@ -0,0 +1,33 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Json; + +public class JsonConfigurationSourceByEmbedded : IConfigurationSource +{ + public Type ResourceType { get; } + + public string Culture { get; } + + public Stream Stream { get; } + + public bool UseMasaConfiguration { get; } + + public JsonConfigurationSourceByEmbedded( + Type resourceType, + Stream stream, + string culture, + bool useMasaConfiguration) + { + ResourceType = resourceType; + Stream = stream; + Culture = culture; + UseMasaConfiguration = useMasaConfiguration; + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + var configurationProvider = new JsonConfigurationProviderByEmbedded(this); + return configurationProvider; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/LocalI18nResourceContributor.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/LocalI18nResourceContributor.cs new file mode 100644 index 000000000..4795367fa --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/LocalI18nResourceContributor.cs @@ -0,0 +1,41 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Microsoft.Extensions.Localization; + +public class LocalI18nResourceContributor : II18nResourceContributor +{ + /// + /// Random number for handling special keys. + /// + private static readonly string _randomNumber = Guid.NewGuid().ToString(); + + private readonly IConfiguration _configuration; + + public Type ResourceType { get; } + + public string CultureName { get; } + + public LocalI18nResourceContributor( + Type resourceType, + string cultureName, + IConfiguration configuration) + { + ResourceType = resourceType; + CultureName = cultureName; + _configuration = configuration; + } + + public string? GetOrNull(string name) + { + var section = _configuration.GetSection(Masa.BuildingBlocks.Globalization.I18n.Constant.DEFAULT_LOCAL_SECTION)?.GetSection(ResourceType.Name)?.GetSection(CultureName); + if (section != null && section.Exists()) + { + string newName = name.Replace("\\.", _randomNumber).Replace(".", ConfigurationPath.KeyDelimiter).Replace(_randomNumber, "."); + return section.GetValue(newName); + } + return null; + } +} diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Masa.Contrib.Globalization.I18n.csproj b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Masa.Contrib.Globalization.I18n.csproj new file mode 100644 index 000000000..8bf37049e --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Masa.Contrib.Globalization.I18n.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/README.md b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/README.md new file mode 100644 index 000000000..c019f0ea2 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/README.md @@ -0,0 +1,104 @@ +[中](README.zh-CN.md) | EN + +Provide [Internationalization](https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/Internationalization) capability + +## Masa.Contrib.Globalization.I18n + +Example: + +``` powershell +Install-Package Masa.Contrib.Globalization.I18n +``` + +### getting Started + +1. Default resource folder structure + +``` structure +- Resources + - I18n + - en-US.json + - zh-CN.json + - supportedCultures.json +``` + +* en-US.json + +``` en-US.json +{ + "Home":"Home", + "Docs":"Docs", + "Blog":"Blog", + "Team":"Team", + "Search":"Search" + "User":{ + "Name":"Name" + } +} +``` + +* zh-CN.json + +``` zh-CN.json +{ + "Home":"Home", + "Docs":"Documents", + "Blog":"Blog", + "Team":"Team", + "Search":"Search", + "User":{ + "Name":"Name" + } +} +``` + +* supportedCultures.json + +``` supportedCultures.json +[ + { + "Culture":"zh-CN", + "DisplayName":"Simplified Chinese", + "Icon": "{Replace-Your-Icon}" + }, + { + "Culture":"en-US", + "DisplayName":"English (United States)", + "Icon": "{Replace-Your-Icon}" + } +] +``` + +2. Register to use I18n, modify `Program.cs` + +``` C# +services.AddI18n(); +``` + +3. How to use I18n + +* Get `II18n` from DI (**II18n** is the interface, supports getting from DI) +* Use `I18n` (**I18n** is a static class) + +Take `I18n` as an example: + +``` C# +var home = I18n.T("Home");//Get the value of the language corresponding to the key value Home, this method call will return "Home"; +var name = I18n.T("User.Name");//Output: name (support nesting) +``` + +### Provided by I18n + +* SetCulture (string cultureName): Switch CurrentCulture to zh-CN, and the format of numbers, dates, etc. will also change after it is changed +* SetUiCulture (string cultureName): Switch the interface language (CurrentUICulture) to zh-CN +* GetCultureInfo (): Get CurrentCulture configuration +* GetUiCultureInfo (): Get the interface language (CurrentUICulture) +* T (string name): get the value of the parameter 'name' in the current language + * Returns the value of parameter 'name' when parameter 'name' is not configured in the current language +* T (string name, bool returnKey): get the value of the parameter 'name' in the current language + * returnKey: whether to return the value of the parameter `name` when the parameter 'name' is not configured in the current language +* string T(string name, params object[] arguments): Get the value of the parameter 'name' in the current language, and get the final result according to the incoming parameters + * arguments: parameter value, for example: '{0}' must be greater than 18, then I18n.T("Age",18) should be used + * If the parameter has a format such as number, date, etc., it will change with the change of CurrentCulture +* string T(string name, bool returnKey, params object[] arguments): Get the value of the parameter 'name' in the current language, and get the final result according to the incoming parameters +* GetLanguages(): Get all languages supported by the current system \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/README.zh-CN.md b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/README.zh-CN.md new file mode 100644 index 000000000..70524b95d --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/README.zh-CN.md @@ -0,0 +1,104 @@ +中 | [EN](README.md) + +提供[国际化](https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/Internationalization)能力 + +## Masa.Contrib.Globalization.I18n + +用例: + +``` powershell +Install-Package Masa.Contrib.Globalization.I18n +``` + +### 入门 + +1. 默认资源文件夹结构 + +``` structure +- Resources + - I18n + - en-US.json + - zh-CN.json + - supportedCultures.json +``` + +* en-US.json + +``` en-US.json +{ + "Home":"Home", + "Docs":"Docs", + "Blog":"Blog", + "Team":"Team", + "Search":"Search" + "User":{ + "Name":"Name" + } +} +``` + +* zh-CN.json + +``` zh-CN.json +{ + "Home":"首页", + "Docs":"文档", + "Blog":"博客", + "Team":"团队", + "Search":"搜索", + "User":{ + "Name":"名称" + } +} +``` + +* supportedCultures.json + +``` supportedCultures.json +[ + { + "Culture":"zh-CN", + "DisplayName":"中文简体", + "Icon": "{Replace-Your-Icon}" + }, + { + "Culture":"en-US", + "DisplayName":"English (United States)", + "Icon": "{Replace-Your-Icon}" + } +] +``` + +2. 注册使用I18n, 修改`Program.cs` + +``` C# +services.AddI18n(); +``` + +3. 如何使用I18n + +* 从DI获取`II18n` (**II18n**是接口,支持从DI获取) +* 使用`I18n` (**I18n**是静态类) + +以`I18n`为例: + +``` C# +var home = I18n.T("Home"); //获取键值Home对应语言的值,此方法调用将返回"首页"; +var name = I18n.T("User.Name");//输出:名称(支持嵌套) +``` + +### I18n提供 + +* SetCulture (string cultureName): 将CurrentCulture切换成zh-CN,它更改后数字、日期等表示格式也随之改变 +* SetUiCulture (string cultureName): 将界面语言(CurrentUICulture)切换成zh-CN +* GetCultureInfo (): 得到CurrentCulture配置 +* GetUiCultureInfo (): 得到界面语言(CurrentUICulture) +* T (string name): 得到参数'name'在当前语言下的值 + * 当参数'name'在当前语言下未配置时返回参数'name'的值 +* T (string name, bool returnKey): 得到参数'name'在当前语言下的值 + * returnKey: 当参数'name'在当前语言下未配置时,是否返回参数`name`的值 +* string T(string name, params object[] arguments): 得到参数'name'在当前语言下的值, 并根据传入参数得到最终的结果 + * arguments: 参数值, 例如: '{0}' 必须大于18, 则应该使用 I18n.T("Age",18) + * 如果参数存在数字、日期等格式,它将随着CurrentCulture的更改更改变 +* string T(string name, bool returnKey, params object[] arguments): 得到参数'name'在当前语言下的值, 并根据传入参数得到最终的结果 +* GetLanguages(): 获取当前系统支持的所有语言 \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/Languages/en-US.json b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/Languages/en-US.json new file mode 100644 index 000000000..225a21533 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/Languages/en-US.json @@ -0,0 +1,4 @@ +{ + "UIDisplayName": "English (United States)", + "zh-CN.UIDisplayName": "Chinese (Simplified)" +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/Languages/zh-CN.json b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/Languages/zh-CN.json new file mode 100644 index 000000000..3a7224488 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/Languages/zh-CN.json @@ -0,0 +1,4 @@ +{ + "UIDisplayName": "中文 (简体)", + "en-US.UIDisplayName": "英语 (美国)" +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/ParameterValidations/en-US.json b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/ParameterValidations/en-US.json new file mode 100644 index 000000000..d95b09692 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/ParameterValidations/en-US.json @@ -0,0 +1,34 @@ +{ + "MFARG0001" : "'{0}' is not a valid email address.", + "MFARG0002" : "'{0}' must be greater than or equal to '{1}'.", + "MFARG0003" : "'{0}' must be greater than '{1}'.", + "MFARG0004" : "'{0}' must be between {1} and {2} characters. You entered {3} characters.", + "MFARG0005" : "The length of '{0}' must be at least {1} characters. You entered {2} characters.", + "MFARG0006" : "The length of '{0}' must be {1} characters or fewer. You entered {2} characters.", + "MFARG0007" : "'{0}' must be less than or equal to '{1}'.", + "MFARG0008" : "'{0}' must be less than '{1}'.", + "MFARG0009" : "'{0}' must not be empty.", + "MFARG0010" : "'{0}' must not be equal to '{1}'.", + "MFARG0011" : "'{0}' must not be empty.", + "MFARG0012" : "The specified condition was not met for '{0}'.", + "MFARG0013" : "The specified condition was not met for '{0}'.", + "MFARG0014" : "'{0}' is not in the correct format.", + "MFARG0015" : "'{0}' must be equal to '{1}'.", + "MFARG0016" : "'{0}' must be {1} characters in length. You entered {2} characters.", + "MFARG0017" : "'{0}' must be between {1} and {2}. You entered {3}.", + "MFARG0018" : "'{0}' must be between {1} and {2} (exclusive). You entered {3}.", + "MFARG0019" : "'{0}' cannot be null and empty.", + "MFARG0020" : "'{0}' must not be more than {1} digits in total, with allowance for {2} decimals. {3} digits and {4} decimals were found.", + "MFARG0021" : "'{0}' must be empty.", + "MFARG0022" : "'{0}' must be empty.", + "MFARG0023" : "'{0}' has a range of values which does not include '{1}'.", + "MFARG0024" : "'{0}' must be between {1} and {2} characters.", + "MFARG0025" : "The length of '{0}' must be at least {1} characters.", + "MFARG0026" : "The length of '{0}' must be {1} characters or fewer.", + "MFARG0027" : "'{0}' must be {1} characters in length.", + "MFARG0028" : "'{0}' must be between {1} and {2}.", + "MFARG0029" : "'{0}' cannot be Null or empty collection.", + "MFARG0030" : "'{0}' cannot be Null or whitespace.", + "MFARG0031" : "'{0}' cannot contain {1}.", + "MFARG0032" : "'{0}' must be greater than or equal to '{1}' and less than or equal to '{2}'." +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/ParameterValidations/zh-CN.json b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/ParameterValidations/zh-CN.json new file mode 100644 index 000000000..d66c496e3 --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/ParameterValidations/zh-CN.json @@ -0,0 +1,34 @@ +{ + "MFARG0001" : "'{0}' 不是有效的电子邮件地址。", + "MFARG0002" : "'{0}' 必须大于或等于 '{1}'。", + "MFARG0003" : "'{0}' 必须大于 '{1}'。", + "MFARG0004" : "'{0}' 的长度必须在 {1} 到 {2} 字符,您输入了 {3} 字符。", + "MFARG0005" : "'{0}' 必须大于或等于{1}个字符。您输入了{2}个字符。", + "MFARG0006" : "'{0}' 必须小于或等于{1}个字符。您输入了{2}个字符。", + "MFARG0007" : "'{0}' 必须小于或等于 '{1}'。", + "MFARG0008" : "'{0}' 必须小于 '{1}'。", + "MFARG0009" : "'{0}' 不能为空。", + "MFARG0010" : "'{0}' 不能和 '{1}' 相等。", + "MFARG0011" : "'{0}' 不能为Null。", + "MFARG0012" : "'{0}' 不符合指定的条件。", + "MFARG0013" : "'{0}' 不符合指定的条件。", + "MFARG0014" : "'{0}' 的格式不正确。", + "MFARG0015" : "'{0}' 应该和 '{1}' 相等。", + "MFARG0016" : "'{0}' 必须是 {1} 个字符,您输入了 {2} 字符。", + "MFARG0017" : "'{0}' 必须在 {1} (包含)和 {2} (包含)之间, 您输入了 {3}。", + "MFARG0018" : "'{0}' 必须在 {1} (不包含)和 {2} (不包含)之间, 您输入了 {3}。", + "MFARG0019" : "'{0}' 不能为Null和空。", + "MFARG0020" : "'{0}' 总位数不能超过 {1} 位,其中小数部分 {2} 位。您共计输入了 {3} 位数字,其中小数部分{4} 位。", + "MFARG0021" : "'{0}' 必须为空。", + "MFARG0022" : "'{0}' 必须为Null。", + "MFARG0023" : "'{0}' 的值范围不包含 '{1}'。", + "MFARG0024" : "'{0}' 的长度必须在 {1} 到 {2} 字符。", + "MFARG0025" : "'{0}' 必须大于或等于{1}个字符。", + "MFARG0026" : "'{0}' 必须小于或等于{1}个字符。", + "MFARG0027" : "'{0}' 必须是 {1} 个字符。", + "MFARG0028" : "'{0}' 必须在 {1} (包含)和 {2} (包含)之间。", + "MFARG0029" : "'{0}' 不能为Null或空集合。", + "MFARG0030" : "'{0}' 不能为Null或空白字符。", + "MFARG0031" : "'{0}' 不能包含{1}。", + "MFARG0032" : "'{0}' 必须大于等于 '{1}' 且 小于等于 '{2}'。" +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/en-US.json b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/en-US.json new file mode 100644 index 000000000..2a3ecdada --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/en-US.json @@ -0,0 +1,3 @@ +{ + "MFSer0001": "Internal service error" +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/zh-CN.json b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/zh-CN.json new file mode 100644 index 000000000..a52be869b --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/Resources/I18n/Framework/zh-CN.json @@ -0,0 +1,3 @@ +{ + "MFSer0001": "内部服务出错" +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/_Imports.cs b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/_Imports.cs new file mode 100644 index 000000000..3cfdd90bb --- /dev/null +++ b/src/Contrib/Globalization/Masa.Contrib.Globalization.I18n/_Imports.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.BuildingBlocks.Configuration; +global using Masa.BuildingBlocks.Data; +global using Masa.BuildingBlocks.Globalization.I18n; +global using Masa.Contrib.Globalization.I18n; +global using Masa.Contrib.Globalization.I18n.Internal; +global using Masa.Contrib.Globalization.I18n.Json; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.Configuration.Json; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Localization; +global using Microsoft.Extensions.Options; +global using Microsoft.Extensions.Primitives; +global using System.Reflection; +global using ContribI18nConstant = Masa.Contrib.Globalization.I18n.Constant; +global using MasaJsonConfigurationSource = Masa.Contrib.Globalization.I18n.Json.JsonConfigurationSource; diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/I18nTest.cs b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/I18nTest.cs new file mode 100644 index 000000000..d9337b8ff --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/I18nTest.cs @@ -0,0 +1,49 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Dcc.Tests; + +[TestClass] +public class I18nTest +{ + private const string DEFAULT_RESOURCE = "Resources/I18n"; + + [TestInitialize] + public void Initialize() + { + I18nResourceResourceConfiguration.Resources = new(); + } + + [DataTestMethod] + [DataRow("zh-CN", "吉姆")] + [DataRow("en-US", "JIM")] + public void Test(string cultureName, string expectedValue) + { + var appId = "appid"; + var configObjectPrefix = "Culture"; + var key = "key"; + var services = new ServiceCollection(); + MasaApp.SetServiceCollection(services); + Mock masaConfiguration = new(); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(new List>() + { + new($"{configObjectPrefix}.{cultureName}:{key}", expectedValue) + }); + var configuration = configurationBuilder.Build(); + masaConfiguration.Setup(config => config.ConfigurationApi.Get(appId)).Returns(configuration); + services.AddSingleton(masaConfiguration.Object); + services.AddI18n(options => + { + options.ResourcesDirectory = DEFAULT_RESOURCE; + }, options => options.UseDcc(appId, configObjectPrefix)); + MasaApp.SetServiceCollection(services); + + var serviceProvider = services.BuildServiceProvider(); + var i18n = serviceProvider.GetService(); + Assert.IsNotNull(i18n); + i18n.SetUiCulture(cultureName); + var value = i18n.T(key); + Assert.AreEqual(expectedValue, value); + } +} diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests.csproj b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests.csproj new file mode 100644 index 000000000..03500a563 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests.csproj @@ -0,0 +1,35 @@ + + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + Always + + + + diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/en-US.json b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/en-US.json new file mode 100644 index 000000000..a297596b1 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/en-US.json @@ -0,0 +1,3 @@ +{ + "Name": "Jim" +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/supportedCultures.json b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/supportedCultures.json new file mode 100644 index 000000000..f6e4fbb29 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/supportedCultures.json @@ -0,0 +1,10 @@ +[ + { + "Culture": "zh-CN", + "DisplayName": "中文简体" + }, + { + "Culture": "en-US", + "DisplayName": "English (United States)" + } +] \ No newline at end of file diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/zh-CN.json b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/zh-CN.json new file mode 100644 index 000000000..cc6cfb637 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/Resources/I18n/zh-CN.json @@ -0,0 +1,3 @@ +{ + "Name": "吉姆" +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/_Imports.cs b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/_Imports.cs new file mode 100644 index 000000000..07dd5f8d3 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Dcc.Tests/_Imports.cs @@ -0,0 +1,10 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.BuildingBlocks.Configuration; +global using Masa.BuildingBlocks.Data; +global using Masa.BuildingBlocks.Globalization.I18n; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/I18nTest.cs b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/I18nTest.cs new file mode 100644 index 000000000..3127c0160 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/I18nTest.cs @@ -0,0 +1,70 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Globalization.I18n.Tests; + +[TestClass] +public class I18nTest +{ + private const string DEFAULT_RESOURCE = "Resources/I18n"; + + [TestInitialize] + public void Initialize() + { + I18nResourceResourceConfiguration.Resources = new(); + } + + [DataTestMethod] + [DataRow("zh-CN", "吉姆")] + [DataRow("en-US", "Jim")] + public void TestLocalization(string cultureName, string expectedValue) + { + var services = new ServiceCollection(); + services.AddLogging(); + services.TestAddI18n(); + var serviceProvider = services.BuildServiceProvider(); + var i18n = serviceProvider.GetRequiredService(); + i18n.SetUiCulture(cultureName); + var value = i18n["Name"]; + Assert.AreEqual(expectedValue, value); + value = i18n.T("Name"); + Assert.AreEqual(expectedValue, value); + value = i18n["Name2"]; + Assert.AreEqual("Name2", value); + } + + [DataTestMethod] + // [DataRow("zh-CN", "吉姆")] + [DataRow("en-US", "Jim")] + public void TestLocalization2(string cultureName, string expectedValue) + { + var builder = WebApplication.CreateBuilder(); + builder.Services.TestAddI18n(DEFAULT_RESOURCE); + var serviceProvider = builder.Services.BuildServiceProvider(); + var i18n = serviceProvider.GetRequiredService(); + i18n.SetUiCulture(cultureName); + var value = i18n["Name"]; + Assert.AreEqual(expectedValue, value); + value = i18n.T("Name"); + Assert.AreEqual(expectedValue, value); + value = i18n["Name2"]; + Assert.AreEqual("Name2", value); + } + + [DataTestMethod] + [DataRow("zh-TW", "Name")] + public void TestLocalization3(string cultureName, string expectedValue) + { + var builder = WebApplication.CreateBuilder(); + builder.Services.AddI18n(DEFAULT_RESOURCE); + var serviceProvider = builder.Services.BuildServiceProvider(); + var i18n = serviceProvider.GetRequiredService(); + i18n.SetUiCulture(cultureName); + var value = i18n["Name"]; + Assert.AreEqual(expectedValue, value); + value = i18n.T("Name"); + Assert.AreEqual(expectedValue, value); + value = i18n["Name2"]; + Assert.AreEqual("Name2", value); + } +} diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Masa.Contrib.Globalization.I18n.Tests.csproj b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Masa.Contrib.Globalization.I18n.Tests.csproj new file mode 100644 index 000000000..7c7c64021 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Masa.Contrib.Globalization.I18n.Tests.csproj @@ -0,0 +1,37 @@ + + + + net6.0 + enable + false + enable + Masa.Contrib.Globalization.I18n.Tests + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + Always + + + diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/en-US.json b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/en-US.json new file mode 100644 index 000000000..d7e013916 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/en-US.json @@ -0,0 +1,6 @@ +{ + "Name": "Jim", + "User": { + "Name": "Jim2" + } +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/supportedCultures.json b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/supportedCultures.json new file mode 100644 index 000000000..f6e4fbb29 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/supportedCultures.json @@ -0,0 +1,10 @@ +[ + { + "Culture": "zh-CN", + "DisplayName": "中文简体" + }, + { + "Culture": "en-US", + "DisplayName": "English (United States)" + } +] \ No newline at end of file diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/zh-CN.json b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/zh-CN.json new file mode 100644 index 000000000..c76dfe1f6 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/Resources/I18n/zh-CN.json @@ -0,0 +1,6 @@ +{ + "Name": "吉姆", + "User": { + "Name": "Jim2" + } +} \ No newline at end of file diff --git a/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/_Imports.cs b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/_Imports.cs new file mode 100644 index 000000000..a05fd1712 --- /dev/null +++ b/src/Contrib/Globalization/Tests/Masa.Contrib.Globalization.I18n.Tests/_Imports.cs @@ -0,0 +1,7 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.BuildingBlocks.Globalization.I18n; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Contrib/RulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine.csproj b/src/Contrib/RulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine.csproj index 73d670eb7..644525a64 100644 --- a/src/Contrib/RulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine.csproj +++ b/src/Contrib/RulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine/Masa.Contrib.RulesEngine.MicrosoftRulesEngine.csproj @@ -7,8 +7,8 @@ + - diff --git a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultRequestMessage.cs b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultRequestMessage.cs index d1167e92e..b6352cdd8 100644 --- a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultRequestMessage.cs +++ b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultRequestMessage.cs @@ -31,4 +31,27 @@ protected virtual void TrySetRequestId(HttpRequestMessage requestMessage) if (requestMessage.Headers.All(h => h.Key != _requestIdKey)) requestMessage.Headers.Add(_requestIdKey, requestId.ToString()); } + + protected virtual void TrySetCulture(HttpRequestMessage requestMessage) + { + var cultures = new List<(string Key, string Value)> + { + ("c", CultureInfo.CurrentCulture.Name), + ("uic", CultureInfo.CurrentUICulture.Name) + }; + TrySetCulture(requestMessage, cultures); + } + + protected virtual void TrySetCulture(HttpRequestMessage requestMessage, List<(string Key, string Value)> cultures) + { + var name = "cookie"; + if (requestMessage.Headers.TryGetValues(name, out IEnumerable? cookieValues)) + requestMessage.Headers.Remove(name); + string value = System.Web.HttpUtility.UrlEncode(string.Join('|', cultures.Select(c => $"{c.Key}={c.Value}"))); + var cookies = cookieValues?.ToList() ?? new List(); + if (!cookies.Any(cookie => cookie.Contains(".AspNetCore.Culture=", StringComparison.OrdinalIgnoreCase))) + cookies.Add($".AspNetCore.Culture={value}"); + + requestMessage.Headers.Add(name, cookies); + } } diff --git a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultResponseMessage.cs b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultResponseMessage.cs index bca6b7120..ee5b8cc2c 100644 --- a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultResponseMessage.cs +++ b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/DefaultResponseMessage.cs @@ -17,82 +17,35 @@ public DefaultResponseMessage(IOptions options, ILogger ProcessResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) { - var responseType = typeof(TResponse); - if (response.IsSuccessStatusCode) + await ProcessResponseAsync(response, cancellationToken); + + switch (response.StatusCode) { - switch (response.StatusCode) - { - case HttpStatusCode.Accepted: - case HttpStatusCode.NoContent: - return default; - case (HttpStatusCode)MasaHttpStatusCode.UserFriendlyException: - throw new UserFriendlyException(await response.Content.ReadAsStringAsync(cancellationToken)); - default: - if (responseType == typeof(Guid) || responseType == typeof(Guid?)) - { - var content = (await response.Content.ReadAsStringAsync(cancellationToken)).Replace("\"", ""); - if (IsNullOrEmpty(content)) - return default; - - return (TResponse?)(object)Guid.Parse(content); - } - if (responseType == typeof(DateTime) || responseType == typeof(DateTime?)) - { - var content = (await response.Content.ReadAsStringAsync(cancellationToken)).Replace("\"", ""); - if (IsNullOrEmpty(content)) - return default; - - return (TResponse?)(object)DateTime.Parse(content); - } - - var actualType = Nullable.GetUnderlyingType(responseType); - - if (responseType.GetInterfaces().Any(type => type == typeof(IConvertible)) || - (actualType != null && actualType.GetInterfaces().Any(type => type == typeof(IConvertible)))) - { - var content = await response.Content.ReadAsStringAsync(cancellationToken); - if (IsNullOrEmpty(content)) - return default; - - if (actualType != null) - return (TResponse?)Convert.ChangeType(content, actualType); - - return (TResponse?)Convert.ChangeType(content, responseType); - } - - try - { - return await response.Content.ReadFromJsonAsync( - _options.JsonSerializerOptions?? MasaApp.GetJsonSerializerOptions() - , cancellationToken); - } - catch (Exception exception) - { - _logger?.LogWarning(exception, exception.Message); - ExceptionDispatchInfo.Capture(exception).Throw(); - return default; //This will never be executed, the previous line has already thrown an exception - } - } + case HttpStatusCode.Accepted: + case HttpStatusCode.NoContent: + return default; + default: + return await FormatResponseAsync(response, cancellationToken); } - - await ProcessResponseExceptionAsync(response, cancellationToken); - return default; //never executed } public async Task ProcessResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) { - if (response.IsSuccessStatusCode) + if (!response.IsSuccessStatusCode) { - switch (response.StatusCode) - { - case (HttpStatusCode)MasaHttpStatusCode.UserFriendlyException: - throw new UserFriendlyException(await response.Content.ReadAsStringAsync(cancellationToken)); - default: - return; - } + await ProcessResponseExceptionAsync(response, cancellationToken); + return; } - await ProcessResponseExceptionAsync(response, cancellationToken); + switch (response.StatusCode) + { + case (HttpStatusCode)MasaHttpStatusCode.UserFriendlyException: + throw new UserFriendlyException(await response.Content.ReadAsStringAsync(cancellationToken)); + case (HttpStatusCode)MasaHttpStatusCode.ValidatorException: + throw new MasaValidatorException(await response.Content.ReadAsStringAsync(cancellationToken)); + default: + return; + } } public async Task ProcessResponseExceptionAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) @@ -103,5 +56,75 @@ public async Task ProcessResponseExceptionAsync(HttpResponseMessage response, Ca throw new MasaException($"ReasonPhrase: {response.ReasonPhrase ?? string.Empty}, StatusCode: {response.StatusCode}"); } + private async Task FormatResponseAsync( + HttpResponseMessage response, + CancellationToken cancellationToken = default) + { + var responseType = typeof(TResponse); + if (responseType == typeof(Guid) || responseType == typeof(Guid?)) + return await FormatResponseByGuidAsync(response, cancellationToken); + + if (responseType == typeof(DateTime) || responseType == typeof(DateTime?)) + return await FormatResponseByDateTimeAsync(response, cancellationToken); + + var actualType = Nullable.GetUnderlyingType(responseType); + + if (responseType.GetInterfaces().Any(type => type == typeof(IConvertible)) || + (actualType != null && actualType.GetInterfaces().Any(type => type == typeof(IConvertible)))) + { + return await FormatResponseByValueTypeAsync(responseType, actualType, response, cancellationToken); + } + + try + { + return await response.Content.ReadFromJsonAsync( + _options.JsonSerializerOptions ?? MasaApp.GetJsonSerializerOptions() + , cancellationToken); + } + catch (Exception exception) + { + _logger?.LogWarning(exception, "{Message}", exception.Message); + ExceptionDispatchInfo.Capture(exception).Throw(); + return default; //This will never be executed, the previous line has already thrown an exception + } + } + + private static async Task FormatResponseByGuidAsync( + HttpResponseMessage response, + CancellationToken cancellationToken = default) + { + var content = (await response.Content.ReadAsStringAsync(cancellationToken)).Replace("\"", ""); + if (IsNullOrEmpty(content)) + return default; + + return (TResponse?)(object)Guid.Parse(content); + } + + private static async Task FormatResponseByDateTimeAsync(HttpResponseMessage response, + CancellationToken cancellationToken = default) + { + var content = (await response.Content.ReadAsStringAsync(cancellationToken)).Replace("\"", ""); + if (IsNullOrEmpty(content)) + return default; + + return (TResponse?)(object)DateTime.Parse(content); + } + + private static async Task FormatResponseByValueTypeAsync( + Type responseType, + Type? actualType, + HttpResponseMessage response, + CancellationToken cancellationToken = default) + { + var content = await response.Content.ReadAsStringAsync(cancellationToken); + if (IsNullOrEmpty(content)) + return default; + + if (actualType != null) + return (TResponse?)Convert.ChangeType(content, actualType); + + return (TResponse?)Convert.ChangeType(content, responseType); + } + private static bool IsNullOrEmpty(string value) => string.IsNullOrEmpty(value) || value == "null"; } diff --git a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/JsonRequestMessage.cs b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/JsonRequestMessage.cs index 3a4c698ac..8e5942756 100644 --- a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/JsonRequestMessage.cs +++ b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/JsonRequestMessage.cs @@ -18,6 +18,7 @@ public JsonRequestMessage( public virtual Task ProcessHttpRequestMessageAsync(HttpRequestMessage requestMessage) { TrySetRequestId(requestMessage); + TrySetCulture(requestMessage); return Task.FromResult(requestMessage); } diff --git a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/Masa.Contrib.Service.Caller.csproj b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/Masa.Contrib.Service.Caller.csproj index caebe44cc..51cf247b4 100644 --- a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/Masa.Contrib.Service.Caller.csproj +++ b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/Masa.Contrib.Service.Caller.csproj @@ -7,8 +7,12 @@ - - + + + + + + diff --git a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/XmlRequestMessage.cs b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/XmlRequestMessage.cs index 5790aa200..7c366cd73 100644 --- a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/XmlRequestMessage.cs +++ b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/XmlRequestMessage.cs @@ -15,6 +15,7 @@ public XmlRequestMessage( public Task ProcessHttpRequestMessageAsync(HttpRequestMessage requestMessage) { TrySetRequestId(requestMessage); + TrySetCulture(requestMessage); return Task.FromResult(requestMessage); } diff --git a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/_Imports.cs b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/_Imports.cs index f6eb18cab..60b4a2f0d 100644 --- a/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/_Imports.cs +++ b/src/Contrib/Service/Caller/Masa.Contrib.Service.Caller/_Imports.cs @@ -6,13 +6,13 @@ global using Masa.BuildingBlocks.Service.Caller.Options; global using Masa.Contrib.Service.Caller.Internal; global using Masa.Contrib.Service.Caller.Internal.Options; -global using Masa.Utils.Exceptions; global using Microsoft.AspNetCore.Http; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using System.Collections.Concurrent; +global using System.Globalization; global using System.Net; global using System.Net.Http.Json; global using System.Reflection; diff --git a/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/DefaultRequestMessageTest.cs b/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/DefaultRequestMessageTest.cs new file mode 100644 index 000000000..bac2c855e --- /dev/null +++ b/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/DefaultRequestMessageTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Service.Caller.Tests; + +[TestClass] +public class DefaultRequestMessageTest +{ + [TestMethod] + public void TestTrySetCulture() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + var requestMessage = new JsonDefaultRequestMessage(services.BuildServiceProvider()); + + var httpRequestMessage = new HttpRequestMessage(); + var value = " .AspNetCore.Culture=te"; + httpRequestMessage.Headers.Add("cookie", new List() + { + value + }); + requestMessage.TestTrySetCulture(httpRequestMessage, new List<(string Key, string Value)>() + { + ("c", "en-US"), + ("uic", "en-US") + }); + var cookies = httpRequestMessage.Headers.GetValues("cookie").ToList(); + Assert.IsTrue(cookies.Contains(value)); + } +} diff --git a/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/JsonDefaultRequestMessage.cs b/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/JsonDefaultRequestMessage.cs new file mode 100644 index 000000000..46a2f8eff --- /dev/null +++ b/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/JsonDefaultRequestMessage.cs @@ -0,0 +1,17 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Service.Caller.Tests; + +public class JsonDefaultRequestMessage : DefaultRequestMessage +{ + public JsonDefaultRequestMessage(IServiceProvider serviceProvider, IOptions? options = null) + : base(serviceProvider, options) + { + } + + public void TestTrySetCulture(HttpRequestMessage requestMessage, List<(string Key, string Value)> cultures) + { + base.TrySetCulture(requestMessage, cultures); + } +} diff --git a/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/_Imports.cs b/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/_Imports.cs index 7d230833d..6ef3ae1cd 100644 --- a/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/_Imports.cs +++ b/src/Contrib/Service/Caller/Tests/Masa.Contrib.Service.Caller.Tests/_Imports.cs @@ -8,7 +8,6 @@ global using Masa.Contrib.Service.Caller.Tests.Queries; global using Masa.Contrib.Service.Caller.Tests.Requesties; global using Masa.Contrib.Service.Caller.Tests.Response; -global using Masa.Utils.Exceptions; global using Microsoft.AspNetCore.Builder; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; diff --git a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj index 0148a036b..ca34630c7 100644 --- a/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj +++ b/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj @@ -11,10 +11,10 @@ + - diff --git a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultStorageClient.cs b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultStorageClient.cs index 00dc3b101..a73d37eb5 100644 --- a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultStorageClient.cs +++ b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultStorageClient.cs @@ -20,7 +20,7 @@ public TemporaryCredentialsResponse GetSecurityToken() { if (OptionProvider.IncompleteStsOptions) throw new ArgumentException( - $"Sts options is imcomplete, {nameof(AliyunStsOptions.RegionId)} or {nameof(Options.RoleArn)} or {nameof(Options.RoleSessionName)} cannot be empty or null"); + $"Sts options is incomplete, {nameof(AliyunStsOptions.RegionId)} or {nameof(Options.RoleArn)} or {nameof(Options.RoleSessionName)} cannot be empty or null"); return CredentialProvider.GetSecurityToken(); } diff --git a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Constant.cs similarity index 95% rename from src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs rename to src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Constant.cs index 9e26a82d3..e777f53b7 100644 --- a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs +++ b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Constant.cs @@ -3,7 +3,7 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; -internal static class Const +internal static class Constant { public const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.Storage.TemporaryCredentials"; diff --git a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs index 8b4ca44fc..a773f2ebd 100644 --- a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs +++ b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs @@ -7,9 +7,8 @@ internal static class ObjectStorageExtensions { internal static string CheckNullOrEmptyAndReturnValue(string? parameter, string parameterName) { - if (string.IsNullOrEmpty(parameter)) - throw new ArgumentException($"{parameterName} cannot be null and empty string"); + MasaArgumentException.ThrowIfNullOrWhiteSpace(parameter, parameterName); - return parameter; + return parameter!; } } diff --git a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj index 1643603e5..3a7add0f8 100644 --- a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj +++ b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs index ea920f0b8..e52dc6c69 100644 --- a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs +++ b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs @@ -15,7 +15,7 @@ public string Endpoint set => _endpoint = value?.Trim() ?? string.Empty; } - private string _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; + private string _temporaryCredentialsCacheKey = Constant.TEMPORARY_CREDENTIALS_CACHEKEY; public string TemporaryCredentialsCacheKey { diff --git a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs index 3cdff636e..5ef4403b0 100644 --- a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs +++ b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs @@ -56,7 +56,7 @@ public AliyunStsOptions(string? regionId = null) RegionId = regionId; } - public long GetDurationSeconds() => DurationSeconds ?? Const.DEFAULT_DURATION_SECONDS; + public long GetDurationSeconds() => DurationSeconds ?? Constant.DEFAULT_DURATION_SECONDS; - public long GetEarlyExpires() => EarlyExpires ?? Const.DEFAULT_EARLY_EXPIRES; + public long GetEarlyExpires() => EarlyExpires ?? Constant.DEFAULT_EARLY_EXPIRES; } diff --git a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs index 5c9c21d03..45f3c345c 100644 --- a/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs +++ b/src/Contrib/Storage/ObjectStorage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs @@ -16,10 +16,9 @@ public static class ServiceCollectionExtensions /// public static IServiceCollection AddAliyunStorage( this IServiceCollection services, - string sectionName = Const.DEFAULT_SECTION) + string sectionName = Constant.DEFAULT_SECTION) { - if (string.IsNullOrEmpty(sectionName)) - throw new ArgumentException(sectionName, nameof(sectionName)); + MasaArgumentException.ThrowIfNullOrEmpty(sectionName); services.AddConfigure($"{sectionName}{ConfigurationPath.KeyDelimiter}{nameof(AliyunStorageConfigureOptions.Storage)}"); services.AddConfigure(sectionName); @@ -108,11 +107,7 @@ private static IOptionsMonitor GetAliyunStorageCo private static void CheckAliYunStorageOptions(AliyunStorageOptions options) { - ArgumentNullException.ThrowIfNull(options, nameof(options)); - - if (options.AccessKeyId == null && options.AccessKeySecret == null) - throw new ArgumentException( - $"{nameof(options.AccessKeyId)}, {nameof(options.AccessKeySecret)} are required and cannot be empty"); + MasaArgumentException.ThrowIfNull(options); ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeyId, nameof(options.AccessKeyId)); ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeySecret, nameof(options.AccessKeySecret)); diff --git a/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/StorageTest.cs b/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/StorageTest.cs index 0cc17c9eb..df7688927 100644 --- a/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/StorageTest.cs +++ b/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/StorageTest.cs @@ -19,7 +19,7 @@ public void TestAddAliyunStorageAndNotAddConfigurationReturnClientIsNotNull() public void TestAddAliyunStorageByEmptySectionReturnThrowArgumentException() { var services = new ServiceCollection(); - Assert.ThrowsException(() => services.AddAliyunStorage(string.Empty)); + Assert.ThrowsException(() => services.AddAliyunStorage(string.Empty)); } [TestMethod] @@ -226,7 +226,7 @@ public void TestAddAliyunStorageAndNullALiYunStorageOptionsReturnThrowArgumentNu [TestMethod] public void TestAddAliyunStorageByEmptyAccessKeyIdReturnThrowArgumentNullException() { - Assert.ThrowsException(() => new AliyunStorageOptions(null!, null!, null!)); + Assert.ThrowsException(() => new AliyunStorageOptions(null!, null!, null!)); } [TestMethod] diff --git a/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs b/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs index 4861e61f6..7744fe0e6 100644 --- a/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs +++ b/src/Contrib/Storage/ObjectStorage/Tests/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs @@ -24,7 +24,7 @@ public void TestErrorParameterThrowArgumentException( string roleSessionName, string parameterName) { - Assert.ThrowsException(() => + Assert.ThrowsException(() => new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, roleArn, roleSessionName), $"{parameterName} cannot be empty"); } @@ -73,7 +73,7 @@ public void TestErrorTemporaryCredentialsCacheKeyReturnThrowArgumentException( string temporaryCredentialsCacheKeyName) { var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); - Assert.ThrowsException(() => + Assert.ThrowsException(() => options.SetTemporaryCredentialsCacheKey(temporaryCredentialsCacheKey), $"{temporaryCredentialsCacheKeyName} cannot be empty"); } diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/Utils/AttributeUtils.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/Utils/AttributeUtils.cs new file mode 100644 index 000000000..fb603a7ec --- /dev/null +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/Utils/AttributeUtils.cs @@ -0,0 +1,32 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +public static class AttributeUtils +{ + public static string? GetDescriptionByConst(string fieldName, BindingFlags? bindingFlags = null) + => GetDescriptionByConst(typeof(TClass), fieldName, bindingFlags); + + public static string? GetDescriptionByConst(Type type, string fieldName, BindingFlags? bindingFlags = null) + { + var fieldInfo = type.GetField(fieldName, bindingFlags ?? BindingFlags.Public | BindingFlags.Static); + if (fieldInfo == null) + return null; + + return GetDescriptionByField(fieldInfo); + } + + public static string? GetDescriptionByField(FieldInfo fieldInfo) + => GetFieldAttributeValue(fieldInfo, attribute => attribute.Description); + + public static TOpt? GetFieldAttributeValue( + FieldInfo fieldInfo, + Func valueSelector) + where TAttribute : Attribute + { + return fieldInfo.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() is TAttribute att ? valueSelector(att) : default; + } +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/AttributeTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/AttributeTest.cs new file mode 100644 index 000000000..14f2b141a --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/AttributeTest.cs @@ -0,0 +1,21 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.DotNet.Tests; + +[TestClass] +public class AttributeTest +{ + [TestMethod] + public void TestGetDescriptionByConst() + { + var value = AttributeUtils.GetDescriptionByConst(typeof(ErrorCode), nameof(ErrorCode.FRAMEWORK_PREFIX)); + Assert.AreEqual("Framework Prefix", value); + } +} + +public static class ErrorCode +{ + [System.ComponentModel.Description("Framework Prefix")] + public const string FRAMEWORK_PREFIX = "MF"; +} diff --git a/src/Utils/Masa.Utils.Exceptions/Extensions/ApplicationBuilderExtensions.cs b/src/Utils/Masa.Utils.Exceptions/Extensions/ApplicationBuilderExtensions.cs deleted file mode 100644 index 284ad4b5b..000000000 --- a/src/Utils/Masa.Utils.Exceptions/Extensions/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -// ReSharper disable once CheckNamespace -namespace Microsoft.AspNetCore.Builder; - -public static class ApplicationBuilderExtensions -{ - /// - /// Use localizable - /// - /// - /// - /// - [Obsolete("UseMasaExceptionHandler is recommended to use instead.")] - public static IApplicationBuilder UseMasaExceptionHandling( - this IApplicationBuilder app, - Action? exceptionHandlingOptions = null) - { - return app.UseMasaExceptionHandling(_ => - { - }, exceptionHandlingOptions); - } - - /// - /// Use localizable - /// - /// - /// - /// - /// - [Obsolete("UseMasaExceptionHandler is recommended to use instead.")] - public static IApplicationBuilder UseMasaExceptionHandling( - this IApplicationBuilder app, - Action action, - Action? exceptionHandlingOptions) - { - var option = new MasaExceptionHandlingOptions(); - exceptionHandlingOptions?.Invoke(option); - - app.UseMiddleware(Options.Create(option)); - app.UseRequestLocalization(action); - return app; - } - - /// - /// Use localizable - /// - /// - /// - /// - public static IApplicationBuilder UseMasaExceptionHandler( - this IApplicationBuilder app, - Action? exceptionHandlingOptions = null) - { - return app.UseMasaExceptionHandler(_ => - { - }, exceptionHandlingOptions); - } - - /// - /// Use localizable - /// - /// - /// - /// - /// - public static IApplicationBuilder UseMasaExceptionHandler( - this IApplicationBuilder app, - Action action, - Action? exceptionHandlingOptions) - { - var option = new MasaExceptionHandlerOptions(); - exceptionHandlingOptions?.Invoke(option); - - app.UseMiddleware(Options.Create(option)); - app.UseRequestLocalization(action); - return app; - } -} diff --git a/src/Utils/Masa.Utils.Exceptions/Handlers/ExceptionHandlingMiddleware.cs b/src/Utils/Masa.Utils.Exceptions/Handlers/ExceptionHandlingMiddleware.cs deleted file mode 100644 index a1660889c..000000000 --- a/src/Utils/Masa.Utils.Exceptions/Handlers/ExceptionHandlingMiddleware.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Builder; - -public class ExceptionHandlingMiddleware -{ - private readonly RequestDelegate _next; - private readonly ILogger? _logger; - private readonly MasaExceptionHandlingOptions _options; - - public ExceptionHandlingMiddleware( - RequestDelegate next, - ILogger? logger, - IOptions options) - { - _next = next; - _logger = logger; - _options = options.Value; - } - - public async Task InvokeAsync(HttpContext httpContext) - { - try - { - await _next(httpContext); - } - catch (Exception exception) - { - if (_options.CustomExceptionHandler is not null) - { - var handlerResult = _options.CustomExceptionHandler.Invoke(exception); - - if (handlerResult.ExceptionHandled) return; - - if (handlerResult.OverrideException is not null) exception = handlerResult.OverrideException; - } - if (exception is UserFriendlyException) - { - var message = exception.Message; - _logger?.LogError(exception, message); - await httpContext.Response.WriteTextAsync((int)MasaHttpStatusCode.UserFriendlyException, message); - } - else if (exception is MasaException || _options.CatchAllException) - { - var message = Constant.DEFAULT_EXCEPTION_MESSAGE; - _logger?.LogError(exception, message); - await httpContext.Response.WriteTextAsync((int)HttpStatusCode.InternalServerError, message); - } - else - { - throw; - } - } - } -} diff --git a/src/Utils/Masa.Utils.Exceptions/Internal/ExceptionExtensions.cs b/src/Utils/Masa.Utils.Exceptions/Internal/ExceptionExtensions.cs deleted file mode 100644 index ff05ff2a7..000000000 --- a/src/Utils/Masa.Utils.Exceptions/Internal/ExceptionExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace System; - -internal static class ExceptionExtensions -{ - public static void WriteLog( - this ILogger logger, - Exception exception, - LogLevel defaultLogLevel, - MasaExceptionLogRelationOptions logRelationOptions, - string? message = null) - { - var logLevel = logRelationOptions.GetLogLevel(exception, defaultLogLevel); - logger.Log(logLevel, exception, message ?? exception.Message); - } -} diff --git a/src/Utils/Masa.Utils.Exceptions/MasaException.cs b/src/Utils/Masa.Utils.Exceptions/MasaException.cs deleted file mode 100644 index 0afa766c7..000000000 --- a/src/Utils/Masa.Utils.Exceptions/MasaException.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace System; - -[Serializable] -public class MasaException : Exception -{ - public string? ErrorCode { get; set; } - - public object[] Parameters { get; set; } - - public MasaException() - { - } - - public MasaException(string message) - : base(message) - { - } - - public MasaException(string errorCode, params object[] parameters) - : this(null, errorCode, parameters) - { - } - - public MasaException(Exception? innerException, string errorCode, params object[] parameters) - : base(null, innerException) - { - ErrorCode = errorCode; - Parameters = parameters; - } - - public MasaException(string message, Exception? innerException) - : base(message, innerException) - { - } - - public MasaException(SerializationInfo serializationInfo, StreamingContext context) - : base(serializationInfo, context) - { - } -} diff --git a/src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionHandlingOptions.cs b/src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionHandlingOptions.cs deleted file mode 100644 index b5c69e4c7..000000000 --- a/src/Utils/Masa.Utils.Exceptions/Options/MasaExceptionHandlingOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace System; - -public class MasaExceptionHandlingOptions -{ - public bool CatchAllException { get; set; } = true; - - public Func? CustomExceptionHandler { get; set; } -} diff --git a/src/Utils/Masa.Utils.Exceptions/README.md b/src/Utils/Masa.Utils.Exceptions/README.md deleted file mode 100644 index e03c24b21..000000000 --- a/src/Utils/Masa.Utils.Exceptions/README.md +++ /dev/null @@ -1,130 +0,0 @@ -[中](README.zh-CN.md) | EN - -## Masa.Utils.Exceptions - -Provides a model for handling web application exceptions - -* Support custom handling exceptions for handling exceptions not provided by Masa -* Take over the `UserFriendlyException` exception and respond with a status code of 299 and return a friendly error message -* Handle all exceptions by default, and output `An error occur in masa framework` externally - -Example: - -``` powershell -Install-Package Masa.Utils.Exceptions -``` - -1. Modify `Program.cs` - -``` C# -app.UseMasaExceptionHandler(); -``` - -2. How to use? - -``` C# -app.MapGet("/Test", () -{ - throw new UserFriendlyException("This method is deprecated"); -} -``` - -3. Error response message, where Http status code is 299 - -``` js -axios - .get('/Test') - .then(response => { - if (response.status === 299) { - alert(response.data); - } - }) -``` - -## How to customize exception handling? - -1. By specifying `ExceptionHandler` - - ```` C# - app.UseMasaExceptionHandler(option => - { - option.CatchAllException = true;//Whether to catch all exceptions, the default is true, the default output of caught exceptions: An error occur in masa framework - - // Custom handling exceptions, similar to ExceptionFilter, can handle exception information according to the exception type, and output the response result through the ToResult method - option.ExceptionHandler = context => - { - if (context.Exception is ArgumentNullException argumentNullException) - { - context.ExceptionHandled = true; - context.Message = "Parameter cannot be empty"; - // or abbreviated as: context.ToResult("Parameter cannot be empty"); - } - }; - }); - ```` - -2. Implement the `IExceptionHandler` interface and register it with the service - - ```` C# - public class ExceptionHandler : IMasaExceptionHandler - { - private readonly ILogger _logger; - - public ExceptionHandler(ILogger logger) - { - _logger = logger; - } - - public void OnException(MasaExceptionContext context) - { - if (context.Exception is ArgumentNullException) - { - _logger.LogWarning(context.Message); - context.ToResult(context.Exception.Message); - } - } - } - builder.Services.AddSingleton(); - - app.UseMasaExceptionHandler(); - ```` - -3. Implement the `IExceptionHandler` interface and specify the use of Handler - - ```` C# - public class ExceptionHandler : IMasaExceptionHandler - { - private readonly ILogger _logger; - - public ExceptionHandler(ILogger logger) - { - _logger = logger; - } - - public void OnException(MasaExceptionContext context) - { - if (context.Exception is ArgumentNullException) - { - _logger.LogWarning(context.Message); - context.ToResult(context.Exception.Message); - } - } - } - app.UseMasaExceptionHandler(option => - { - option.UseExceptionHanlder(); - }); - ```` - -## Common problem - -The default log level of `UserFriendlyException` is `Information`, other types of exceptions are `Error` - -1. How to modify the log level of UserFriendlyException? - - ```` C# - builder.Services.Configure(options => - { - options.MapLogLevel(LogLevel.None); - }); - ```` \ No newline at end of file diff --git a/src/Utils/Masa.Utils.Exceptions/README.zh-CN.md b/src/Utils/Masa.Utils.Exceptions/README.zh-CN.md deleted file mode 100644 index cc93bb3ac..000000000 --- a/src/Utils/Masa.Utils.Exceptions/README.zh-CN.md +++ /dev/null @@ -1,130 +0,0 @@ -中 | [EN](README.md) - -## Masa.Utils.Exceptions - -提供了用于处理Web应用程序异常的模型 - -* 支持自定义处理异常,用于处理非Masa提供的异常 -* 接管`UserFriendlyException`异常,并响应状态码为299,返回友好的错误信息 -* 默认处理所有异常,并对外输出`An error occur in masa framework` - -用例: - -``` powershell -Install-Package Masa.Utils.Exceptions -``` - -1. 修改`Program.cs` - -``` C# -app.UseMasaExceptionHandler(); -``` - -2. 如何使用? - -``` C# -app.MapGet("/Test", () -{ - throw new UserFriendlyException("This method is deprecated"); -} -``` - -3. 错误响应消息,其中Http状态码为299 - -``` js -axios - .get('/Test') - .then(response => { - if (response.status === 299) { - alert(response.data); - } - }) -``` - -## 如何自定义异常处理? - -1. 通过指定`ExceptionHandler` - - ``` C# - app.UseMasaExceptionHandler(option => - { - option.CatchAllException = true;//是否捕获所有异常,默认为true,捕获到的异常默认输出:An error occur in masa framework - - // 自定义处理异常,与ExceptionFilter类似,可根据异常类型处理异常信息,并通过ToResult方法输出响应结果 - option.ExceptionHandler = context => - { - if (context.Exception is ArgumentNullException argumentNullException) - { - context.ExceptionHandled = true; - context.Message = "参数不能为空"; - // 或者简写为context.ToResult("参数不能为空"); - } - }; - }); - ``` - -2. 实现`IExceptionHandler`接口,并注册到服务中 - - ``` C# - public class ExceptionHandler : IMasaExceptionHandler - { - private readonly ILogger _logger; - - public ExceptionHandler(ILogger logger) - { - _logger = logger; - } - - public void OnException(MasaExceptionContext context) - { - if (context.Exception is ArgumentNullException) - { - _logger.LogWarning(context.Message); - context.ToResult(context.Exception.Message); - } - } - } - builder.Services.AddSingleton(); - - app.UseMasaExceptionHandler(); - ``` - -3. 实现`IExceptionHandler`接口,并指定使用Handler - - ``` C# - public class ExceptionHandler : IMasaExceptionHandler - { - private readonly ILogger _logger; - - public ExceptionHandler(ILogger logger) - { - _logger = logger; - } - - public void OnException(MasaExceptionContext context) - { - if (context.Exception is ArgumentNullException) - { - _logger.LogWarning(context.Message); - context.ToResult(context.Exception.Message); - } - } - } - app.UseMasaExceptionHandler(option => - { - option.UseExceptionHanlder(); - }); - ``` - -## 常见问题 - -默认`UserFriendlyException`的日志等级为`Information`, 其它类型异常为`Error` - -1. 如何修改UserFriendlyException的日志等级? - -``` C# -builder.Services.Configure(options => -{ - options.MapLogLevel(LogLevel.None); -}); -``` \ No newline at end of file diff --git a/src/Utils/Masa.Utils.Exceptions/UserFriendlyException.cs b/src/Utils/Masa.Utils.Exceptions/UserFriendlyException.cs deleted file mode 100644 index 1ffb0ce24..000000000 --- a/src/Utils/Masa.Utils.Exceptions/UserFriendlyException.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace System; - -[Serializable] -public class UserFriendlyException : MasaException -{ - public UserFriendlyException(string message) - : base(message) - { - } - - public UserFriendlyException(string message, Exception? innerException) - : base(message, innerException) - { - } - - public UserFriendlyException(string errorCode, params object[] parameters) - : base(errorCode, parameters) - { - } - - public UserFriendlyException(Exception? innerException, string errorCode, params object[] parameters) - : base(innerException, errorCode, parameters) - { - } -}