diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0721832 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,144 @@ +[*.cs] + +# SA1614: Element parameter documentation should have text +dotnet_diagnostic.SA1614.severity = none + +[*.cs] +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# 命名样式 + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +csharp_using_directive_placement = outside_namespace:warning +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:none +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:error +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +dotnet_diagnostic.SA1623.severity = silent +dotnet_diagnostic.SA1101.severity = none +dotnet_diagnostic.SX1101.severity = none +dotnet_diagnostic.SA1305.severity = silent +dotnet_diagnostic.SA1208.severity = silent +dotnet_diagnostic.SA1309.severity = silent +dotnet_diagnostic.SA1201.severity = silent +dotnet_diagnostic.SA1121.severity = silent +dotnet_diagnostic.SA1413.severity = silent +dotnet_diagnostic.ASP0009.severity = error +dotnet_diagnostic.IDE0009.severity = none +dotnet_diagnostic.SX1309.severity = none +dotnet_diagnostic.SX1309S.severity = none +csharp_space_around_binary_operators = before_and_after +dotnet_diagnostic.CA1305.severity = none +dotnet_diagnostic.CA2007.severity = none + +[*.vb] +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion +dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface +dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始 + +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型 +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 + +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员 +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.类型.required_modifiers = + +dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method +dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.非字段成员.required_modifiers = + +# 命名样式 + +dotnet_naming_style.以_i_开始.required_prefix = I +dotnet_naming_style.以_i_开始.required_suffix = +dotnet_naming_style.以_i_开始.word_separator = +dotnet_naming_style.以_i_开始.capitalization = pascal_case + +dotnet_naming_style.帕斯卡拼写法.required_prefix = +dotnet_naming_style.帕斯卡拼写法.required_suffix = +dotnet_naming_style.帕斯卡拼写法.word_separator = +dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case + +dotnet_naming_style.帕斯卡拼写法.required_prefix = +dotnet_naming_style.帕斯卡拼写法.required_suffix = +dotnet_naming_style.帕斯卡拼写法.word_separator = +dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case + +[*.{cs,vb}] +end_of_line = lf +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_diagnostic.CA1848.severity = none \ No newline at end of file diff --git a/src/MaomiFramework/MaomiFramework.sln b/MaomiFramework.sln similarity index 86% rename from src/MaomiFramework/MaomiFramework.sln rename to MaomiFramework.sln index dd64095..adb2089 100644 --- a/src/MaomiFramework/MaomiFramework.sln +++ b/MaomiFramework.sln @@ -8,8 +8,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maomi.Core", "framework\Mao EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{EDF4DC8D-8FA5-425B-BA01-23617A9DCE50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maomi.Core.Tests", "framework\Maomi.Core.Tests\Maomi.Core.Tests.csproj", "{53F4A041-E3B5-40D9-8BBA-3114F9D00743}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01", "01", "{3F478664-4837-4CBB-BA5A-A3C61F6668F1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo1.Application", "demo\1\Demo1.Application\Demo1.Application.csproj", "{549F9A4D-E0D6-4B77-B91E-3DDA9AD9DC07}" @@ -58,8 +56,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo8.FxConsole", "demo\8\D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maomi.Web.Core", "framework\Maomi.Web.Core\Maomi.Web.Core.csproj", "{7AFE762C-4649-4CFA-9300-AD4B0D9FA71D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maomi.I18n.Redis", "framework\Maomi.I18n.Redis\Maomi.I18n.Redis.csproj", "{49EA9318-7156-4B6B-9873-A160FB02DEE5}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo5.Lib", "demo\5\Demo5.Lib\Demo5.Lib.csproj", "{1D2BFDED-0BE1-449C-BF1C-02C4B83EA299}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo5.Console", "demo\5\Demo5.Console\Demo5.Console.csproj", "{01754174-F81E-4C91-9E4C-CFCB9796BEC9}" @@ -154,7 +150,29 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo8.ORM", "demo\8\Demo8.O EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo9.MaomiSwagger", "demo\9\Demo9.MaomiSwagger\Demo9.MaomiSwagger.csproj", "{BF1F3736-1595-461B-9B19-99BBDA229C49}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maomi.Swagger", "framework\Maomi.Swagger\Maomi.Swagger.csproj", "{E2028120-FEC3-4AA8-8248-9B3D33AE53F6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maomi.Swagger", "framework\Maomi.Swagger\Maomi.Swagger.csproj", "{E2028120-FEC3-4AA8-8248-9B3D33AE53F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maomi.I18n.AspNetCore", "framework\Maomi.I18n.AspNetCore\Maomi.I18n.AspNetCore.csproj", "{20B66C28-3D90-4680-B562-87082F607836}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo2.JsonLog", "demo\2\Demo2.JsonLog\Demo2.JsonLog.csproj", "{1BF4EAC1-17CF-44A5-9FB2-581FDCAE8EB8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maomi.I18n.Wpf", "framework\Maomi.I18n.Wpf\Maomi.I18n.Wpf.csproj", "{2D9A896E-8DD1-4DC9-B937-085DA89EA453}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MaomiCoreTests", "MaomiCoreTests", "{7A4EA04E-59AB-44BC-AC94-8E5F903DE0B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultipleModule", "tests\MaomiCoreTests\MultipleModule\MultipleModule.csproj", "{EBA92FE2-0921-4460-8DB8-A02A93B14F1B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maomi.Core.Tests", "tests\MaomiCoreTests\Maomi.Core.Tests\Maomi.Core.Tests.csproj", "{4554BEBA-5873-4301-B81B-E775D12019A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleAssembly", "tests\MaomiCoreTests\ModuleAssembly\ModuleAssembly.csproj", "{F64707DF-054B-440D-A5A2-046AD6D9309E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo5Wpf", "demo\5\Demo5Wpf\Demo5Wpf.csproj", "{8BAEC9A5-7712-42EE-8BC6-DD3333054BA8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo5.HttpApi", "demo\5\Demo5.HttpApi\Demo5.HttpApi.csproj", "{983D0342-CF22-450A-BBB8-1DD7A6847D16}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo5.GrpcApi", "demo\5\Demo5.GrpcApi\Demo5.GrpcApi.csproj", "{53BA4B8D-4A34-42F9-919D-54F3424F6ECC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "exporti18n", "demo\5\exporti18n\exporti18n.csproj", "{1A9041B6-FDA6-4C52-B285-900C0E749D2A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -166,10 +184,6 @@ Global {E8C68478-9793-46E7-B6C8-614EB78A641B}.Debug|Any CPU.Build.0 = Debug|Any CPU {E8C68478-9793-46E7-B6C8-614EB78A641B}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8C68478-9793-46E7-B6C8-614EB78A641B}.Release|Any CPU.Build.0 = Release|Any CPU - {53F4A041-E3B5-40D9-8BBA-3114F9D00743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {53F4A041-E3B5-40D9-8BBA-3114F9D00743}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53F4A041-E3B5-40D9-8BBA-3114F9D00743}.Release|Any CPU.ActiveCfg = Release|Any CPU - {53F4A041-E3B5-40D9-8BBA-3114F9D00743}.Release|Any CPU.Build.0 = Release|Any CPU {549F9A4D-E0D6-4B77-B91E-3DDA9AD9DC07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {549F9A4D-E0D6-4B77-B91E-3DDA9AD9DC07}.Debug|Any CPU.Build.0 = Debug|Any CPU {549F9A4D-E0D6-4B77-B91E-3DDA9AD9DC07}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -234,10 +248,6 @@ Global {7AFE762C-4649-4CFA-9300-AD4B0D9FA71D}.Debug|Any CPU.Build.0 = Debug|Any CPU {7AFE762C-4649-4CFA-9300-AD4B0D9FA71D}.Release|Any CPU.ActiveCfg = Release|Any CPU {7AFE762C-4649-4CFA-9300-AD4B0D9FA71D}.Release|Any CPU.Build.0 = Release|Any CPU - {49EA9318-7156-4B6B-9873-A160FB02DEE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {49EA9318-7156-4B6B-9873-A160FB02DEE5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {49EA9318-7156-4B6B-9873-A160FB02DEE5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {49EA9318-7156-4B6B-9873-A160FB02DEE5}.Release|Any CPU.Build.0 = Release|Any CPU {1D2BFDED-0BE1-449C-BF1C-02C4B83EA299}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1D2BFDED-0BE1-449C-BF1C-02C4B83EA299}.Debug|Any CPU.Build.0 = Debug|Any CPU {1D2BFDED-0BE1-449C-BF1C-02C4B83EA299}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -414,13 +424,52 @@ Global {E2028120-FEC3-4AA8-8248-9B3D33AE53F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2028120-FEC3-4AA8-8248-9B3D33AE53F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2028120-FEC3-4AA8-8248-9B3D33AE53F6}.Release|Any CPU.Build.0 = Release|Any CPU + {20B66C28-3D90-4680-B562-87082F607836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20B66C28-3D90-4680-B562-87082F607836}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20B66C28-3D90-4680-B562-87082F607836}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20B66C28-3D90-4680-B562-87082F607836}.Release|Any CPU.Build.0 = Release|Any CPU + {1BF4EAC1-17CF-44A5-9FB2-581FDCAE8EB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BF4EAC1-17CF-44A5-9FB2-581FDCAE8EB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BF4EAC1-17CF-44A5-9FB2-581FDCAE8EB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BF4EAC1-17CF-44A5-9FB2-581FDCAE8EB8}.Release|Any CPU.Build.0 = Release|Any CPU + {2D9A896E-8DD1-4DC9-B937-085DA89EA453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D9A896E-8DD1-4DC9-B937-085DA89EA453}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D9A896E-8DD1-4DC9-B937-085DA89EA453}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D9A896E-8DD1-4DC9-B937-085DA89EA453}.Release|Any CPU.Build.0 = Release|Any CPU + {EBA92FE2-0921-4460-8DB8-A02A93B14F1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBA92FE2-0921-4460-8DB8-A02A93B14F1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBA92FE2-0921-4460-8DB8-A02A93B14F1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBA92FE2-0921-4460-8DB8-A02A93B14F1B}.Release|Any CPU.Build.0 = Release|Any CPU + {4554BEBA-5873-4301-B81B-E775D12019A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4554BEBA-5873-4301-B81B-E775D12019A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4554BEBA-5873-4301-B81B-E775D12019A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4554BEBA-5873-4301-B81B-E775D12019A5}.Release|Any CPU.Build.0 = Release|Any CPU + {F64707DF-054B-440D-A5A2-046AD6D9309E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F64707DF-054B-440D-A5A2-046AD6D9309E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F64707DF-054B-440D-A5A2-046AD6D9309E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F64707DF-054B-440D-A5A2-046AD6D9309E}.Release|Any CPU.Build.0 = Release|Any CPU + {8BAEC9A5-7712-42EE-8BC6-DD3333054BA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BAEC9A5-7712-42EE-8BC6-DD3333054BA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BAEC9A5-7712-42EE-8BC6-DD3333054BA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BAEC9A5-7712-42EE-8BC6-DD3333054BA8}.Release|Any CPU.Build.0 = Release|Any CPU + {983D0342-CF22-450A-BBB8-1DD7A6847D16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {983D0342-CF22-450A-BBB8-1DD7A6847D16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {983D0342-CF22-450A-BBB8-1DD7A6847D16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {983D0342-CF22-450A-BBB8-1DD7A6847D16}.Release|Any CPU.Build.0 = Release|Any CPU + {53BA4B8D-4A34-42F9-919D-54F3424F6ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53BA4B8D-4A34-42F9-919D-54F3424F6ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53BA4B8D-4A34-42F9-919D-54F3424F6ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53BA4B8D-4A34-42F9-919D-54F3424F6ECC}.Release|Any CPU.Build.0 = Release|Any CPU + {1A9041B6-FDA6-4C52-B285-900C0E749D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A9041B6-FDA6-4C52-B285-900C0E749D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A9041B6-FDA6-4C52-B285-900C0E749D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A9041B6-FDA6-4C52-B285-900C0E749D2A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {E8C68478-9793-46E7-B6C8-614EB78A641B} = {34494A92-1E89-46E6-8504-A8558E9B43AC} - {53F4A041-E3B5-40D9-8BBA-3114F9D00743} = {34494A92-1E89-46E6-8504-A8558E9B43AC} {3F478664-4837-4CBB-BA5A-A3C61F6668F1} = {EDF4DC8D-8FA5-425B-BA01-23617A9DCE50} {549F9A4D-E0D6-4B77-B91E-3DDA9AD9DC07} = {3F478664-4837-4CBB-BA5A-A3C61F6668F1} {E032E2C0-63B1-49A8-AACE-F0C97DA3FFF8} = {3F478664-4837-4CBB-BA5A-A3C61F6668F1} @@ -445,7 +494,6 @@ Global {91B08CFC-74FD-4EFD-BC7E-0A541882B665} = {4F5E5E53-FEC7-4348-8C57-7FC9FA118BB2} {BA924397-C7F6-4612-8392-93A810964204} = {4F5E5E53-FEC7-4348-8C57-7FC9FA118BB2} {7AFE762C-4649-4CFA-9300-AD4B0D9FA71D} = {34494A92-1E89-46E6-8504-A8558E9B43AC} - {49EA9318-7156-4B6B-9873-A160FB02DEE5} = {34494A92-1E89-46E6-8504-A8558E9B43AC} {1D2BFDED-0BE1-449C-BF1C-02C4B83EA299} = {C54DE49A-ADA6-4526-845F-40BF97386890} {01754174-F81E-4C91-9E4C-CFCB9796BEC9} = {C54DE49A-ADA6-4526-845F-40BF97386890} {219581F0-DFBB-4CFD-BA54-9EE40DE9FAB7} = {C54DE49A-ADA6-4526-845F-40BF97386890} @@ -493,6 +541,17 @@ Global {B93F5E4F-EE2A-47A5-B76C-C557037B4A96} = {0E8BD1B5-6FF9-451C-9C73-52D3A45F74C5} {BF1F3736-1595-461B-9B19-99BBDA229C49} = {6AAD62A7-CE11-4F5C-B679-9A7062CF6F51} {E2028120-FEC3-4AA8-8248-9B3D33AE53F6} = {34494A92-1E89-46E6-8504-A8558E9B43AC} + {20B66C28-3D90-4680-B562-87082F607836} = {34494A92-1E89-46E6-8504-A8558E9B43AC} + {1BF4EAC1-17CF-44A5-9FB2-581FDCAE8EB8} = {6FDBFD60-1F24-416D-90DD-461CF93B67D6} + {2D9A896E-8DD1-4DC9-B937-085DA89EA453} = {34494A92-1E89-46E6-8504-A8558E9B43AC} + {7A4EA04E-59AB-44BC-AC94-8E5F903DE0B0} = {34494A92-1E89-46E6-8504-A8558E9B43AC} + {EBA92FE2-0921-4460-8DB8-A02A93B14F1B} = {7A4EA04E-59AB-44BC-AC94-8E5F903DE0B0} + {4554BEBA-5873-4301-B81B-E775D12019A5} = {7A4EA04E-59AB-44BC-AC94-8E5F903DE0B0} + {F64707DF-054B-440D-A5A2-046AD6D9309E} = {7A4EA04E-59AB-44BC-AC94-8E5F903DE0B0} + {8BAEC9A5-7712-42EE-8BC6-DD3333054BA8} = {C54DE49A-ADA6-4526-845F-40BF97386890} + {983D0342-CF22-450A-BBB8-1DD7A6847D16} = {C54DE49A-ADA6-4526-845F-40BF97386890} + {53BA4B8D-4A34-42F9-919D-54F3424F6ECC} = {C54DE49A-ADA6-4526-845F-40BF97386890} + {1A9041B6-FDA6-4C52-B285-900C0E749D2A} = {C54DE49A-ADA6-4526-845F-40BF97386890} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0912EEF8-B8DF-4911-825C-B9B65FC0A716} diff --git a/Readme.md b/Readme.md index 921be6c..27fc166 100644 --- a/Readme.md +++ b/Readme.md @@ -1,60 +1,94 @@ -# 猫咪框架 +# 文档说明 -Maomi 框架是一个简单的、简洁的开发框架,除了框架本身提供的功能之外,Maomi 还作为一个易于阅读的开源项目,能够给开发者提供设计框架的思路和代码。 +作者:痴者工良 + +文档地址:[https://maomi.whuanle.cn](https://maomi.whuanle.cn) + +仓库地址:[https://github.com/whuanle/maomi](https://github.com/whuanle/maomi) + +作者博客: + +* [https://www.whuanle.cn](https://www.whuanle.cn) +* [https://www.cnblogs.com/whuanle](https://www.cnblogs.com/whuanle) -Maomi 框架目前具有模块化和自动服务注册、多语言、事件总线、Web 四个模块。而整个解决方案中一共有 62 个项目,包括了日常部分框架的编写示例,例如怎么制作类似 dotnet-dump 的诊断工具、怎么定制日志框架以及怎么写一个日志框架、怎么使用 EMIT 写一个 AOP、怎么使用 Roslyn 写一个代码编译器、怎么设计类似 ABP 的模块化等,还包括了单元测试。 + +## 导读 + +本教程讲解 Maomi 框架中的使用方法,以及是如何设计的。 + +Maomi 框架是一个简单的、简洁的开发框架,除了框架本身提供的功能之外,Maomi 还作为一个易于阅读的开源项目,能够给开发者提供设计框架的思路和代码。 + +Maomi 框架目前具有模块化和自动服务注册、多语言、事件总线、Web 四个模块。而整个解决方案中一共有 70+ 个项目,包括了日常部分框架的编写示例,例如怎么制作类似 dotnet-dump 的诊断工具、怎么定制日志框架以及怎么写一个日志框架、怎么使用 EMIT 写一个 AOP、怎么使用 Roslyn 写一个代码编译器、怎么设计类似 ABP 的模块化等,还包括了单元测试。 如果你想从零编写一个自己的开发框架,那么 62 个项目,每个部分都是独立的,可以帮助你学习、了解怎么编写各类框架。 -关于如何使用 Maomi 框架,以及如何定制、编写常用框架,请参考文档地址:https://maomi.whuanle.cn -* [1.模块化和自动服务注册](https://maomi.whuanle.cn/1.module.html) + +## 目录 + +### 使用篇 + +* [模块化和自动服务注册](./start/1.module.md) + + > 如何使用 Maomi.Core 框架实现模块化和自动服务注册。 + +* [多语言](./start/2.i18n.md) + + > 注册 ASP.NET Core、控制台、WPF,简单易用的后端 i18n 方案。 + +* [Swagger](./start/3.swagger.md) + + > 简化 Swagger 生成,简化 Swagger 。 + +### 设计篇 + +* [1.模块化和自动服务注册](1.module.md) > 讲解 Maomi.Core 的使用方法和基本原理 -* [2.模块化和自动服务注册的设计和实现](https://maomi.whuanle.cn/2.design_module.html) +* [2.模块化和自动服务注册的设计和实现](2.design_module.md) > 讲解 Maomi.Core 是如何设计和实现,我们想开发一个框架时,怎么从设计、抽象、编码到最后实现。讲解了模块化和自动服务注册的原理,如何从零开发,最后制作 nuget 包,分发到 nuget.org 给所有人使用。 -* [3.故障诊断和日志](https://maomi.whuanle.cn/3.0.gz_log.html) +* [3.故障诊断和日志](3.0.gz_log.md) > 介绍故障诊断的一些方法,以及 .NET 中的日志抽象接口。 -* [3.1.自定义开发日志框架](https://maomi.whuanle.cn/3.1.design_log.html) +* [3.1.自定义开发日志框架](3.1.design_log.md) > 如何自己设计、开发一个日志框架。 -* [3.2. .NET 日志使用技巧](https://maomi.whuanle.cn/3.2.serilog.html) +* [3.2. .NET 日志使用技巧](3.2.serilog.md) > 非常推荐阅读,介绍了 Serilog 的配置、使用方法,介绍了生命周期作用域、属性、日志模板等相关说明,以便在程序运行时,输出非常高效的日志,为排查问题带来方便。很多开发者使用日志都很敷衍,不知道怎么利用好日志工具,那么这篇文章可以帮到你。 -* [3.3.开发 .NET 诊断工具](https://maomi.whuanle.cn/3.3.diagostics.html) +* [3.3.开发 .NET 诊断工具](3.3.diagostics.md) > 介绍一些 .NET 诊断的方法和原理,然后介绍如何开发 dotnet-trace、dotnet-counters、dotnet-dump 等这样的工具,没错,我们也可以写出这样的工具! -* [4.配置和选项](https://maomi.whuanle.cn/4.pz.html) +* [4.配置和选项](4.pz.md) > 简述了 IConfiguration 、Options 的原理和使用方法,自定义配置提供器、使用 signalR 实现一个配置中心。 -* [5.NET 中的序列化和反序列化](https://maomi.whuanle.cn/5.xlh.html) +* [5.NET 中的序列化和反序列化](5.xlh.md) > 本章的内容比较丰富,讲解了 .NET 下序列化和反序列化的一些特征、自定义配置、使用技巧,如何自定义枚举转换器、字符串转换器、时间格式转换器等,详细讲解了实现细节。最后介绍了 Utf8JsonReader 和怎么编写性能测试代码,通过 Utf8JsonReader 解析 json 的示例,让读者掌握原理,在后续章节中,还会介绍如何使用 Utf8JsonReader 实现多语言等基础能力。 -* [6.多语言](https://maomi.whuanle.cn/6.i18n.html) +* [6.多语言](6.i18n.md) > 本章内容比较丰富,首先介绍 Maomi.I18n 框架的使用方法,ASP.NET Core 是怎么识别多语言请求和使用多语言的,了解 i18n 框架需要做什么,然后开始设计抽象、编写实现代码。编写框架完毕后,还需要编写单元测试,笔者介绍了如何编写单元测试。接着介绍了如何基于 Redis 实现多语言,最后介绍如何在 nuget 包中打包多语言文件与他人共享。 -* [7.http 应用开发](https://maomi.whuanle.cn/7.http.html) +* [7.http 应用开发](7.http.md) > 本章内容详细介绍了 HttpClient 的使用方法,除了基础知识外,还包括比如请求参数、请求凭证、异常处理,接着详细介绍了 IHttpClientFactory ,包括请求拦截、请求策略(重试、超时)等技术。介绍了 Refit 工具的使用方法,如何在业务开发中使用 Refit 快速生成 http 请求代码,简化开发过程。最后介绍如何自己编写一个类似 curl 的工具,掌握使用 .NET 编写命令行工具的技术和技巧。 -* [8.事件总线框架的设计](https://maomi.whuanle.cn/8.event.html) +* [8.事件总线框架的设计](8.event.md) > 事件总线是 DDD 开发中最常用的解耦通讯手段,所以本章会带着读者从零设计一个事件总线框架,从抽象设计到编写,讲解了每个环节的原理和实现代码。事件总线中会使用到反射、委托、表达式树等技术,如果你对表达式树不了解,没关系,先照着做、按照教程学,不需要死扣技术细节,只需要掌握大体设计和开发思路即可。 -* [9.动态代码](https://maomi.whuanle.cn/9.dt.html) +* [9.动态代码](9.dt.md) > 本章内容比较丰富,讲解了 EMIT 技术和如何开发 AOP 框架,表达式树的两种使用方法、编写对象映射框架、简单的 ORM 框架,介绍 Roslyn 技术、代码生成和编译、Natasha 框架的简单使用,最后介绍了 Source Generators (简称 sg 技术)实现代码生成。 > @@ -64,10 +98,10 @@ Maomi 框架目前具有模块化和自动服务注册、多语言、事件总 > > 表达式树基础:https://ex.whuanle.cn -* [10.Web 框架定制开发](https://maomi.whuanle.cn/10.web.html) +* [10.Web 框架定制开发](10.web.md) > 本章内容比较丰富,日常开发中大家都会定制 Web 框架,以使用企业内部需求,那么本章介绍了开发中比较常见的东西,以及如何定制它,比如模型验证是怎么实现的、如何自定义模型验证器、模型验证器中使用 i18n,各种筛选器的使用方法和技巧、定制开发筛选器(Action 筛选器、资源筛选器、异常筛选器),Swagger 定制(模型类属性类型转换、接口分组、接口版本号、微服务路由后缀)等。 -* [11.对象映射框架](https://maomi.whuanle.cn/11.mapper.html) +* [11.对象映射框架](11.mapper.md) > 详细介绍了 Maomi.Mapper 的使用方法。 \ No newline at end of file diff --git a/demo/1/Demo1.Api/ApiModule.cs b/demo/1/Demo1.Api/ApiModule.cs new file mode 100644 index 0000000..9e885f5 --- /dev/null +++ b/demo/1/Demo1.Api/ApiModule.cs @@ -0,0 +1,20 @@ +using Demo1.Application; +using Maomi; + +namespace Demo1.Api; + +[InjectModule] +public class ApiModule : IModule +{ + private readonly IConfiguration _configuration; + public ApiModule(IConfiguration configuration) + { + _configuration = configuration; + } + + public void ConfigureServices(ServiceContext context) + { + var configuration = context.Configuration; + context.Services.AddCors(); + } +} diff --git a/demo/1/Demo1.Api/Controllers/IndexController.cs b/demo/1/Demo1.Api/Controllers/IndexController.cs new file mode 100644 index 0000000..c30f193 --- /dev/null +++ b/demo/1/Demo1.Api/Controllers/IndexController.cs @@ -0,0 +1,23 @@ +using Demo1.Application; +using Microsoft.AspNetCore.Mvc; + +namespace Demo1.Api.Controllers; + +[ApiController] +[Route("[controller]")] +public class IndexController : ControllerBase +{ + + private readonly IMyService _service; + + public IndexController(IMyService service) + { + _service = service; + } + + [HttpGet(Name = "sum")] + public int Get(int a, int b) + { + return _service.Sum(a, b); + } +} \ No newline at end of file diff --git a/src/MaomiFramework/demo/1/Demo1.Api/Demo1.Api.csproj b/demo/1/Demo1.Api/Demo1.Api.csproj similarity index 100% rename from src/MaomiFramework/demo/1/Demo1.Api/Demo1.Api.csproj rename to demo/1/Demo1.Api/Demo1.Api.csproj diff --git a/src/MaomiFramework/demo/1/Demo1.Api/Program.cs b/demo/1/Demo1.Api/Program.cs similarity index 100% rename from src/MaomiFramework/demo/1/Demo1.Api/Program.cs rename to demo/1/Demo1.Api/Program.cs diff --git a/src/MaomiFramework/demo/1/Demo1.Api/Properties/launchSettings.json b/demo/1/Demo1.Api/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/1/Demo1.Api/Properties/launchSettings.json rename to demo/1/Demo1.Api/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/1/Demo1.Api/appsettings.Development.json b/demo/1/Demo1.Api/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/1/Demo1.Api/appsettings.Development.json rename to demo/1/Demo1.Api/appsettings.Development.json diff --git a/src/MaomiFramework/demo/1/Demo1.Api/appsettings.json b/demo/1/Demo1.Api/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/1/Demo1.Api/appsettings.json rename to demo/1/Demo1.Api/appsettings.json diff --git a/demo/1/Demo1.Application/ApplicationModule.cs b/demo/1/Demo1.Application/ApplicationModule.cs new file mode 100644 index 0000000..d906052 --- /dev/null +++ b/demo/1/Demo1.Application/ApplicationModule.cs @@ -0,0 +1,19 @@ +using Maomi; +using Microsoft.Extensions.Configuration; + +namespace Demo1.Application; + +public class ApplicationModule : IModule +{ + // 模块类中可以使用依赖注入 + private readonly IConfiguration _configuration; + public ApplicationModule(IConfiguration configuration) + { + _configuration = configuration; + } + + public void ConfigureServices(ServiceContext services) + { + // services.Services.AddScoped(); + } +} diff --git a/src/MaomiFramework/demo/1/Demo1.Application/Demo1.Application.csproj b/demo/1/Demo1.Application/Demo1.Application.csproj similarity index 100% rename from src/MaomiFramework/demo/1/Demo1.Application/Demo1.Application.csproj rename to demo/1/Demo1.Application/Demo1.Application.csproj diff --git a/demo/1/Demo1.Application/IMyService.cs b/demo/1/Demo1.Application/IMyService.cs new file mode 100644 index 0000000..7ebfad1 --- /dev/null +++ b/demo/1/Demo1.Application/IMyService.cs @@ -0,0 +1,6 @@ +namespace Demo1.Application; + +public interface IMyService +{ + int Sum(int a, int b); +} diff --git a/demo/1/Demo1.Application/MyService.cs b/demo/1/Demo1.Application/MyService.cs new file mode 100644 index 0000000..fab5695 --- /dev/null +++ b/demo/1/Demo1.Application/MyService.cs @@ -0,0 +1,13 @@ +using Maomi; + +namespace Demo1.Application; + +//[InjectOn(ServiceLifetime.Scoped, Own = true)] +[InjectOnScoped(Own = true)] +public class MyService : IMyService +{ + public int Sum(int a, int b) + { + return a + b; + } +} diff --git a/src/MaomiFramework/demo/1/Demo1.Application/Properties/launchSettings.json b/demo/1/Demo1.Application/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/1/Demo1.Application/Properties/launchSettings.json rename to demo/1/Demo1.Application/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/1/templates/MaomiPack.csproj b/demo/1/templates/MaomiPack.csproj similarity index 100% rename from src/MaomiFramework/demo/1/templates/MaomiPack.csproj rename to demo/1/templates/MaomiPack.csproj diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Console.sln b/demo/1/templates/templates/Maomi.Console.sln similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Console.sln rename to demo/1/templates/templates/Maomi.Console.sln diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Console/ConsoleModule.cs b/demo/1/templates/templates/Maomi.Console/ConsoleModule.cs similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Console/ConsoleModule.cs rename to demo/1/templates/templates/Maomi.Console/ConsoleModule.cs diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Console/Maomi.Console.csproj b/demo/1/templates/templates/Maomi.Console/Maomi.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Console/Maomi.Console.csproj rename to demo/1/templates/templates/Maomi.Console/Maomi.Console.csproj diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Console/Program.cs b/demo/1/templates/templates/Maomi.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Console/Program.cs rename to demo/1/templates/templates/Maomi.Console/Program.cs diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/IMyService.cs b/demo/1/templates/templates/Maomi.Lib/IMyService.cs similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/IMyService.cs rename to demo/1/templates/templates/Maomi.Lib/IMyService.cs diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/LibModule.cs b/demo/1/templates/templates/Maomi.Lib/LibModule.cs similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/LibModule.cs rename to demo/1/templates/templates/Maomi.Lib/LibModule.cs diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/Maomi.Lib.csproj b/demo/1/templates/templates/Maomi.Lib/Maomi.Lib.csproj similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/Maomi.Lib.csproj rename to demo/1/templates/templates/Maomi.Lib/Maomi.Lib.csproj diff --git a/src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/MyService.cs b/demo/1/templates/templates/Maomi.Lib/MyService.cs similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/Maomi.Lib/MyService.cs rename to demo/1/templates/templates/Maomi.Lib/MyService.cs diff --git a/src/MaomiFramework/demo/1/templates/templates/template.json b/demo/1/templates/templates/template.json similarity index 100% rename from src/MaomiFramework/demo/1/templates/templates/template.json rename to demo/1/templates/templates/template.json diff --git a/src/MaomiFramework/demo/2/Demo2.AopLog/Demo2.AopLog.csproj b/demo/2/Demo2.AopLog/Demo2.AopLog.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.AopLog/Demo2.AopLog.csproj rename to demo/2/Demo2.AopLog/Demo2.AopLog.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.AopLog/Hello.cs b/demo/2/Demo2.AopLog/Hello.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.AopLog/Hello.cs rename to demo/2/Demo2.AopLog/Hello.cs diff --git a/src/MaomiFramework/demo/2/Demo2.AopLog/LogAttribute.cs b/demo/2/Demo2.AopLog/LogAttribute.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.AopLog/LogAttribute.cs rename to demo/2/Demo2.AopLog/LogAttribute.cs diff --git a/src/MaomiFramework/demo/2/Demo2.AopLog/Program.cs b/demo/2/Demo2.AopLog/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.AopLog/Program.cs rename to demo/2/Demo2.AopLog/Program.cs diff --git a/src/MaomiFramework/demo/2/Demo2.AopLog/Properties/launchSettings.json b/demo/2/Demo2.AopLog/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.AopLog/Properties/launchSettings.json rename to demo/2/Demo2.AopLog/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/2/Demo2.Api/Controllers/WeatherForecastController.cs b/demo/2/Demo2.Api/Controllers/WeatherForecastController.cs similarity index 94% rename from src/MaomiFramework/demo/2/Demo2.Api/Controllers/WeatherForecastController.cs rename to demo/2/Demo2.Api/Controllers/WeatherForecastController.cs index 21ba204..597f376 100644 --- a/src/MaomiFramework/demo/2/Demo2.Api/Controllers/WeatherForecastController.cs +++ b/demo/2/Demo2.Api/Controllers/WeatherForecastController.cs @@ -21,7 +21,7 @@ public WeatherForecastController(ILogger logger) [HttpGet(Name = "GetWeatherForecast")] public IEnumerable Get() { - _logger.LogInformation("���Դ�ӡ"); + _logger.LogInformation("请求接口"); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { diff --git a/src/MaomiFramework/demo/2/Demo2.Api/Demo2.Api.csproj b/demo/2/Demo2.Api/Demo2.Api.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Api/Demo2.Api.csproj rename to demo/2/Demo2.Api/Demo2.Api.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.Api/Program.cs b/demo/2/Demo2.Api/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Api/Program.cs rename to demo/2/Demo2.Api/Program.cs diff --git a/src/MaomiFramework/demo/2/Demo2.Api/Properties/launchSettings.json b/demo/2/Demo2.Api/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Api/Properties/launchSettings.json rename to demo/2/Demo2.Api/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/2/Demo2.Api/WeatherForecast.cs b/demo/2/Demo2.Api/WeatherForecast.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Api/WeatherForecast.cs rename to demo/2/Demo2.Api/WeatherForecast.cs diff --git a/src/MaomiFramework/demo/2/Demo2.Api/appsettings.Development.json b/demo/2/Demo2.Api/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Api/appsettings.Development.json rename to demo/2/Demo2.Api/appsettings.Development.json diff --git a/src/MaomiFramework/demo/2/Demo2.Api/appsettings.json b/demo/2/Demo2.Api/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Api/appsettings.json rename to demo/2/Demo2.Api/appsettings.json diff --git a/src/MaomiFramework/demo/2/Demo2.Console/Demo2.Console.csproj b/demo/2/Demo2.Console/Demo2.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Console/Demo2.Console.csproj rename to demo/2/Demo2.Console/Demo2.Console.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.Console/Program.cs b/demo/2/Demo2.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Console/Program.cs rename to demo/2/Demo2.Console/Program.cs diff --git a/src/MaomiFramework/demo/2/Demo2.Console/Properties/launchSettings.json b/demo/2/Demo2.Console/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Console/Properties/launchSettings.json rename to demo/2/Demo2.Console/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/2/Demo2.Console/serilog.json b/demo/2/Demo2.Console/serilog.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Console/serilog.json rename to demo/2/Demo2.Console/serilog.json diff --git a/src/MaomiFramework/demo/2/Demo2.Diagnostics/Demo2.Diagnostics.csproj b/demo/2/Demo2.Diagnostics/Demo2.Diagnostics.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Diagnostics/Demo2.Diagnostics.csproj rename to demo/2/Demo2.Diagnostics/Demo2.Diagnostics.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.Diagnostics/Program.cs b/demo/2/Demo2.Diagnostics/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Diagnostics/Program.cs rename to demo/2/Demo2.Diagnostics/Program.cs diff --git a/src/MaomiFramework/demo/2/Demo2.Diagnostics/Properties/launchSettings.json b/demo/2/Demo2.Diagnostics/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Diagnostics/Properties/launchSettings.json rename to demo/2/Demo2.Diagnostics/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/2/Demo2.Dump/Demo2.Dump.csproj b/demo/2/Demo2.Dump/Demo2.Dump.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Dump/Demo2.Dump.csproj rename to demo/2/Demo2.Dump/Demo2.Dump.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.Dump/Program.cs b/demo/2/Demo2.Dump/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.Dump/Program.cs rename to demo/2/Demo2.Dump/Program.cs diff --git a/src/MaomiFramework/demo/2/Demo2.ES/Demo2.ES.csproj b/demo/2/Demo2.ES/Demo2.ES.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.ES/Demo2.ES.csproj rename to demo/2/Demo2.ES/Demo2.ES.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.ES/Program.cs b/demo/2/Demo2.ES/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.ES/Program.cs rename to demo/2/Demo2.ES/Program.cs diff --git a/src/MaomiFramework/demo/2/Demo2.ES/Properties/launchSettings.json b/demo/2/Demo2.ES/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.ES/Properties/launchSettings.json rename to demo/2/Demo2.ES/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/2/Demo2.ESTrace/Demo2.ESTrace.csproj b/demo/2/Demo2.ESTrace/Demo2.ESTrace.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.ESTrace/Demo2.ESTrace.csproj rename to demo/2/Demo2.ESTrace/Demo2.ESTrace.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.ESTrace/Program.cs b/demo/2/Demo2.ESTrace/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.ESTrace/Program.cs rename to demo/2/Demo2.ESTrace/Program.cs diff --git a/demo/2/Demo2.JsonLog/Demo2.JsonLog.csproj b/demo/2/Demo2.JsonLog/Demo2.JsonLog.csproj new file mode 100644 index 0000000..d4f6923 --- /dev/null +++ b/demo/2/Demo2.JsonLog/Demo2.JsonLog.csproj @@ -0,0 +1,31 @@ + + + + Exe + net8.0 + enable + enable + + + + + Always + + + + + + + + + + + + + + + + + + + diff --git a/demo/2/Demo2.JsonLog/Program.cs b/demo/2/Demo2.JsonLog/Program.cs new file mode 100644 index 0000000..bde4225 --- /dev/null +++ b/demo/2/Demo2.JsonLog/Program.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Events; +using Serilog.Formatting; +using Serilog.Formatting.Compact; +using Serilog.Formatting.Json; + +public class Program +{ + static void Main() + { + IConfiguration configuration = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile(path: "serilog.json", optional: true, reloadOnChange: true) + .Build(); + if (configuration == null) + { + throw new ArgumentNullException($"未能找到 serilog.json 日志配置文件"); + } + + var loggerBuilder = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .WriteTo.Console(new CompactJsonFormatter()) + .CreateLogger(); + + var services = new ServiceCollection(); + services.AddLogging(s => + { + s.AddSerilog(loggerBuilder); + }); + + var ioc = services.BuildServiceProvider(); + + var logger = ioc.GetRequiredService>(); + logger.LogWarning("Test log {@Message}",new Dictionary() { { "A","1"},{ "B","2"} }); + } +} \ No newline at end of file diff --git a/demo/2/Demo2.JsonLog/serilog.json b/demo/2/Demo2.JsonLog/serilog.json new file mode 100644 index 0000000..fe9c36f --- /dev/null +++ b/demo/2/Demo2.JsonLog/serilog.json @@ -0,0 +1,22 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Formatting.Compact" ], + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Default": "Debug", + "Microsoft": "Debug", + "System": "Debug" + } + }, + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "" + } + }, + ] + } +} diff --git a/src/MaomiFramework/demo/2/Demo2.MyLogger.Console/Demo2.MyLogger.Console.csproj b/demo/2/Demo2.MyLogger.Console/Demo2.MyLogger.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.MyLogger.Console/Demo2.MyLogger.Console.csproj rename to demo/2/Demo2.MyLogger.Console/Demo2.MyLogger.Console.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyConsoleLogger.cs b/demo/2/Demo2.MyLogger.Console/MyConsoleLogger.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyConsoleLogger.cs rename to demo/2/Demo2.MyLogger.Console/MyConsoleLogger.cs diff --git a/src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyLoggerExtensions.cs b/demo/2/Demo2.MyLogger.Console/MyLoggerExtensions.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyLoggerExtensions.cs rename to demo/2/Demo2.MyLogger.Console/MyLoggerExtensions.cs diff --git a/src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyLoggerOptions.cs b/demo/2/Demo2.MyLogger.Console/MyLoggerOptions.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyLoggerOptions.cs rename to demo/2/Demo2.MyLogger.Console/MyLoggerOptions.cs diff --git a/src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyLoggerProvider.cs b/demo/2/Demo2.MyLogger.Console/MyLoggerProvider.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.MyLogger.Console/MyLoggerProvider.cs rename to demo/2/Demo2.MyLogger.Console/MyLoggerProvider.cs diff --git a/src/MaomiFramework/demo/2/Demo2.MyLogger.Console/Program.cs b/demo/2/Demo2.MyLogger.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.MyLogger.Console/Program.cs rename to demo/2/Demo2.MyLogger.Console/Program.cs diff --git a/src/MaomiFramework/demo/2/Demo2.ScopeLog/Demo2.ScopeLog.csproj b/demo/2/Demo2.ScopeLog/Demo2.ScopeLog.csproj similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.ScopeLog/Demo2.ScopeLog.csproj rename to demo/2/Demo2.ScopeLog/Demo2.ScopeLog.csproj diff --git a/src/MaomiFramework/demo/2/Demo2.ScopeLog/Program.cs b/demo/2/Demo2.ScopeLog/Program.cs similarity index 100% rename from src/MaomiFramework/demo/2/Demo2.ScopeLog/Program.cs rename to demo/2/Demo2.ScopeLog/Program.cs diff --git a/src/MaomiFramework/demo/3/Demo3.Api/Controllers/IndexController.cs b/demo/3/Demo3.Api/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/Controllers/IndexController.cs rename to demo/3/Demo3.Api/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/3/Demo3.Api/Demo3.Api.csproj b/demo/3/Demo3.Api/Demo3.Api.csproj similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/Demo3.Api.csproj rename to demo/3/Demo3.Api/Demo3.Api.csproj diff --git a/src/MaomiFramework/demo/3/Demo3.Api/OnlineConfigClient.cs b/demo/3/Demo3.Api/OnlineConfigClient.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/OnlineConfigClient.cs rename to demo/3/Demo3.Api/OnlineConfigClient.cs diff --git a/src/MaomiFramework/demo/3/Demo3.Api/Program.cs b/demo/3/Demo3.Api/Program.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/Program.cs rename to demo/3/Demo3.Api/Program.cs diff --git a/src/MaomiFramework/demo/3/Demo3.Api/Properties/launchSettings.json b/demo/3/Demo3.Api/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/Properties/launchSettings.json rename to demo/3/Demo3.Api/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/3/Demo3.Api/appsettings.Development.json b/demo/3/Demo3.Api/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/appsettings.Development.json rename to demo/3/Demo3.Api/appsettings.Development.json diff --git a/src/MaomiFramework/demo/3/Demo3.Api/appsettings.json b/demo/3/Demo3.Api/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/appsettings.json rename to demo/3/Demo3.Api/appsettings.json diff --git a/src/MaomiFramework/demo/3/Demo3.Api/tmp_config.json b/demo/3/Demo3.Api/tmp_config.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Api/tmp_config.json rename to demo/3/Demo3.Api/tmp_config.json diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/Controllers/ConfigController.cs b/demo/3/Demo3.ConfigCenter/Controllers/ConfigController.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/Controllers/ConfigController.cs rename to demo/3/Demo3.ConfigCenter/Controllers/ConfigController.cs diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/Demo3.ConfigCenter.csproj b/demo/3/Demo3.ConfigCenter/Demo3.ConfigCenter.csproj similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/Demo3.ConfigCenter.csproj rename to demo/3/Demo3.ConfigCenter/Demo3.ConfigCenter.csproj diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/Hubs/ConfigCenterHub.cs b/demo/3/Demo3.ConfigCenter/Hubs/ConfigCenterHub.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/Hubs/ConfigCenterHub.cs rename to demo/3/Demo3.ConfigCenter/Hubs/ConfigCenterHub.cs diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/Hubs/ConnectionInfo.cs b/demo/3/Demo3.ConfigCenter/Hubs/ConnectionInfo.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/Hubs/ConnectionInfo.cs rename to demo/3/Demo3.ConfigCenter/Hubs/ConnectionInfo.cs diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/Program.cs b/demo/3/Demo3.ConfigCenter/Program.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/Program.cs rename to demo/3/Demo3.ConfigCenter/Program.cs diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/Properties/launchSettings.json b/demo/3/Demo3.ConfigCenter/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/Properties/launchSettings.json rename to demo/3/Demo3.ConfigCenter/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/appsettings.Development.json b/demo/3/Demo3.ConfigCenter/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/appsettings.Development.json rename to demo/3/Demo3.ConfigCenter/appsettings.Development.json diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigCenter/appsettings.json b/demo/3/Demo3.ConfigCenter/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigCenter/appsettings.json rename to demo/3/Demo3.ConfigCenter/appsettings.json diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigClient/Demo3.ConfigClient.csproj b/demo/3/Demo3.ConfigClient/Demo3.ConfigClient.csproj similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigClient/Demo3.ConfigClient.csproj rename to demo/3/Demo3.ConfigClient/Demo3.ConfigClient.csproj diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigClient/Extensions.cs b/demo/3/Demo3.ConfigClient/Extensions.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigClient/Extensions.cs rename to demo/3/Demo3.ConfigClient/Extensions.cs diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigClient/OnlineConfigClient.cs b/demo/3/Demo3.ConfigClient/OnlineConfigClient.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigClient/OnlineConfigClient.cs rename to demo/3/Demo3.ConfigClient/OnlineConfigClient.cs diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigClient/OnlineConfigurationSource.cs b/demo/3/Demo3.ConfigClient/OnlineConfigurationSource.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigClient/OnlineConfigurationSource.cs rename to demo/3/Demo3.ConfigClient/OnlineConfigurationSource.cs diff --git a/src/MaomiFramework/demo/3/Demo3.ConfigClient/Program.cs b/demo/3/Demo3.ConfigClient/Program.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.ConfigClient/Program.cs rename to demo/3/Demo3.ConfigClient/Program.cs diff --git a/src/MaomiFramework/demo/3/Demo3.Console/Demo3.Console.csproj b/demo/3/Demo3.Console/Demo3.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Console/Demo3.Console.csproj rename to demo/3/Demo3.Console/Demo3.Console.csproj diff --git a/src/MaomiFramework/demo/3/Demo3.Console/Program.cs b/demo/3/Demo3.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Console/Program.cs rename to demo/3/Demo3.Console/Program.cs diff --git a/src/MaomiFramework/demo/3/Demo3.Console/Properties/launchSettings.json b/demo/3/Demo3.Console/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Console/Properties/launchSettings.json rename to demo/3/Demo3.Console/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/3/Demo3.Console/env.conf b/demo/3/Demo3.Console/env.conf similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Console/env.conf rename to demo/3/Demo3.Console/env.conf diff --git a/src/MaomiFramework/demo/3/Demo3.Options/Demo3.Options.csproj b/demo/3/Demo3.Options/Demo3.Options.csproj similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Options/Demo3.Options.csproj rename to demo/3/Demo3.Options/Demo3.Options.csproj diff --git a/src/MaomiFramework/demo/3/Demo3.Options/Program.cs b/demo/3/Demo3.Options/Program.cs similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Options/Program.cs rename to demo/3/Demo3.Options/Program.cs diff --git a/src/MaomiFramework/demo/3/Demo3.Options/test.json b/demo/3/Demo3.Options/test.json similarity index 100% rename from src/MaomiFramework/demo/3/Demo3.Options/test.json rename to demo/3/Demo3.Options/test.json diff --git a/src/MaomiFramework/demo/4/Demo4.Benchmark/Demo4.Benchmark.csproj b/demo/4/Demo4.Benchmark/Demo4.Benchmark.csproj similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Benchmark/Demo4.Benchmark.csproj rename to demo/4/Demo4.Benchmark/Demo4.Benchmark.csproj diff --git a/src/MaomiFramework/demo/4/Demo4.Benchmark/ParseJson.cs b/demo/4/Demo4.Benchmark/ParseJson.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Benchmark/ParseJson.cs rename to demo/4/Demo4.Benchmark/ParseJson.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Benchmark/Program.cs b/demo/4/Demo4.Benchmark/Program.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Benchmark/Program.cs rename to demo/4/Demo4.Benchmark/Program.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Benchmark/json/100.json b/demo/4/Demo4.Benchmark/json/100.json similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Benchmark/json/100.json rename to demo/4/Demo4.Benchmark/json/100.json diff --git a/src/MaomiFramework/demo/4/Demo4.Benchmark/json/1000.json b/demo/4/Demo4.Benchmark/json/1000.json similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Benchmark/json/1000.json rename to demo/4/Demo4.Benchmark/json/1000.json diff --git a/src/MaomiFramework/demo/4/Demo4.Benchmark/json/10000.json b/demo/4/Demo4.Benchmark/json/10000.json similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Benchmark/json/10000.json rename to demo/4/Demo4.Benchmark/json/10000.json diff --git a/src/MaomiFramework/demo/4/Demo4.Console/CustomDateTimeConverter.cs b/demo/4/Demo4.Console/CustomDateTimeConverter.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/CustomDateTimeConverter.cs rename to demo/4/Demo4.Console/CustomDateTimeConverter.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/Demo4.Console.csproj b/demo/4/Demo4.Console/Demo4.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/Demo4.Console.csproj rename to demo/4/Demo4.Console/Demo4.Console.csproj diff --git a/src/MaomiFramework/demo/4/Demo4.Console/EnumConverterAttribute.cs b/demo/4/Demo4.Console/EnumConverterAttribute.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/EnumConverterAttribute.cs rename to demo/4/Demo4.Console/EnumConverterAttribute.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/EnumStringConverter.cs b/demo/4/Demo4.Console/EnumStringConverter.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/EnumStringConverter.cs rename to demo/4/Demo4.Console/EnumStringConverter.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/EnumStringConverterFactory.cs b/demo/4/Demo4.Console/EnumStringConverterFactory.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/EnumStringConverterFactory.cs rename to demo/4/Demo4.Console/EnumStringConverterFactory.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/JsonStringToNumberConverter.cs b/demo/4/Demo4.Console/JsonStringToNumberConverter.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/JsonStringToNumberConverter.cs rename to demo/4/Demo4.Console/JsonStringToNumberConverter.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/Model.cs b/demo/4/Demo4.Console/Model.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/Model.cs rename to demo/4/Demo4.Console/Model.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/NetworkType.cs b/demo/4/Demo4.Console/NetworkType.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/NetworkType.cs rename to demo/4/Demo4.Console/NetworkType.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/Program.cs b/demo/4/Demo4.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/Program.cs rename to demo/4/Demo4.Console/Program.cs diff --git a/src/MaomiFramework/demo/4/Demo4.Console/Properties/launchSettings.json b/demo/4/Demo4.Console/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/Properties/launchSettings.json rename to demo/4/Demo4.Console/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/4/Demo4.Console/read.json b/demo/4/Demo4.Console/read.json similarity index 100% rename from src/MaomiFramework/demo/4/Demo4.Console/read.json rename to demo/4/Demo4.Console/read.json diff --git a/demo/5/Demo5.Api/BusinessException.cs b/demo/5/Demo5.Api/BusinessException.cs new file mode 100644 index 0000000..7fcadb8 --- /dev/null +++ b/demo/5/Demo5.Api/BusinessException.cs @@ -0,0 +1,55 @@ +namespace Demo5.Api; + +/// +/// 业务异常类型. +/// +public class BusinessException : Exception +{ + /// + /// 异常代码. + /// + public int? Code { get; set; } + + /// + /// 异常描述. + /// + public string? Details { get; set; } + + /// + /// 参数. + /// + public object[]? Paramters { get; set; } + + /// + /// 异常级别. + /// + public LogLevel LogLevel { get; set; } = LogLevel.Error; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public BusinessException( + int? code = null, + string? message = null, + params object[] paramters) + : base(message) + { + Code = code; + Paramters = paramters; + } + + /// + /// 记录额外的异常信息. + /// + /// + /// + /// . + public BusinessException WithData(string name, object value) + { + Data[name] = value; + return this; + } +} \ No newline at end of file diff --git a/src/MaomiFramework/demo/5/Demo5.Api/Demo5.Api.csproj b/demo/5/Demo5.Api/Demo5.Api.csproj similarity index 83% rename from src/MaomiFramework/demo/5/Demo5.Api/Demo5.Api.csproj rename to demo/5/Demo5.Api/Demo5.Api.csproj index 256d5fa..e59ff4f 100644 --- a/src/MaomiFramework/demo/5/Demo5.Api/Demo5.Api.csproj +++ b/demo/5/Demo5.Api/Demo5.Api.csproj @@ -13,6 +13,7 @@ + diff --git a/src/MaomiFramework/demo/5/Demo5.Api/Program.cs b/demo/5/Demo5.Api/Program.cs similarity index 84% rename from src/MaomiFramework/demo/5/Demo5.Api/Program.cs rename to demo/5/Demo5.Api/Program.cs index fcaa154..95ad0a2 100644 --- a/src/MaomiFramework/demo/5/Demo5.Api/Program.cs +++ b/demo/5/Demo5.Api/Program.cs @@ -1,5 +1,4 @@ using Maomi.I18n; -using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.Localization; var builder = WebApplication.CreateBuilder(args); @@ -8,13 +7,15 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddHttpContextAccessor(); + // 添加 i18n 多语言支持 -builder.Services.AddI18n(defaultLanguage: "zh-CN"); +builder.Services.AddI18nAspNetCore(defaultLanguage: "zh-CN"); // 设置多语言来源-json builder.Services.AddI18nResource(option => { var basePath = "i18n"; - option.AddJson(basePath); + option.AddJsonDirectory(basePath); }); var app = builder.Build(); diff --git a/src/MaomiFramework/demo/5/Demo5.Api/Properties/launchSettings.json b/demo/5/Demo5.Api/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Api/Properties/launchSettings.json rename to demo/5/Demo5.Api/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/5/Demo5.Api/appsettings.Development.json b/demo/5/Demo5.Api/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Api/appsettings.Development.json rename to demo/5/Demo5.Api/appsettings.Development.json diff --git a/src/MaomiFramework/demo/5/Demo5.Api/appsettings.json b/demo/5/Demo5.Api/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Api/appsettings.json rename to demo/5/Demo5.Api/appsettings.json diff --git a/src/MaomiFramework/demo/5/Demo5.Api/i18n/Demo5.Api/en-US.json b/demo/5/Demo5.Api/i18n/en-US.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Api/i18n/Demo5.Api/en-US.json rename to demo/5/Demo5.Api/i18n/en-US.json diff --git a/src/MaomiFramework/demo/5/Demo5.Api/i18n/Demo5.Api/zh-CN.json b/demo/5/Demo5.Api/i18n/zh-CN.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Api/i18n/Demo5.Api/zh-CN.json rename to demo/5/Demo5.Api/i18n/zh-CN.json diff --git a/src/MaomiFramework/demo/5/Demo5.Console/Demo5.Console.csproj b/demo/5/Demo5.Console/Demo5.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Console/Demo5.Console.csproj rename to demo/5/Demo5.Console/Demo5.Console.csproj diff --git a/src/MaomiFramework/demo/5/Demo5.Console/Program.cs b/demo/5/Demo5.Console/Program.cs similarity index 54% rename from src/MaomiFramework/demo/5/Demo5.Console/Program.cs rename to demo/5/Demo5.Console/Program.cs index 8386a48..b3b1401 100644 --- a/src/MaomiFramework/demo/5/Demo5.Console/Program.cs +++ b/demo/5/Demo5.Console/Program.cs @@ -2,7 +2,6 @@ using Maomi.I18n; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; -using System.Globalization; public class Program { @@ -12,7 +11,8 @@ static void Main() ioc.AddI18n("zh-CN"); ioc.AddI18nResource(options => { - options.AddJson("i18n"); + options.ParseDirectory("i18n"); + options.AddJsonDirectory("i18n"); }); ioc.AddLib(); @@ -20,7 +20,7 @@ static void Main() var services = ioc.BuildServiceProvider(); // 手动设置当前请求语言 - using (var c = new CultureInfoScope("en-US")) + using (var c = new I18nScope("en-US")) { var l1 = services.GetRequiredService>(); var l2 = services.GetRequiredService>(); @@ -29,5 +29,16 @@ static void Main() Console.WriteLine(s1); Console.WriteLine(s2); } - } + + // 手动设置当前请求语言 + using (var c = new I18nScope("zh-CN")) + { + var l1 = services.GetRequiredService>(); + var l2 = services.GetRequiredService>(); + var s1 = l1["test"]; + var s2 = l2["test"]; + Console.WriteLine(s1); + Console.WriteLine(s2); + } + } } \ No newline at end of file diff --git a/src/MaomiFramework/demo/5/Demo5.Console/Properties/launchSettings.json b/demo/5/Demo5.Console/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Console/Properties/launchSettings.json rename to demo/5/Demo5.Console/Properties/launchSettings.json diff --git a/demo/5/Demo5.Console/i18n/Demo5.Console/en-US.json b/demo/5/Demo5.Console/i18n/Demo5.Console/en-US.json new file mode 100644 index 0000000..e26ed15 --- /dev/null +++ b/demo/5/Demo5.Console/i18n/Demo5.Console/en-US.json @@ -0,0 +1,3 @@ +{ + "test": "console en-US" +} \ No newline at end of file diff --git a/demo/5/Demo5.Console/i18n/Demo5.Console/zh-CN.json b/demo/5/Demo5.Console/i18n/Demo5.Console/zh-CN.json new file mode 100644 index 0000000..9813a91 --- /dev/null +++ b/demo/5/Demo5.Console/i18n/Demo5.Console/zh-CN.json @@ -0,0 +1,3 @@ +{ + "test": "console zh-CN" +} \ No newline at end of file diff --git a/demo/5/Demo5.GrpcApi/Demo5.GrpcApi.csproj b/demo/5/Demo5.GrpcApi/Demo5.GrpcApi.csproj new file mode 100644 index 0000000..69258bb --- /dev/null +++ b/demo/5/Demo5.GrpcApi/Demo5.GrpcApi.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/demo/5/Demo5.GrpcApi/Program.cs b/demo/5/Demo5.GrpcApi/Program.cs new file mode 100644 index 0000000..65b9cc0 --- /dev/null +++ b/demo/5/Demo5.GrpcApi/Program.cs @@ -0,0 +1,31 @@ +using Demo5.GrpcApi.Services; +using Maomi.I18n; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHttpContextAccessor(); + +// i18n ֧ +builder.Services.AddI18nAspNetCore(defaultLanguage: "zh-CN"); + +// öԴ-json +builder.Services.AddI18nResource(option => +{ + var basePath = "i18n"; + option.AddJsonDirectory(basePath); +}); + +builder.Services.AddGrpc(o => +{ + o.Interceptors.Add(); +}); + + +var app = builder.Build(); + +app.UseI18n(); + +app.MapGrpcService(); +app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + +app.Run(); diff --git a/demo/5/Demo5.GrpcApi/Properties/launchSettings.json b/demo/5/Demo5.GrpcApi/Properties/launchSettings.json new file mode 100644 index 0000000..0f7a785 --- /dev/null +++ b/demo/5/Demo5.GrpcApi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5171", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7131;http://localhost:5171", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/demo/5/Demo5.GrpcApi/Protos/greet.proto b/demo/5/Demo5.GrpcApi/Protos/greet.proto new file mode 100644 index 0000000..c23edc1 --- /dev/null +++ b/demo/5/Demo5.GrpcApi/Protos/greet.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option csharp_namespace = "Demo5.GrpcApi"; + +package greet; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply); +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings. +message HelloReply { + string message = 1; +} diff --git a/demo/5/Demo5.GrpcApi/Services/BusinessInterceptor.cs b/demo/5/Demo5.GrpcApi/Services/BusinessInterceptor.cs new file mode 100644 index 0000000..55d9f26 --- /dev/null +++ b/demo/5/Demo5.GrpcApi/Services/BusinessInterceptor.cs @@ -0,0 +1,65 @@ +using Demo5.HttpApi; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Microsoft.Extensions.Localization; + +namespace Demo5.GrpcApi.Services; + +/// +/// 业务异常拦截器. +/// +public class BusinessInterceptor : Interceptor +{ + private readonly ILogger _logger; + private readonly IStringLocalizer _stringLocalizer; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public BusinessInterceptor(ILogger logger, IStringLocalizer stringLocalizer) + { + _logger = logger; + _stringLocalizer = stringLocalizer; + } + + /// + public override async Task UnaryServerHandler(TRequest request, ServerCallContext context, UnaryServerMethod continuation) + where TRequest : class + where TResponse : class + { + try + { + var response = await continuation(request, context); + return response; + } + catch (BusinessException ex) + { + // ... 打印日志 ... + + string message = string.Empty; + if (ex.Paramters != null) + { + message = _stringLocalizer[ex.Message, ex.Paramters]; + } + else + { + message = _stringLocalizer[ex.Message]; + } + + throw new RpcException(new Status(StatusCode.Internal, message)); + } + catch (Exception ex) + { + // ... 打印日志 ... + + if (ex is RpcException) + { + throw; + } + + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } +} diff --git a/demo/5/Demo5.GrpcApi/Services/GreeterService.cs b/demo/5/Demo5.GrpcApi/Services/GreeterService.cs new file mode 100644 index 0000000..4de340c --- /dev/null +++ b/demo/5/Demo5.GrpcApi/Services/GreeterService.cs @@ -0,0 +1,28 @@ +using Demo5.GrpcApi; +using Demo5.HttpApi; +using Grpc.Core; +using Microsoft.AspNetCore.Builder.Extensions; +using System.Diagnostics; +using System.Text.Json; + +namespace Demo5.GrpcApi.Services; +public class GreeterService : Greeter.GreeterBase +{ + private readonly ILogger _logger; + public GreeterService(ILogger logger) + { + _logger = logger; + } + + public override Task SayHello(HelloRequest request, ServerCallContext context) + { + if (request.Name == "error") + { + throw new BusinessException(500, "用户未填写手机号"); + } + return Task.FromResult(new HelloReply + { + Message = "Hello " + request.Name + }); + } +} diff --git a/src/MaomiFramework/demo/5/Demo5.Redis/appsettings.Development.json b/demo/5/Demo5.GrpcApi/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Redis/appsettings.Development.json rename to demo/5/Demo5.GrpcApi/appsettings.Development.json diff --git a/demo/5/Demo5.GrpcApi/appsettings.json b/demo/5/Demo5.GrpcApi/appsettings.json new file mode 100644 index 0000000..1aef507 --- /dev/null +++ b/demo/5/Demo5.GrpcApi/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + } +} diff --git a/demo/5/Demo5.GrpcApi/grpcui.exe b/demo/5/Demo5.GrpcApi/grpcui.exe new file mode 100644 index 0000000..9d57de7 Binary files /dev/null and b/demo/5/Demo5.GrpcApi/grpcui.exe differ diff --git a/demo/5/Demo5.GrpcApi/i18n/en-US.json b/demo/5/Demo5.GrpcApi/i18n/en-US.json new file mode 100644 index 0000000..0f9a171 --- /dev/null +++ b/demo/5/Demo5.GrpcApi/i18n/en-US.json @@ -0,0 +1,4 @@ +{ + "用户未填写手机号": "The user did not enter the mobile phone number", + "邮箱格式错误{0}": "Mailbox format error{0}" +} diff --git a/demo/5/Demo5.GrpcApi/i18n/zh-CN.json b/demo/5/Demo5.GrpcApi/i18n/zh-CN.json new file mode 100644 index 0000000..f26da14 --- /dev/null +++ b/demo/5/Demo5.GrpcApi/i18n/zh-CN.json @@ -0,0 +1,4 @@ +{ + "用户未填写手机号": "用户未填写手机号", + "邮箱格式错误{0}": "邮箱格式错误{0}" +} diff --git a/demo/5/Demo5.HttpApi/BusinessException.cs b/demo/5/Demo5.HttpApi/BusinessException.cs new file mode 100644 index 0000000..d3c3e2e --- /dev/null +++ b/demo/5/Demo5.HttpApi/BusinessException.cs @@ -0,0 +1,55 @@ +namespace Demo5.HttpApi; + +/// +/// 业务异常类型. +/// +public class BusinessException : Exception +{ + /// + /// 异常代码. + /// + public int? Code { get; set; } + + /// + /// 异常描述. + /// + public string? Details { get; set; } + + /// + /// 参数. + /// + public object[]? Paramters { get; set; } + + /// + /// 异常级别. + /// + public LogLevel LogLevel { get; set; } = LogLevel.Error; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public BusinessException( + int? code = null, + string? message = null, + params object[] paramters) + : base(message) + { + Code = code; + Paramters = paramters; + } + + /// + /// 记录额外的异常信息. + /// + /// + /// + /// . + public BusinessException WithData(string name, object value) + { + Data[name] = value; + return this; + } +} \ No newline at end of file diff --git a/demo/5/Demo5.HttpApi/BusinessExceptionFilter.cs b/demo/5/Demo5.HttpApi/BusinessExceptionFilter.cs new file mode 100644 index 0000000..1911a7d --- /dev/null +++ b/demo/5/Demo5.HttpApi/BusinessExceptionFilter.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Localization; + +namespace Demo5.HttpApi; + +/// +/// 统一异常处理. +/// +public class BusinessExceptionFilter : IAsyncExceptionFilter +{ + private readonly ILogger _logger; + private readonly IStringLocalizer _stringLocalizer; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public BusinessExceptionFilter(ILogger logger, IStringLocalizer stringLocalizer) + { + _logger = logger; + _stringLocalizer = stringLocalizer; + } + + /// + public async Task OnExceptionAsync(ExceptionContext context) + { + // 未被处理的异常 + if (!context.ExceptionHandled) + { + object? response = default; + + // 如果抛出的是业务异常,转换为对应异常信息返回 + if (context.Exception is BusinessException ex) + { + string message = string.Empty; + if (ex.Paramters != null && ex.Paramters.Length != 0) + { + message = _stringLocalizer[ex.Message, ex.Paramters]; + } + else + { + message = _stringLocalizer[ex.Message]; + } + + response = new + { + Code = 500, + Message = message + }; + + // ... 记录异常日志 ... + } + else + { + + response = new + { + Code = 500, + context.Exception.Message + }; + + // ... 记录异常日志 ... + } + + context.Result = new ObjectResult(response) + { + StatusCode = 500, + }; + + context.ExceptionHandled = true; + } + + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/demo/5/Demo5.HttpApi/Controllers/WeatherForecastController.cs b/demo/5/Demo5.HttpApi/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..3753d90 --- /dev/null +++ b/demo/5/Demo5.HttpApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Builder.Extensions; +using Microsoft.AspNetCore.Mvc; + +namespace Demo5.HttpApi.Controllers; +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + + [HttpGet("create_user")] + public string CreateUser(string? userName, string email) + { + if (string.IsNullOrEmpty(userName)) + { + throw new BusinessException(500, "ûδдֻ"); + } + + if (!email.Contains("@")) + { + throw new BusinessException(500, "ʽ{0}", email); + } + + return "ɹ"; + } +} diff --git a/demo/5/Demo5.HttpApi/Demo5.HttpApi.csproj b/demo/5/Demo5.HttpApi/Demo5.HttpApi.csproj new file mode 100644 index 0000000..3b5456d --- /dev/null +++ b/demo/5/Demo5.HttpApi/Demo5.HttpApi.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/demo/5/Demo5.HttpApi/Demo5.HttpApi.http b/demo/5/Demo5.HttpApi/Demo5.HttpApi.http new file mode 100644 index 0000000..0d81275 --- /dev/null +++ b/demo/5/Demo5.HttpApi/Demo5.HttpApi.http @@ -0,0 +1,6 @@ +@Demo5.HttpApi_HostAddress = http://localhost:5263 + +GET {{Demo5.HttpApi_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/demo/5/Demo5.HttpApi/Program.cs b/demo/5/Demo5.HttpApi/Program.cs new file mode 100644 index 0000000..642b019 --- /dev/null +++ b/demo/5/Demo5.HttpApi/Program.cs @@ -0,0 +1,39 @@ +using Demo5.HttpApi; +using Maomi.I18n; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(o => +{ + o.Filters.Add(); +}); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddHttpContextAccessor(); + +// 添加 i18n 多语言支持 +builder.Services.AddI18nAspNetCore(defaultLanguage: "zh-CN"); +// 设置多语言来源-json +builder.Services.AddI18nResource(option => +{ + var basePath = "i18n"; + option.AddJsonDirectory(basePath); +}); + +var app = builder.Build(); + +app.UseI18n(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/demo/5/Demo5.HttpApi/Properties/launchSettings.json b/demo/5/Demo5.HttpApi/Properties/launchSettings.json new file mode 100644 index 0000000..09273c9 --- /dev/null +++ b/demo/5/Demo5.HttpApi/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:10871", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5263", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/demo/5/Demo5.HttpApi/WeatherForecast.cs b/demo/5/Demo5.HttpApi/WeatherForecast.cs new file mode 100644 index 0000000..4865163 --- /dev/null +++ b/demo/5/Demo5.HttpApi/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Demo5.HttpApi; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/src/MaomiFramework/demo/6/Demo6.Api/appsettings.Development.json b/demo/5/Demo5.HttpApi/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Api/appsettings.Development.json rename to demo/5/Demo5.HttpApi/appsettings.Development.json diff --git a/src/MaomiFramework/demo/5/Demo5.Redis/appsettings.json b/demo/5/Demo5.HttpApi/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Redis/appsettings.json rename to demo/5/Demo5.HttpApi/appsettings.json diff --git a/demo/5/Demo5.HttpApi/i18n/en-US.json b/demo/5/Demo5.HttpApi/i18n/en-US.json new file mode 100644 index 0000000..0f9a171 --- /dev/null +++ b/demo/5/Demo5.HttpApi/i18n/en-US.json @@ -0,0 +1,4 @@ +{ + "用户未填写手机号": "The user did not enter the mobile phone number", + "邮箱格式错误{0}": "Mailbox format error{0}" +} diff --git a/demo/5/Demo5.HttpApi/i18n/zh-CN.json b/demo/5/Demo5.HttpApi/i18n/zh-CN.json new file mode 100644 index 0000000..f26da14 --- /dev/null +++ b/demo/5/Demo5.HttpApi/i18n/zh-CN.json @@ -0,0 +1,4 @@ +{ + "用户未填写手机号": "用户未填写手机号", + "邮箱格式错误{0}": "邮箱格式错误{0}" +} diff --git a/src/MaomiFramework/demo/5/Demo5.Lib/Demo5.Lib.csproj b/demo/5/Demo5.Lib/Demo5.Lib.csproj similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Lib/Demo5.Lib.csproj rename to demo/5/Demo5.Lib/Demo5.Lib.csproj diff --git a/demo/5/Demo5.Lib/Extensions.cs b/demo/5/Demo5.Lib/Extensions.cs new file mode 100644 index 0000000..b51f4b5 --- /dev/null +++ b/demo/5/Demo5.Lib/Extensions.cs @@ -0,0 +1,16 @@ +using Maomi.I18n; +using Microsoft.Extensions.DependencyInjection; + +namespace Demo5.Lib; + +public class Test { } +public static class Extensions +{ + public static void AddLib(this IServiceCollection services) + { + services.AddI18nResource(options => + { + options.ParseDirectory("i18n"); + }); + } +} diff --git a/src/MaomiFramework/demo/5/Demo5.Lib/Properties/launchSettings.json b/demo/5/Demo5.Lib/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Lib/Properties/launchSettings.json rename to demo/5/Demo5.Lib/Properties/launchSettings.json diff --git a/demo/5/Demo5.Lib/i18n/Demo5.Lib/en-US.json b/demo/5/Demo5.Lib/i18n/Demo5.Lib/en-US.json new file mode 100644 index 0000000..f9937a0 --- /dev/null +++ b/demo/5/Demo5.Lib/i18n/Demo5.Lib/en-US.json @@ -0,0 +1,3 @@ +{ + "test": "lib en-US" +} \ No newline at end of file diff --git a/demo/5/Demo5.Lib/i18n/Demo5.Lib/zh-CN.json b/demo/5/Demo5.Lib/i18n/Demo5.Lib/zh-CN.json new file mode 100644 index 0000000..bb41bb4 --- /dev/null +++ b/demo/5/Demo5.Lib/i18n/Demo5.Lib/zh-CN.json @@ -0,0 +1,3 @@ +{ + "test": "lib zh-CN" +} \ No newline at end of file diff --git a/src/MaomiFramework/demo/5/Demo5.Nuget/Demo5.Nuget.csproj b/demo/5/Demo5.Nuget/Demo5.Nuget.csproj similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Nuget/Demo5.Nuget.csproj rename to demo/5/Demo5.Nuget/Demo5.Nuget.csproj diff --git a/src/MaomiFramework/demo/5/Demo5.Nuget/i18n/Demo5.Nuget/en-US.json b/demo/5/Demo5.Nuget/i18n/Demo5.Nuget/en-US.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Nuget/i18n/Demo5.Nuget/en-US.json rename to demo/5/Demo5.Nuget/i18n/Demo5.Nuget/en-US.json diff --git a/src/MaomiFramework/demo/5/Demo5.Nuget/i18n/Demo5.Nuget/zh-CN.json b/demo/5/Demo5.Nuget/i18n/Demo5.Nuget/zh-CN.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Nuget/i18n/Demo5.Nuget/zh-CN.json rename to demo/5/Demo5.Nuget/i18n/Demo5.Nuget/zh-CN.json diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/Demo6.FullApi.csproj b/demo/5/Demo5.Redis/Demo5.Redis.csproj similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/Demo6.FullApi.csproj rename to demo/5/Demo5.Redis/Demo5.Redis.csproj diff --git a/src/MaomiFramework/demo/5/Demo5.Redis/Program.cs b/demo/5/Demo5.Redis/Program.cs similarity index 96% rename from src/MaomiFramework/demo/5/Demo5.Redis/Program.cs rename to demo/5/Demo5.Redis/Program.cs index 767b5c3..558144e 100644 --- a/src/MaomiFramework/demo/5/Demo5.Redis/Program.cs +++ b/demo/5/Demo5.Redis/Program.cs @@ -23,7 +23,7 @@ static void Main(string[] args) builder.Services.AddI18nResource(option => { option.AddRedis(cli, "language", TimeSpan.FromMinutes(100), 10); - option.AddJson("i18n"); + option.ParseDirectory("i18n"); }); diff --git a/src/MaomiFramework/demo/5/Demo5.Redis/Properties/launchSettings.json b/demo/5/Demo5.Redis/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/5/Demo5.Redis/Properties/launchSettings.json rename to demo/5/Demo5.Redis/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/appsettings.Development.json b/demo/5/Demo5.Redis/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/appsettings.Development.json rename to demo/5/Demo5.Redis/appsettings.Development.json diff --git a/src/MaomiFramework/demo/6/Demo6.Api/appsettings.json b/demo/5/Demo5.Redis/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Api/appsettings.json rename to demo/5/Demo5.Redis/appsettings.json diff --git a/demo/5/Demo5Wpf/App.xaml b/demo/5/Demo5Wpf/App.xaml new file mode 100644 index 0000000..00bfc41 --- /dev/null +++ b/demo/5/Demo5Wpf/App.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/demo/5/Demo5Wpf/App.xaml.cs b/demo/5/Demo5Wpf/App.xaml.cs new file mode 100644 index 0000000..82687cf --- /dev/null +++ b/demo/5/Demo5Wpf/App.xaml.cs @@ -0,0 +1,97 @@ +using Demo5Wpf.Services; +using Demo5Wpf.ViewModels.Pages; +using Demo5Wpf.ViewModels.Windows; +using Demo5Wpf.Views.Pages; +using Demo5Wpf.Views.Windows; +using Maomi.I18n; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.IO; +using System.Reflection; +using System.Windows.Threading; +using Wpf.Ui; + +namespace Demo5Wpf; +/// +/// Interaction logic for App.xaml +/// +public partial class App +{ + // The.NET Generic Host provides dependency injection, configuration, logging, and other services. + // https://docs.microsoft.com/dotnet/core/extensions/generic-host + // https://docs.microsoft.com/dotnet/core/extensions/dependency-injection + // https://docs.microsoft.com/dotnet/core/extensions/configuration + // https://docs.microsoft.com/dotnet/core/extensions/logging + private static readonly IHost _host = Host + .CreateDefaultBuilder() + .ConfigureAppConfiguration(c => { c.SetBasePath(Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!); }) + .ConfigureServices((context, services) => + { + services.AddHostedService(); + + // Page resolver service + services.AddSingleton(); + + // Theme manipulation + services.AddSingleton(); + + // TaskBar manipulation + services.AddSingleton(); + + // Service containing navigation, same as INavigationWindow... but without window + services.AddSingleton(); + + // Main window with navigation + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddI18n("zh-CN"); + services.AddI18nWpf("Demo5Wpf", "Localization"); + }).Build(); + + /// + /// Gets registered service. + /// + /// Type of the service to get. + /// Instance of the service or . + public static T GetService() + where T : class + { + return _host.Services.GetRequiredService(); + } + + /// + /// Occurs when the application is loading. + /// + private void OnStartup(object sender, StartupEventArgs e) + { + _host.Start(); + } + + /// + /// Occurs when the application is closing. + /// + private async void OnExit(object sender, ExitEventArgs e) + { + await _host.StopAsync(); + + _host.Dispose(); + } + + /// + /// Occurs when an exception is thrown by an application but not handled. + /// + private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + // For more info see https://docs.microsoft.com/en-us/dotnet/api/system.windows.application.dispatcherunhandledexception?view=windowsdesktop-6.0 + } +} diff --git a/demo/5/Demo5Wpf/AssemblyInfo.cs b/demo/5/Demo5Wpf/AssemblyInfo.cs new file mode 100644 index 0000000..2818484 --- /dev/null +++ b/demo/5/Demo5Wpf/AssemblyInfo.cs @@ -0,0 +1,8 @@ +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located +//(used if a resource is not found in the page, +// app, or any theme specific resource dictionaries) +)] diff --git a/demo/5/Demo5Wpf/Assets/wpfui-icon-1024.png b/demo/5/Demo5Wpf/Assets/wpfui-icon-1024.png new file mode 100644 index 0000000..b70c4ed Binary files /dev/null and b/demo/5/Demo5Wpf/Assets/wpfui-icon-1024.png differ diff --git a/demo/5/Demo5Wpf/Assets/wpfui-icon-256.png b/demo/5/Demo5Wpf/Assets/wpfui-icon-256.png new file mode 100644 index 0000000..6b5cf5d Binary files /dev/null and b/demo/5/Demo5Wpf/Assets/wpfui-icon-256.png differ diff --git a/demo/5/Demo5Wpf/Demo5Wpf.csproj b/demo/5/Demo5Wpf/Demo5Wpf.csproj new file mode 100644 index 0000000..03a59ba --- /dev/null +++ b/demo/5/Demo5Wpf/Demo5Wpf.csproj @@ -0,0 +1,38 @@ + + + + WinExe + net8.0-windows + app.manifest + wpfui-icon.ico + true + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/5/Demo5Wpf/Helpers/EnumToBooleanConverter.cs b/demo/5/Demo5Wpf/Helpers/EnumToBooleanConverter.cs new file mode 100644 index 0000000..9562114 --- /dev/null +++ b/demo/5/Demo5Wpf/Helpers/EnumToBooleanConverter.cs @@ -0,0 +1,35 @@ +using System.Globalization; +using System.Windows.Data; +using Wpf.Ui.Appearance; + +namespace Demo5Wpf.Helpers; + +internal class EnumToBooleanConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (parameter is not String enumString) + { + throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); + } + + if (!Enum.IsDefined(typeof(ApplicationTheme), value)) + { + throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum"); + } + + var enumValue = Enum.Parse(typeof(ApplicationTheme), enumString); + + return enumValue.Equals(value); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (parameter is not String enumString) + { + throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); + } + + return Enum.Parse(typeof(ApplicationTheme), enumString); + } +} diff --git a/demo/5/Demo5Wpf/Helpers/SnackbarServiceExtensions.cs b/demo/5/Demo5Wpf/Helpers/SnackbarServiceExtensions.cs new file mode 100644 index 0000000..2c47ede --- /dev/null +++ b/demo/5/Demo5Wpf/Helpers/SnackbarServiceExtensions.cs @@ -0,0 +1,80 @@ +using Wpf.Ui; +using Wpf.Ui.Controls; + +namespace Demo5; + +public static class SnackbarServiceExtensions +{ + public static void ShowPrimary(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Primary, null, TimeSpan.FromSeconds(seconds)); + }); + } + + public static void ShowSecondary(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Secondary, null, TimeSpan.FromSeconds(seconds)); + }); + } + + public static void ShowInfo(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Info, null, TimeSpan.FromSeconds(seconds)); + }); + } + + public static void ShowDark(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Dark, null, TimeSpan.FromSeconds(seconds)); + + }); + } + + public static void ShowLight(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Light, null, TimeSpan.FromSeconds(seconds)); + }); + } + + public static void ShowDanger(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Danger, null, TimeSpan.FromSeconds(seconds)); + }); + } + + public static void ShowSuccess(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Success, null, TimeSpan.FromSeconds(seconds)); + }); + } + + public static void ShowCaution(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Caution, null, TimeSpan.FromSeconds(seconds)); + }); + } + + public static void ShowTransparent(this ISnackbarService snackbarService, string title, string message, int seconds = 2, IconElement? icon = null) + { + Application.Current.Dispatcher.Invoke(() => + { + snackbarService.Show(title, message, ControlAppearance.Transparent, null, TimeSpan.FromSeconds(seconds)); + }); + } +} \ No newline at end of file diff --git a/demo/5/Demo5Wpf/Localization/en-US.xaml b/demo/5/Demo5Wpf/Localization/en-US.xaml new file mode 100644 index 0000000..c1dd1de --- /dev/null +++ b/demo/5/Demo5Wpf/Localization/en-US.xaml @@ -0,0 +1,17 @@ + + + Error + Username + Email + Phone + Select language + Please enter your phone. + Please enter your email. + Please enter your name. + Success + Save info. + Save + + \ No newline at end of file diff --git a/demo/5/Demo5Wpf/Localization/zh-CN.xaml b/demo/5/Demo5Wpf/Localization/zh-CN.xaml new file mode 100644 index 0000000..16a9b47 --- /dev/null +++ b/demo/5/Demo5Wpf/Localization/zh-CN.xaml @@ -0,0 +1,17 @@ + + + 错误 + 用户名 + 邮箱 + 手机号 + 切换语言 + 手机号必填 + 邮箱必填 + 用户名必填 + 成功 + 已保存信息 + 保存 + + \ No newline at end of file diff --git a/demo/5/Demo5Wpf/Models/AppConfig.cs b/demo/5/Demo5Wpf/Models/AppConfig.cs new file mode 100644 index 0000000..79ae944 --- /dev/null +++ b/demo/5/Demo5Wpf/Models/AppConfig.cs @@ -0,0 +1,8 @@ +namespace Demo5Wpf.Models; + +public class AppConfig +{ + public string ConfigurationsFolder { get; set; } + + public string AppPropertiesFileName { get; set; } +} diff --git a/demo/5/Demo5Wpf/Models/DataColor.cs b/demo/5/Demo5Wpf/Models/DataColor.cs new file mode 100644 index 0000000..35d4f37 --- /dev/null +++ b/demo/5/Demo5Wpf/Models/DataColor.cs @@ -0,0 +1,7 @@ +using System.Windows.Media; + +namespace Demo5Wpf.Models; +public struct DataColor +{ + public Brush Color { get; set; } +} diff --git a/demo/5/Demo5Wpf/Resources/Translations.cs b/demo/5/Demo5Wpf/Resources/Translations.cs new file mode 100644 index 0000000..067c795 --- /dev/null +++ b/demo/5/Demo5Wpf/Resources/Translations.cs @@ -0,0 +1,5 @@ +namespace Demo5Wpf.Resources; + +public partial class Translations +{ +} diff --git a/demo/5/Demo5Wpf/Services/ApplicationHostService.cs b/demo/5/Demo5Wpf/Services/ApplicationHostService.cs new file mode 100644 index 0000000..6f3aae7 --- /dev/null +++ b/demo/5/Demo5Wpf/Services/ApplicationHostService.cs @@ -0,0 +1,55 @@ +using Demo5Wpf.Views.Windows; +using Microsoft.Extensions.Hosting; +using Wpf.Ui; + +namespace Demo5Wpf.Services; +/// +/// Managed host of the application. +/// +public class ApplicationHostService : IHostedService +{ + private readonly IServiceProvider _serviceProvider; + + private INavigationWindow _navigationWindow; + + public ApplicationHostService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + /// Triggered when the application host is ready to start the service. + /// + /// Indicates that the start process has been aborted. + public async Task StartAsync(CancellationToken cancellationToken) + { + await HandleActivationAsync(); + } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// + /// Indicates that the shutdown process should no longer be graceful. + public async Task StopAsync(CancellationToken cancellationToken) + { + await Task.CompletedTask; + } + + /// + /// Creates main window during activation. + /// + private async Task HandleActivationAsync() + { + if (!Application.Current.Windows.OfType().Any()) + { + _navigationWindow = ( + _serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow + )!; + _navigationWindow!.ShowWindow(); + + _navigationWindow.Navigate(typeof(Views.Pages.DashboardPage)); + } + + await Task.CompletedTask; + } +} diff --git a/demo/5/Demo5Wpf/Services/PageService.cs b/demo/5/Demo5Wpf/Services/PageService.cs new file mode 100644 index 0000000..e1952b4 --- /dev/null +++ b/demo/5/Demo5Wpf/Services/PageService.cs @@ -0,0 +1,40 @@ +using Wpf.Ui; + +namespace Demo5Wpf.Services; +/// +/// Service that provides pages for navigation. +/// +public class PageService : IPageService +{ + /// + /// Service which provides the instances of pages. + /// + private readonly IServiceProvider _serviceProvider; + + /// + /// Creates new instance and attaches the . + /// + public PageService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + public T? GetPage() + where T : class + { + if (!typeof(FrameworkElement).IsAssignableFrom(typeof(T))) + throw new InvalidOperationException("The page should be a WPF control."); + + return (T?)_serviceProvider.GetService(typeof(T)); + } + + /// + public FrameworkElement? GetPage(Type pageType) + { + if (!typeof(FrameworkElement).IsAssignableFrom(pageType)) + throw new InvalidOperationException("The page should be a WPF control."); + + return _serviceProvider.GetService(pageType) as FrameworkElement; + } +} diff --git a/demo/5/Demo5Wpf/Usings.cs b/demo/5/Demo5Wpf/Usings.cs new file mode 100644 index 0000000..5efad98 --- /dev/null +++ b/demo/5/Demo5Wpf/Usings.cs @@ -0,0 +1,4 @@ +global using CommunityToolkit.Mvvm.ComponentModel; +global using CommunityToolkit.Mvvm.Input; +global using System; +global using System.Windows; diff --git a/demo/5/Demo5Wpf/ViewModels/Pages/DashboardViewModel.cs b/demo/5/Demo5Wpf/ViewModels/Pages/DashboardViewModel.cs new file mode 100644 index 0000000..80ce693 --- /dev/null +++ b/demo/5/Demo5Wpf/ViewModels/Pages/DashboardViewModel.cs @@ -0,0 +1,87 @@ +using Demo5; +using Maomi.I18n; +using Microsoft.Extensions.Localization; +using System.Collections.ObjectModel; +using Wpf.Ui; +using Wpf.Ui.Extensions; + +namespace Demo5Wpf.ViewModels.Pages; + +public partial class DashboardViewModel : ObservableObject +{ + private readonly ISnackbarService _snackbarService; + private readonly IStringLocalizer _i18n; + private readonly I18nResourceFactory _i18nResourceFactory; + private readonly WpfI18nContext _i18nContext; + + public DashboardViewModel(ISnackbarService snackbarService, IStringLocalizer i18n, I18nResourceFactory i18nResourceFactory, WpfI18nContext i18nContext) + { + _snackbarService = snackbarService; + _i18n = i18n; + _i18nResourceFactory = i18nResourceFactory; + _i18nContext = i18nContext; + + Languages = new ObservableCollection(_i18nResourceFactory.SupportedCultures.Select(x => x.Name)); + } + + [ObservableProperty] + private string _userName = ""; + + [ObservableProperty] + private string _email = ""; + + [ObservableProperty] + private string _phone = ""; + + + /// + /// 当前选择语言. + /// + [ObservableProperty] + private string _selectedLanguage = "zh-CN"; + + /// + /// 当前支持的语言列表. + /// + [ObservableProperty] + private ObservableCollection _languages = new(); + + [ObservableProperty] + private int _counter = 0; + + [RelayCommand] + private void OnCounterIncrement() + { + Counter++; + } + + [RelayCommand] + private void OnSetLanguage() + { + _i18nContext.SetLanguage(SelectedLanguage); + } + + [RelayCommand] + private void OnSave() + { + if (string.IsNullOrWhiteSpace(UserName)) + { + _snackbarService.ShowDanger(_i18n["错误"], _i18n["用户名必填"]); + return; + } + + if (string.IsNullOrWhiteSpace(Email)) + { + _snackbarService.ShowDanger(_i18n["错误"], _i18n["邮箱必填"]); + return; + } + + if (string.IsNullOrWhiteSpace(Phone)) + { + _snackbarService.ShowDanger(_i18n["错误"], _i18n["手机号必填"]); + return; + } + + _snackbarService.ShowDanger(_i18n["成功"], _i18n["已保存信息"]); + } +} diff --git a/demo/5/Demo5Wpf/ViewModels/Pages/DataViewModel.cs b/demo/5/Demo5Wpf/ViewModels/Pages/DataViewModel.cs new file mode 100644 index 0000000..bbbb6d0 --- /dev/null +++ b/demo/5/Demo5Wpf/ViewModels/Pages/DataViewModel.cs @@ -0,0 +1,45 @@ +using Demo5Wpf.Models; +using System.Windows.Media; +using Wpf.Ui.Controls; + +namespace Demo5Wpf.ViewModels.Pages; +public partial class DataViewModel : ObservableObject, INavigationAware +{ + private bool _isInitialized = false; + + [ObservableProperty] + private IEnumerable _colors; + + public void OnNavigatedTo() + { + if (!_isInitialized) + InitializeViewModel(); + } + + public void OnNavigatedFrom() { } + + private void InitializeViewModel() + { + var random = new Random(); + var colorCollection = new List(); + + for (int i = 0; i < 8192; i++) + colorCollection.Add( + new DataColor + { + Color = new SolidColorBrush( + Color.FromArgb( + (byte)200, + (byte)random.Next(0, 250), + (byte)random.Next(0, 250), + (byte)random.Next(0, 250) + ) + ) + } + ); + + Colors = colorCollection; + + _isInitialized = true; + } +} diff --git a/demo/5/Demo5Wpf/ViewModels/Pages/SettingsViewModel.cs b/demo/5/Demo5Wpf/ViewModels/Pages/SettingsViewModel.cs new file mode 100644 index 0000000..30c308c --- /dev/null +++ b/demo/5/Demo5Wpf/ViewModels/Pages/SettingsViewModel.cs @@ -0,0 +1,61 @@ +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; + +namespace Demo5Wpf.ViewModels.Pages; +public partial class SettingsViewModel : ObservableObject, INavigationAware +{ + private bool _isInitialized = false; + + [ObservableProperty] + private string _appVersion = String.Empty; + + [ObservableProperty] + private ApplicationTheme _currentTheme = ApplicationTheme.Unknown; + + public void OnNavigatedTo() + { + if (!_isInitialized) + InitializeViewModel(); + } + + public void OnNavigatedFrom() { } + + private void InitializeViewModel() + { + CurrentTheme = ApplicationThemeManager.GetAppTheme(); + AppVersion = $"UiDesktopApp1 - {GetAssemblyVersion()}"; + + _isInitialized = true; + } + + private string GetAssemblyVersion() + { + return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() + ?? String.Empty; + } + + [RelayCommand] + private void OnChangeTheme(string parameter) + { + switch (parameter) + { + case "theme_light": + if (CurrentTheme == ApplicationTheme.Light) + break; + + ApplicationThemeManager.Apply(ApplicationTheme.Light); + CurrentTheme = ApplicationTheme.Light; + + break; + + default: + if (CurrentTheme == ApplicationTheme.Dark) + break; + + ApplicationThemeManager.Apply(ApplicationTheme.Dark); + CurrentTheme = ApplicationTheme.Dark; + + break; + } + } +} diff --git a/demo/5/Demo5Wpf/ViewModels/Windows/MainWindowViewModel.cs b/demo/5/Demo5Wpf/ViewModels/Windows/MainWindowViewModel.cs new file mode 100644 index 0000000..435a334 --- /dev/null +++ b/demo/5/Demo5Wpf/ViewModels/Windows/MainWindowViewModel.cs @@ -0,0 +1,44 @@ +using System.Collections.ObjectModel; +using Wpf.Ui; +using Wpf.Ui.Controls; + +namespace Demo5Wpf.ViewModels.Windows; +public partial class MainWindowViewModel : ObservableObject +{ + [ObservableProperty] + private string _applicationTitle = "WPF UI - Demo5Wpf"; + + [ObservableProperty] + private ObservableCollection _menuItems = new() + { + new NavigationViewItem() + { + Content = "Home", + Icon = new SymbolIcon { Symbol = SymbolRegular.Home24 }, + TargetPageType = typeof(Views.Pages.DashboardPage) + }, + new NavigationViewItem() + { + Content = "Data", + Icon = new SymbolIcon { Symbol = SymbolRegular.DataHistogram24 }, + TargetPageType = typeof(Views.Pages.DataPage) + } + }; + + [ObservableProperty] + private ObservableCollection _footerMenuItems = new() + { + new NavigationViewItem() + { + Content = "Settings", + Icon = new SymbolIcon { Symbol = SymbolRegular.Settings24 }, + TargetPageType = typeof(Views.Pages.SettingsPage) + } + }; + + [ObservableProperty] + private ObservableCollection _trayMenuItems = new() + { + new MenuItem { Header = "Home", Tag = "tray_home" } + }; +} diff --git a/demo/5/Demo5Wpf/Views/Pages/DashboardPage.xaml b/demo/5/Demo5Wpf/Views/Pages/DashboardPage.xaml new file mode 100644 index 0000000..f3bdf94 --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Pages/DashboardPage.xaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/5/Demo5Wpf/Views/Pages/DashboardPage.xaml.cs b/demo/5/Demo5Wpf/Views/Pages/DashboardPage.xaml.cs new file mode 100644 index 0000000..63d6833 --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Pages/DashboardPage.xaml.cs @@ -0,0 +1,16 @@ +using Demo5Wpf.ViewModels.Pages; +using Wpf.Ui.Controls; + +namespace Demo5Wpf.Views.Pages; +public partial class DashboardPage : INavigableView +{ + public DashboardViewModel ViewModel { get; } + + public DashboardPage(DashboardViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + + InitializeComponent(); + } +} diff --git a/demo/5/Demo5Wpf/Views/Pages/DataPage.xaml b/demo/5/Demo5Wpf/Views/Pages/DataPage.xaml new file mode 100644 index 0000000..2e82f79 --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Pages/DataPage.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + diff --git a/demo/5/Demo5Wpf/Views/Pages/DataPage.xaml.cs b/demo/5/Demo5Wpf/Views/Pages/DataPage.xaml.cs new file mode 100644 index 0000000..903b425 --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Pages/DataPage.xaml.cs @@ -0,0 +1,16 @@ +using Demo5Wpf.ViewModels.Pages; +using Wpf.Ui.Controls; + +namespace Demo5Wpf.Views.Pages; +public partial class DataPage : INavigableView +{ + public DataViewModel ViewModel { get; } + + public DataPage(DataViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + + InitializeComponent(); + } +} diff --git a/demo/5/Demo5Wpf/Views/Pages/SettingsPage.xaml b/demo/5/Demo5Wpf/Views/Pages/SettingsPage.xaml new file mode 100644 index 0000000..94b542f --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Pages/SettingsPage.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + diff --git a/demo/5/Demo5Wpf/Views/Pages/SettingsPage.xaml.cs b/demo/5/Demo5Wpf/Views/Pages/SettingsPage.xaml.cs new file mode 100644 index 0000000..a4c2d8d --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Pages/SettingsPage.xaml.cs @@ -0,0 +1,16 @@ +using Demo5Wpf.ViewModels.Pages; +using Wpf.Ui.Controls; + +namespace Demo5Wpf.Views.Pages; +public partial class SettingsPage : INavigableView +{ + public SettingsViewModel ViewModel { get; } + + public SettingsPage(SettingsViewModel viewModel) + { + ViewModel = viewModel; + DataContext = this; + + InitializeComponent(); + } +} diff --git a/demo/5/Demo5Wpf/Views/Windows/MainWindow.xaml b/demo/5/Demo5Wpf/Views/Windows/MainWindow.xaml new file mode 100644 index 0000000..0a5996d --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Windows/MainWindow.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/5/Demo5Wpf/Views/Windows/MainWindow.xaml.cs b/demo/5/Demo5Wpf/Views/Windows/MainWindow.xaml.cs new file mode 100644 index 0000000..0469ae5 --- /dev/null +++ b/demo/5/Demo5Wpf/Views/Windows/MainWindow.xaml.cs @@ -0,0 +1,64 @@ +using Demo5Wpf.ViewModels.Windows; +using Wpf.Ui; +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; + +namespace Demo5Wpf.Views.Windows; +public partial class MainWindow : INavigationWindow +{ + public MainWindowViewModel ViewModel { get; } + + public MainWindow( + MainWindowViewModel viewModel, + IPageService pageService, + ISnackbarService snackbarService, + INavigationService navigationService + ) + { + ViewModel = viewModel; + DataContext = this; + + SystemThemeWatcher.Watch(this); + + InitializeComponent(); + SetPageService(pageService); + + snackbarService.SetSnackbarPresenter(SnackbarPresenter); + navigationService.SetNavigationControl(RootNavigation); + } + + #region INavigationWindow methods + + public INavigationView GetNavigation() => RootNavigation; + + public bool Navigate(Type pageType) => RootNavigation.Navigate(pageType); + + public void SetPageService(IPageService pageService) => RootNavigation.SetPageService(pageService); + + public void ShowWindow() => Show(); + + public void CloseWindow() => Close(); + + #endregion INavigationWindow methods + + /// + /// Raises the closed event. + /// + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + // Make sure that closing this window will begin the process of closing the application. + Application.Current.Shutdown(); + } + + INavigationView INavigationWindow.GetNavigation() + { + throw new NotImplementedException(); + } + + public void SetServiceProvider(IServiceProvider serviceProvider) + { + throw new NotImplementedException(); + } +} diff --git a/demo/5/Demo5Wpf/app.manifest b/demo/5/Demo5Wpf/app.manifest new file mode 100644 index 0000000..f9d26cd --- /dev/null +++ b/demo/5/Demo5Wpf/app.manifest @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PerMonitor + true/PM + true + + + + + + + + + + diff --git a/demo/5/Demo5Wpf/wpfui-icon.ico b/demo/5/Demo5Wpf/wpfui-icon.ico new file mode 100644 index 0000000..cc128fd Binary files /dev/null and b/demo/5/Demo5Wpf/wpfui-icon.ico differ diff --git a/demo/5/exporti18n/Program.cs b/demo/5/exporti18n/Program.cs new file mode 100644 index 0000000..fa06026 --- /dev/null +++ b/demo/5/exporti18n/Program.cs @@ -0,0 +1,132 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Net.Http; +using System.Xml; + +public class Program +{ + static async Task Main(string[] args) + { + var filters = new string[] { "bin", "obj", "Properties" }; + + var i18nDic = new Dictionary(); + + var slnPath = ""; + var jsonDir = ""; + + var dllPath = typeof(Program).Assembly.Location; + + // 如果运行路径在 bin\Debug\net8.0 + if (Directory.GetParent(dllPath)!.FullName.Contains("bin\\Debug")) + { + slnPath = Directory.GetParent(dllPath)!.Parent!.Parent!.Parent!.Parent!.Parent!.FullName; + jsonDir = Directory.GetParent(dllPath)!.Parent!.Parent!.Parent!.FullName; + } + else + { + slnPath = Directory.GetParent(dllPath)!.Parent!.Parent!.FullName; + jsonDir = Directory.GetParent(dllPath)!.FullName; + } + + // 所有项目所在目录都在 src 下面 + var projPath = slnPath; + + // 所有项目目录 + var projects = Directory.GetDirectories(projPath); + + // 使用队列逐个目录搜索,不要一次性都加载进去 + foreach (string project in projects) + { + // 子目录列表 + Queue itemDirs = new(); + + itemDirs.Enqueue(project); + + while (itemDirs.Count > 0) + { + var curDir = itemDirs.Dequeue(); + var csFiles = Directory.GetFiles(curDir, "*.cs", SearchOption.TopDirectoryOnly); + foreach (var csFile in csFiles) + { + Console.WriteLine(csFile); + string fileContent = await File.ReadAllTextAsync(csFile); + + // 读取文件解析语法树 + SyntaxTree tree = CSharpSyntaxTree.ParseText(fileContent); + var root = tree.GetRoot(); + + // 查找所有 new BusinessException 语句,new BsiRpcException 语句 + var objectCreations = root.DescendantNodes() + .OfType() + .Where(node => node.Type.ToString() == "BusinessException"); + + foreach (var objectCreation in objectCreations) + { + if (objectCreation.ArgumentList == null) + { + continue; + } + + // 提取 objectCreation 的参数列表中的字符串 + var arguments = objectCreation.ArgumentList.Arguments; + + foreach (var argument in arguments) + { + if (argument.Expression is LiteralExpressionSyntax literal && + literal.IsKind(SyntaxKind.StringLiteralExpression)) + { + string str = literal.Token.ValueText; + if (!i18nDic.ContainsKey(str)) + { + i18nDic[str] = str; + } + } + } + } + + + // 查找所有 WithMessage 方法 + var invocationExpressions = root.DescendantNodes() + .OfType() + .Where(node => node.Expression is MemberAccessExpressionSyntax memberAccess && + memberAccess.Name.ToString() == "WithMessage"); + + foreach (var invocation in invocationExpressions) + { + var arguments = invocation.ArgumentList.Arguments; + + foreach (var argument in arguments) + { + if (argument.Expression is LiteralExpressionSyntax literal && + literal.IsKind(SyntaxKind.StringLiteralExpression)) + { + string str = literal.Token.ValueText; + if (!i18nDic.ContainsKey(str)) + { + i18nDic[str] = str; + } + } + } + } + } + + var newDirs = Directory.GetDirectories(curDir).Where(x => !filters.Contains(x)).ToArray(); + foreach (var itemDir in newDirs) + { + itemDirs.Enqueue(itemDir); + } + } + } + + // Serialize the dictionary to a JSON file + string jsonOutput = System.Text.Json.JsonSerializer.Serialize(i18nDic, new System.Text.Json.JsonSerializerOptions + { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + File.WriteAllText(Path.Combine(jsonDir, "zh-CN.json"), jsonOutput); + + Console.WriteLine("已经自动生成i18n文件,祝你生活愉快"); + } +} \ No newline at end of file diff --git a/demo/5/exporti18n/exporti18n.csproj b/demo/5/exporti18n/exporti18n.csproj new file mode 100644 index 0000000..8e4f1fb --- /dev/null +++ b/demo/5/exporti18n/exporti18n.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/demo/5/exporti18n/zh-CN.json b/demo/5/exporti18n/zh-CN.json new file mode 100644 index 0000000..82abf09 --- /dev/null +++ b/demo/5/exporti18n/zh-CN.json @@ -0,0 +1,4 @@ +{ + "�û�δ��д�ֻ���": "�û�δ��д�ֻ���", + "�����ʽ����{0}": "�����ʽ����{0}" +} \ No newline at end of file diff --git a/src/MaomiFramework/demo/6/Demo6.Api/Controllers/IndexController.cs b/demo/6/Demo6.Api/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Api/Controllers/IndexController.cs rename to demo/6/Demo6.Api/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Api/Demo6.Api.csproj b/demo/6/Demo6.Api/Demo6.Api.csproj similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Api/Demo6.Api.csproj rename to demo/6/Demo6.Api/Demo6.Api.csproj diff --git a/src/MaomiFramework/demo/6/Demo6.Api/Program.cs b/demo/6/Demo6.Api/Program.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Api/Program.cs rename to demo/6/Demo6.Api/Program.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Api/Properties/launchSettings.json b/demo/6/Demo6.Api/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Api/Properties/launchSettings.json rename to demo/6/Demo6.Api/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/appsettings.Development.json b/demo/6/Demo6.Api/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/appsettings.Development.json rename to demo/6/Demo6.Api/appsettings.Development.json diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/appsettings.json b/demo/6/Demo6.Api/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/appsettings.json rename to demo/6/Demo6.Api/appsettings.json diff --git a/src/MaomiFramework/demo/6/Demo6.Console/Demo6.Console.csproj b/demo/6/Demo6.Console/Demo6.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/Demo6.Console.csproj rename to demo/6/Demo6.Console/Demo6.Console.csproj diff --git a/src/MaomiFramework/demo/6/Demo6.Console/HttpClientHelper.cs b/demo/6/Demo6.Console/HttpClientHelper.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/HttpClientHelper.cs rename to demo/6/Demo6.Console/HttpClientHelper.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Console/MyDelegatingHandler.cs b/demo/6/Demo6.Console/MyDelegatingHandler.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/MyDelegatingHandler.cs rename to demo/6/Demo6.Console/MyDelegatingHandler.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Console/MyException.cs b/demo/6/Demo6.Console/MyException.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/MyException.cs rename to demo/6/Demo6.Console/MyException.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Console/PollyTest.cs b/demo/6/Demo6.Console/PollyTest.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/PollyTest.cs rename to demo/6/Demo6.Console/PollyTest.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Console/Program.cs b/demo/6/Demo6.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/Program.cs rename to demo/6/Demo6.Console/Program.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Console/Properties/launchSettings.json b/demo/6/Demo6.Console/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/Properties/launchSettings.json rename to demo/6/Demo6.Console/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/6/Demo6.Console/RefitTest.cs b/demo/6/Demo6.Console/RefitTest.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Console/RefitTest.cs rename to demo/6/Demo6.Console/RefitTest.cs diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/Controllers/DefaultController.cs b/demo/6/Demo6.FullApi/Controllers/DefaultController.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/Controllers/DefaultController.cs rename to demo/6/Demo6.FullApi/Controllers/DefaultController.cs diff --git a/src/MaomiFramework/demo/5/Demo5.Redis/Demo5.Redis.csproj b/demo/6/Demo6.FullApi/Demo6.FullApi.csproj similarity index 75% rename from src/MaomiFramework/demo/5/Demo5.Redis/Demo5.Redis.csproj rename to demo/6/Demo6.FullApi/Demo6.FullApi.csproj index 22a0e5d..bd6bf5e 100644 --- a/src/MaomiFramework/demo/5/Demo5.Redis/Demo5.Redis.csproj +++ b/demo/6/Demo6.FullApi/Demo6.FullApi.csproj @@ -11,8 +11,4 @@ - - - - diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/Models/CI.cs b/demo/6/Demo6.FullApi/Models/CI.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/Models/CI.cs rename to demo/6/Demo6.FullApi/Models/CI.cs diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/Models/ErrorViewModel.cs b/demo/6/Demo6.FullApi/Models/ErrorViewModel.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/Models/ErrorViewModel.cs rename to demo/6/Demo6.FullApi/Models/ErrorViewModel.cs diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/Program.cs b/demo/6/Demo6.FullApi/Program.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/Program.cs rename to demo/6/Demo6.FullApi/Program.cs diff --git a/src/MaomiFramework/demo/6/Demo6.FullApi/Properties/launchSettings.json b/demo/6/Demo6.FullApi/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.FullApi/Properties/launchSettings.json rename to demo/6/Demo6.FullApi/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/appsettings.Development.json b/demo/6/Demo6.FullApi/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/appsettings.Development.json rename to demo/6/Demo6.FullApi/appsettings.Development.json diff --git a/src/MaomiFramework/demo/7/Demo7.Console/appsettings.json b/demo/6/Demo6.FullApi/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/7/Demo7.Console/appsettings.json rename to demo/6/Demo6.FullApi/appsettings.json diff --git a/src/MaomiFramework/demo/6/Demo6.HttpFactory/Demo6.HttpFactory.csproj b/demo/6/Demo6.HttpFactory/Demo6.HttpFactory.csproj similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.HttpFactory/Demo6.HttpFactory.csproj rename to demo/6/Demo6.HttpFactory/Demo6.HttpFactory.csproj diff --git a/src/MaomiFramework/demo/6/Demo6.HttpFactory/MyDelegatingHandler.cs b/demo/6/Demo6.HttpFactory/MyDelegatingHandler.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.HttpFactory/MyDelegatingHandler.cs rename to demo/6/Demo6.HttpFactory/MyDelegatingHandler.cs diff --git a/src/MaomiFramework/demo/6/Demo6.HttpFactory/MyException.cs b/demo/6/Demo6.HttpFactory/MyException.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.HttpFactory/MyException.cs rename to demo/6/Demo6.HttpFactory/MyException.cs diff --git a/src/MaomiFramework/demo/6/Demo6.HttpFactory/Program.cs b/demo/6/Demo6.HttpFactory/Program.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.HttpFactory/Program.cs rename to demo/6/Demo6.HttpFactory/Program.cs diff --git a/src/MaomiFramework/demo/6/Demo6.Polly/Demo6.Polly.csproj b/demo/6/Demo6.Polly/Demo6.Polly.csproj similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Polly/Demo6.Polly.csproj rename to demo/6/Demo6.Polly/Demo6.Polly.csproj diff --git a/src/MaomiFramework/demo/6/Demo6.Polly/Program.cs b/demo/6/Demo6.Polly/Program.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Polly/Program.cs rename to demo/6/Demo6.Polly/Program.cs diff --git a/src/MaomiFramework/demo/6/Demo6.PollyContrib/Demo6.PollyContrib.csproj b/demo/6/Demo6.PollyContrib/Demo6.PollyContrib.csproj similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.PollyContrib/Demo6.PollyContrib.csproj rename to demo/6/Demo6.PollyContrib/Demo6.PollyContrib.csproj diff --git a/src/MaomiFramework/demo/6/Demo6.PollyContrib/Program.cs b/demo/6/Demo6.PollyContrib/Program.cs similarity index 99% rename from src/MaomiFramework/demo/6/Demo6.PollyContrib/Program.cs rename to demo/6/Demo6.PollyContrib/Program.cs index cbb3e29..28f5699 100644 --- a/src/MaomiFramework/demo/6/Demo6.PollyContrib/Program.cs +++ b/demo/6/Demo6.PollyContrib/Program.cs @@ -17,9 +17,6 @@ static async Task Main(string[] args) var myService = ioc.GetRequiredService(); await myService.GetAsync(); } - - - } public class MyService diff --git a/src/MaomiFramework/demo/6/Demo6.Refit/Demo6.Refit.csproj b/demo/6/Demo6.Refit/Demo6.Refit.csproj similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Refit/Demo6.Refit.csproj rename to demo/6/Demo6.Refit/Demo6.Refit.csproj diff --git a/src/MaomiFramework/demo/6/Demo6.Refit/MyDelegatingHandler.cs b/demo/6/Demo6.Refit/MyDelegatingHandler.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Refit/MyDelegatingHandler.cs rename to demo/6/Demo6.Refit/MyDelegatingHandler.cs diff --git a/demo/6/Demo6.Refit/MyException.cs b/demo/6/Demo6.Refit/MyException.cs new file mode 100644 index 0000000..eafb57e --- /dev/null +++ b/demo/6/Demo6.Refit/MyException.cs @@ -0,0 +1,36 @@ + +public class MyException : Exception +{ + public MyException(HttpRequestMessage request, HttpResponseMessage? response) + : base(CreateMessage(request, response)) + { + } + + public static string CreateMessage(HttpRequestMessage request, HttpResponseMessage? response) + { + string? requestContent = null; + string? responseContent = null; + if (request.Content != null) + { + requestContent = request.Content.ReadAsStringAsync().Result; + } + if (response != null && response.Content != null) + { + responseContent = response.Content.ReadAsStringAsync().Result; + } + + var messsage = + $""" + Request URL:{request.RequestUri} - {request.Method.Method} + Header: {string.Join("\r\n\t", request.Headers.Select(x => $" {x.Key} : {x.Value.FirstOrDefault()}"))} + Request Content: + {requestContent} + --- + Response Status: {response?.StatusCode} ({response?.ReasonPhrase})" + Response Content: + {responseContent} + """; + + return messsage; + } +} diff --git a/src/MaomiFramework/demo/6/Demo6.Refit/Program.cs b/demo/6/Demo6.Refit/Program.cs similarity index 100% rename from src/MaomiFramework/demo/6/Demo6.Refit/Program.cs rename to demo/6/Demo6.Refit/Program.cs diff --git a/src/MaomiFramework/demo/6/mmurl/Program.cs b/demo/6/mmurl/Program.cs similarity index 100% rename from src/MaomiFramework/demo/6/mmurl/Program.cs rename to demo/6/mmurl/Program.cs diff --git a/src/MaomiFramework/demo/6/mmurl/mmurl.csproj b/demo/6/mmurl/mmurl.csproj similarity index 100% rename from src/MaomiFramework/demo/6/mmurl/mmurl.csproj rename to demo/6/mmurl/mmurl.csproj diff --git a/src/MaomiFramework/demo/6/mmurl/packageIcon.png b/demo/6/mmurl/packageIcon.png similarity index 100% rename from src/MaomiFramework/demo/6/mmurl/packageIcon.png rename to demo/6/mmurl/packageIcon.png diff --git a/src/MaomiFramework/demo/7/Demo7.Console/Demo7.Console.csproj b/demo/7/Demo7.Console/Demo7.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/7/Demo7.Console/Demo7.Console.csproj rename to demo/7/Demo7.Console/Demo7.Console.csproj diff --git a/src/MaomiFramework/demo/7/Demo7.Console/Program.cs b/demo/7/Demo7.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/7/Demo7.Console/Program.cs rename to demo/7/Demo7.Console/Program.cs diff --git a/src/MaomiFramework/demo/7/Demo7.Console/Properties/launchSettings.json b/demo/7/Demo7.Console/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/7/Demo7.Console/Properties/launchSettings.json rename to demo/7/Demo7.Console/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/appsettings.json b/demo/7/Demo7.Console/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/appsettings.json rename to demo/7/Demo7.Console/appsettings.json diff --git a/src/MaomiFramework/demo/7/Demo7.Tran/Demo7.Tran.csproj b/demo/7/Demo7.Tran/Demo7.Tran.csproj similarity index 100% rename from src/MaomiFramework/demo/7/Demo7.Tran/Demo7.Tran.csproj rename to demo/7/Demo7.Tran/Demo7.Tran.csproj diff --git a/src/MaomiFramework/demo/7/Demo7.Tran/MyContext.cs b/demo/7/Demo7.Tran/MyContext.cs similarity index 100% rename from src/MaomiFramework/demo/7/Demo7.Tran/MyContext.cs rename to demo/7/Demo7.Tran/MyContext.cs diff --git a/src/MaomiFramework/demo/7/Demo7.Tran/Program.cs b/demo/7/Demo7.Tran/Program.cs similarity index 100% rename from src/MaomiFramework/demo/7/Demo7.Tran/Program.cs rename to demo/7/Demo7.Tran/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.CZGLAOP/Demo8.CZGLAOP.csproj b/demo/8/Demo8.CZGLAOP/Demo8.CZGLAOP.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.CZGLAOP/Demo8.CZGLAOP.csproj rename to demo/8/Demo8.CZGLAOP/Demo8.CZGLAOP.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.CZGLAOP/Program.cs b/demo/8/Demo8.CZGLAOP/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.CZGLAOP/Program.cs rename to demo/8/Demo8.CZGLAOP/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.CZGLAOP/Properties/launchSettings.json b/demo/8/Demo8.CZGLAOP/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.CZGLAOP/Properties/launchSettings.json rename to demo/8/Demo8.CZGLAOP/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/8/Demo8.Console/Demo8.Console.csproj b/demo/8/Demo8.Console/Demo8.Console.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Console/Demo8.Console.csproj rename to demo/8/Demo8.Console/Demo8.Console.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.Console/Program.cs b/demo/8/Demo8.Console/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Console/Program.cs rename to demo/8/Demo8.Console/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Console/Properties/launchSettings.json b/demo/8/Demo8.Console/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Console/Properties/launchSettings.json rename to demo/8/Demo8.Console/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/8/Demo8.FxConsole/App.config b/demo/8/Demo8.FxConsole/App.config similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.FxConsole/App.config rename to demo/8/Demo8.FxConsole/App.config diff --git a/src/MaomiFramework/demo/8/Demo8.FxConsole/Demo8.FxConsole.csproj b/demo/8/Demo8.FxConsole/Demo8.FxConsole.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.FxConsole/Demo8.FxConsole.csproj rename to demo/8/Demo8.FxConsole/Demo8.FxConsole.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.FxConsole/Program.cs b/demo/8/Demo8.FxConsole/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.FxConsole/Program.cs rename to demo/8/Demo8.FxConsole/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.FxConsole/Properties/AssemblyInfo.cs b/demo/8/Demo8.FxConsole/Properties/AssemblyInfo.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.FxConsole/Properties/AssemblyInfo.cs rename to demo/8/Demo8.FxConsole/Properties/AssemblyInfo.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Mapper/Demo8.Mapper.csproj b/demo/8/Demo8.Mapper/Demo8.Mapper.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Mapper/Demo8.Mapper.csproj rename to demo/8/Demo8.Mapper/Demo8.Mapper.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.Mapper/MapperHelper.cs b/demo/8/Demo8.Mapper/MapperHelper.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Mapper/MapperHelper.cs rename to demo/8/Demo8.Mapper/MapperHelper.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Mapper/Program.cs b/demo/8/Demo8.Mapper/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Mapper/Program.cs rename to demo/8/Demo8.Mapper/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Natasha/Demo8.Natasha.csproj b/demo/8/Demo8.Natasha/Demo8.Natasha.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Natasha/Demo8.Natasha.csproj rename to demo/8/Demo8.Natasha/Demo8.Natasha.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.Natasha/Program.cs b/demo/8/Demo8.Natasha/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Natasha/Program.cs rename to demo/8/Demo8.Natasha/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Natasha/Properties/launchSettings.json b/demo/8/Demo8.Natasha/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Natasha/Properties/launchSettings.json rename to demo/8/Demo8.Natasha/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/8/Demo8.ORM/Demo8.ORM.csproj b/demo/8/Demo8.ORM/Demo8.ORM.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.ORM/Demo8.ORM.csproj rename to demo/8/Demo8.ORM/Demo8.ORM.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.ORM/MyDBContext.cs b/demo/8/Demo8.ORM/MyDBContext.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.ORM/MyDBContext.cs rename to demo/8/Demo8.ORM/MyDBContext.cs diff --git a/src/MaomiFramework/demo/8/Demo8.ORM/MyDBContext`.cs b/demo/8/Demo8.ORM/MyDBContext`.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.ORM/MyDBContext`.cs rename to demo/8/Demo8.ORM/MyDBContext`.cs diff --git a/src/MaomiFramework/demo/8/Demo8.ORM/Program.cs b/demo/8/Demo8.ORM/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.ORM/Program.cs rename to demo/8/Demo8.ORM/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.ORM/test.sql b/demo/8/Demo8.ORM/test.sql similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.ORM/test.sql rename to demo/8/Demo8.ORM/test.sql diff --git a/src/MaomiFramework/demo/8/Demo8.Roslyn/CompilationBuilder.cs b/demo/8/Demo8.Roslyn/CompilationBuilder.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Roslyn/CompilationBuilder.cs rename to demo/8/Demo8.Roslyn/CompilationBuilder.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Roslyn/Demo8.Roslyn.csproj b/demo/8/Demo8.Roslyn/Demo8.Roslyn.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Roslyn/Demo8.Roslyn.csproj rename to demo/8/Demo8.Roslyn/Demo8.Roslyn.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.Roslyn/DomainOptionBuilder.cs b/demo/8/Demo8.Roslyn/DomainOptionBuilder.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Roslyn/DomainOptionBuilder.cs rename to demo/8/Demo8.Roslyn/DomainOptionBuilder.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Roslyn/DomainOptions.cs b/demo/8/Demo8.Roslyn/DomainOptions.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Roslyn/DomainOptions.cs rename to demo/8/Demo8.Roslyn/DomainOptions.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Roslyn/Program.cs b/demo/8/Demo8.Roslyn/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Roslyn/Program.cs rename to demo/8/Demo8.Roslyn/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.Roslyn/Properties/launchSettings.json b/demo/8/Demo8.Roslyn/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.Roslyn/Properties/launchSettings.json rename to demo/8/Demo8.Roslyn/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/8/Demo8.SGBuild/Demo8.SGBuild.csproj b/demo/8/Demo8.SGBuild/Demo8.SGBuild.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.SGBuild/Demo8.SGBuild.csproj rename to demo/8/Demo8.SGBuild/Demo8.SGBuild.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.SGBuild/MySourceGenerator.cs b/demo/8/Demo8.SGBuild/MySourceGenerator.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.SGBuild/MySourceGenerator.cs rename to demo/8/Demo8.SGBuild/MySourceGenerator.cs diff --git a/src/MaomiFramework/demo/8/Demo8.SGBuild/Properties/launchSettings.json b/demo/8/Demo8.SGBuild/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.SGBuild/Properties/launchSettings.json rename to demo/8/Demo8.SGBuild/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/8/Demo8.UseSG/Demo8.UseSG.csproj b/demo/8/Demo8.UseSG/Demo8.UseSG.csproj similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.UseSG/Demo8.UseSG.csproj rename to demo/8/Demo8.UseSG/Demo8.UseSG.csproj diff --git a/src/MaomiFramework/demo/8/Demo8.UseSG/Program.cs b/demo/8/Demo8.UseSG/Program.cs similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.UseSG/Program.cs rename to demo/8/Demo8.UseSG/Program.cs diff --git a/src/MaomiFramework/demo/8/Demo8.UseSG/Properties/launchSettings.json b/demo/8/Demo8.UseSG/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/8/Demo8.UseSG/Properties/launchSettings.json rename to demo/8/Demo8.UseSG/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/ApiModule.cs b/demo/9/Demo9.ActionFilter1/ApiModule.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/ApiModule.cs rename to demo/9/Demo9.ActionFilter1/ApiModule.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/Controllers/IndexController.cs b/demo/9/Demo9.ActionFilter1/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/Controllers/IndexController.cs rename to demo/9/Demo9.ActionFilter1/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/Controllers/UserInfo.cs b/demo/9/Demo9.ActionFilter1/Controllers/UserInfo.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/Controllers/UserInfo.cs rename to demo/9/Demo9.ActionFilter1/Controllers/UserInfo.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/Demo9.ActionFilter1.csproj b/demo/9/Demo9.ActionFilter1/Demo9.ActionFilter1.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/Demo9.ActionFilter1.csproj rename to demo/9/Demo9.ActionFilter1/Demo9.ActionFilter1.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/Program.cs b/demo/9/Demo9.ActionFilter1/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/Program.cs rename to demo/9/Demo9.ActionFilter1/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/Properties/launchSettings.json b/demo/9/Demo9.ActionFilter1/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/Properties/launchSettings.json rename to demo/9/Demo9.ActionFilter1/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/appsettings.Development.json b/demo/9/Demo9.ActionFilter1/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/appsettings.Development.json rename to demo/9/Demo9.ActionFilter1/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/appsettings.json b/demo/9/Demo9.ActionFilter1/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/appsettings.json rename to demo/9/Demo9.ActionFilter1/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/en-US.json b/demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/en-US.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/en-US.json rename to demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/en-US.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/zh-CN.json b/demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/zh-CN.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/zh-CN.json rename to demo/9/Demo9.ActionFilter1/i18n/Demo10.ActionFilter1/zh-CN.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/ApiModule.cs b/demo/9/Demo9.ActionFilter2/ApiModule.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/ApiModule.cs rename to demo/9/Demo9.ActionFilter2/ApiModule.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/Controllers/IndexController.cs b/demo/9/Demo9.ActionFilter2/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/Controllers/IndexController.cs rename to demo/9/Demo9.ActionFilter2/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/Controllers/UserInfo.cs b/demo/9/Demo9.ActionFilter2/Controllers/UserInfo.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/Controllers/UserInfo.cs rename to demo/9/Demo9.ActionFilter2/Controllers/UserInfo.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/Demo9.ActionFilter2.csproj b/demo/9/Demo9.ActionFilter2/Demo9.ActionFilter2.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/Demo9.ActionFilter2.csproj rename to demo/9/Demo9.ActionFilter2/Demo9.ActionFilter2.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/Program.cs b/demo/9/Demo9.ActionFilter2/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/Program.cs rename to demo/9/Demo9.ActionFilter2/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/Properties/launchSettings.json b/demo/9/Demo9.ActionFilter2/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/Properties/launchSettings.json rename to demo/9/Demo9.ActionFilter2/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/appsettings.Development.json b/demo/9/Demo9.ActionFilter2/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ExceptionFilter/appsettings.Development.json rename to demo/9/Demo9.ActionFilter2/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/appsettings.json b/demo/9/Demo9.ActionFilter2/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/appsettings.json rename to demo/9/Demo9.ActionFilter2/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/en-US.json b/demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/en-US.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/en-US.json rename to demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/en-US.json diff --git a/src/MaomiFramework/demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/zh-CN.json b/demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/zh-CN.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/zh-CN.json rename to demo/9/Demo9.ActionFilter2/i18n/Demo10.ActionFilter2/zh-CN.json diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/ApiModule.cs b/demo/9/Demo9.ApiDataAnnotations/ApiModule.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/ApiModule.cs rename to demo/9/Demo9.ApiDataAnnotations/ApiModule.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Controllers/IndexController.cs b/demo/9/Demo9.ApiDataAnnotations/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Controllers/IndexController.cs rename to demo/9/Demo9.ApiDataAnnotations/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Controllers/UserInfo.cs b/demo/9/Demo9.ApiDataAnnotations/Controllers/UserInfo.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Controllers/UserInfo.cs rename to demo/9/Demo9.ApiDataAnnotations/Controllers/UserInfo.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Demo9.ApiDataAnnotations.csproj b/demo/9/Demo9.ApiDataAnnotations/Demo9.ApiDataAnnotations.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Demo9.ApiDataAnnotations.csproj rename to demo/9/Demo9.ApiDataAnnotations/Demo9.ApiDataAnnotations.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Program.cs b/demo/9/Demo9.ApiDataAnnotations/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Program.cs rename to demo/9/Demo9.ApiDataAnnotations/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Properties/launchSettings.json b/demo/9/Demo9.ApiDataAnnotations/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/Properties/launchSettings.json rename to demo/9/Demo9.ApiDataAnnotations/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/appsettings.Development.json b/demo/9/Demo9.ApiDataAnnotations/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.MaomiSwagger/appsettings.Development.json rename to demo/9/Demo9.ApiDataAnnotations/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/appsettings.json b/demo/9/Demo9.ApiDataAnnotations/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ExceptionFilter/appsettings.json rename to demo/9/Demo9.ApiDataAnnotations/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/en-US.json b/demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/en-US.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/en-US.json rename to demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/en-US.json diff --git a/src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/zh-CN.json b/demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/zh-CN.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/zh-CN.json rename to demo/9/Demo9.ApiDataAnnotations/i18n/Demo10.ApiDataAnnotations/zh-CN.json diff --git a/src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/Demo9.ConsoleDataAnnotations.csproj b/demo/9/Demo9.ConsoleDataAnnotations/Demo9.ConsoleDataAnnotations.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/Demo9.ConsoleDataAnnotations.csproj rename to demo/9/Demo9.ConsoleDataAnnotations/Demo9.ConsoleDataAnnotations.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/MaomiEmailAttribute.cs b/demo/9/Demo9.ConsoleDataAnnotations/MaomiEmailAttribute.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/MaomiEmailAttribute.cs rename to demo/9/Demo9.ConsoleDataAnnotations/MaomiEmailAttribute.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/Program.cs b/demo/9/Demo9.ConsoleDataAnnotations/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/Program.cs rename to demo/9/Demo9.ConsoleDataAnnotations/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/Properties/launchSettings.json b/demo/9/Demo9.ConsoleDataAnnotations/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/Properties/launchSettings.json rename to demo/9/Demo9.ConsoleDataAnnotations/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/UserInfo.cs b/demo/9/Demo9.ConsoleDataAnnotations/UserInfo.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ConsoleDataAnnotations/UserInfo.cs rename to demo/9/Demo9.ConsoleDataAnnotations/UserInfo.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/ApiModule.cs b/demo/9/Demo9.ExceptionFilter/ApiModule.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ExceptionFilter/ApiModule.cs rename to demo/9/Demo9.ExceptionFilter/ApiModule.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Controllers/TestController.cs b/demo/9/Demo9.ExceptionFilter/Controllers/TestController.cs similarity index 95% rename from src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Controllers/TestController.cs rename to demo/9/Demo9.ExceptionFilter/Controllers/TestController.cs index 9de2e61..2f34e45 100644 --- a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Controllers/TestController.cs +++ b/demo/9/Demo9.ExceptionFilter/Controllers/TestController.cs @@ -1,7 +1,7 @@ using Maomi.Web.Core; using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.Mvc; -using System.Net; +using Maomi.Attributes; namespace Demo9.ExceptionFilter.Controllers { diff --git a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Demo9.ExceptionFilter.csproj b/demo/9/Demo9.ExceptionFilter/Demo9.ExceptionFilter.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Demo9.ExceptionFilter.csproj rename to demo/9/Demo9.ExceptionFilter/Demo9.ExceptionFilter.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Program.cs b/demo/9/Demo9.ExceptionFilter/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Program.cs rename to demo/9/Demo9.ExceptionFilter/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Properties/launchSettings.json b/demo/9/Demo9.ExceptionFilter/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ExceptionFilter/Properties/launchSettings.json rename to demo/9/Demo9.ExceptionFilter/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/appsettings.Development.json b/demo/9/Demo9.ExceptionFilter/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/appsettings.Development.json rename to demo/9/Demo9.ExceptionFilter/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/appsettings.json b/demo/9/Demo9.ExceptionFilter/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.MaomiSwagger/appsettings.json rename to demo/9/Demo9.ExceptionFilter/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Controllers/IndexController.cs b/demo/9/Demo9.MaomiSwagger/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Controllers/IndexController.cs rename to demo/9/Demo9.MaomiSwagger/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.csproj b/demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.csproj rename to demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.http b/demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.http similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.http rename to demo/9/Demo9.MaomiSwagger/Demo9.MaomiSwagger.http diff --git a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Program.cs b/demo/9/Demo9.MaomiSwagger/Program.cs similarity index 94% rename from src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Program.cs rename to demo/9/Demo9.MaomiSwagger/Program.cs index 9cfb297..798b4b7 100644 --- a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Program.cs +++ b/demo/9/Demo9.MaomiSwagger/Program.cs @@ -14,7 +14,7 @@ public static void Main(string[] args) builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); - // 1ע + // 1,这里注入 builder.Services.AddMaomiSwaggerGen(); var app = builder.Build(); @@ -22,7 +22,7 @@ public static void Main(string[] args) // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - // 2м + // 2,这里配置中间件 app.UseMaomiSwagger(setupAction: setup => { setup.PreSerializeFilters.Add((swagger, httpReq) => diff --git a/src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Properties/launchSettings.json b/demo/9/Demo9.MaomiSwagger/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.MaomiSwagger/Properties/launchSettings.json rename to demo/9/Demo9.MaomiSwagger/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.Swagger/appsettings.Development.json b/demo/9/Demo9.MaomiSwagger/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.Swagger/appsettings.Development.json rename to demo/9/Demo9.MaomiSwagger/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/appsettings.json b/demo/9/Demo9.MaomiSwagger/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/appsettings.json rename to demo/9/Demo9.MaomiSwagger/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/ApiModule.cs b/demo/9/Demo9.ResourceFilter/ApiModule.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/ApiModule.cs rename to demo/9/Demo9.ResourceFilter/ApiModule.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/Controllers/CacheResourceFilter.cs b/demo/9/Demo9.ResourceFilter/Controllers/CacheResourceFilter.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/Controllers/CacheResourceFilter.cs rename to demo/9/Demo9.ResourceFilter/Controllers/CacheResourceFilter.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/Controllers/ExceptionFilter.cs b/demo/9/Demo9.ResourceFilter/Controllers/ExceptionFilter.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/Controllers/ExceptionFilter.cs rename to demo/9/Demo9.ResourceFilter/Controllers/ExceptionFilter.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/Controllers/TestController.cs b/demo/9/Demo9.ResourceFilter/Controllers/TestController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/Controllers/TestController.cs rename to demo/9/Demo9.ResourceFilter/Controllers/TestController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/Demo9.ResourceFilter.csproj b/demo/9/Demo9.ResourceFilter/Demo9.ResourceFilter.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/Demo9.ResourceFilter.csproj rename to demo/9/Demo9.ResourceFilter/Demo9.ResourceFilter.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/Program.cs b/demo/9/Demo9.ResourceFilter/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/Program.cs rename to demo/9/Demo9.ResourceFilter/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.ResourceFilter/Properties/launchSettings.json b/demo/9/Demo9.ResourceFilter/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.ResourceFilter/Properties/launchSettings.json rename to demo/9/Demo9.ResourceFilter/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/appsettings.Development.json b/demo/9/Demo9.ResourceFilter/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerModel/appsettings.Development.json rename to demo/9/Demo9.ResourceFilter/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.Swagger/appsettings.json b/demo/9/Demo9.ResourceFilter/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.Swagger/appsettings.json rename to demo/9/Demo9.ResourceFilter/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.Swagger/Controllers/IndexController.cs b/demo/9/Demo9.Swagger/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.Swagger/Controllers/IndexController.cs rename to demo/9/Demo9.Swagger/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.Swagger/Demo9.Swagger.csproj b/demo/9/Demo9.Swagger/Demo9.Swagger.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.Swagger/Demo9.Swagger.csproj rename to demo/9/Demo9.Swagger/Demo9.Swagger.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.Swagger/Program.cs b/demo/9/Demo9.Swagger/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.Swagger/Program.cs rename to demo/9/Demo9.Swagger/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.Swagger/Properties/launchSettings.json b/demo/9/Demo9.Swagger/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.Swagger/Properties/launchSettings.json rename to demo/9/Demo9.Swagger/Properties/launchSettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerVersion/appsettings.Development.json b/demo/9/Demo9.Swagger/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerVersion/appsettings.Development.json rename to demo/9/Demo9.Swagger/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/appsettings.json b/demo/9/Demo9.Swagger/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerModel/appsettings.json rename to demo/9/Demo9.Swagger/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/Controllers/Test.cs b/demo/9/Demo9.SwaggerModel/Controllers/Test.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerModel/Controllers/Test.cs rename to demo/9/Demo9.SwaggerModel/Controllers/Test.cs diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/Controllers/TestController.cs b/demo/9/Demo9.SwaggerModel/Controllers/TestController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerModel/Controllers/TestController.cs rename to demo/9/Demo9.SwaggerModel/Controllers/TestController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/Demo9.SwaggerModel.csproj b/demo/9/Demo9.SwaggerModel/Demo9.SwaggerModel.csproj similarity index 66% rename from src/MaomiFramework/demo/9/Demo9.SwaggerModel/Demo9.SwaggerModel.csproj rename to demo/9/Demo9.SwaggerModel/Demo9.SwaggerModel.csproj index ffa1331..7d175c6 100644 --- a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/Demo9.SwaggerModel.csproj +++ b/demo/9/Demo9.SwaggerModel/Demo9.SwaggerModel.csproj @@ -9,10 +9,9 @@ - - + diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/Program.cs b/demo/9/Demo9.SwaggerModel/Program.cs similarity index 83% rename from src/MaomiFramework/demo/9/Demo9.SwaggerModel/Program.cs rename to demo/9/Demo9.SwaggerModel/Program.cs index fe872f3..62e47c5 100644 --- a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/Program.cs +++ b/demo/9/Demo9.SwaggerModel/Program.cs @@ -4,17 +4,17 @@ builder.Services.AddControllers(); -// 1ע +// 1,这里注入 builder.Services.AddSwaggerGen(options => { - // ģ + // 模型类过滤器 options.SchemaFilter(); }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { - // 2м + // 2,这里配置中间件 app.UseSwagger(); app.UseSwaggerUI(); } diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerModel/Properties/launchSettings.json b/demo/9/Demo9.SwaggerModel/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerModel/Properties/launchSettings.json rename to demo/9/Demo9.SwaggerModel/Properties/launchSettings.json diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/appsettings.Development.json b/demo/9/Demo9.SwaggerModel/appsettings.Development.json similarity index 100% rename from src/MaomiFramework/templates/MaomiDemo.Api/appsettings.Development.json rename to demo/9/Demo9.SwaggerModel/appsettings.Development.json diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerVersion/appsettings.json b/demo/9/Demo9.SwaggerModel/appsettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerVersion/appsettings.json rename to demo/9/Demo9.SwaggerModel/appsettings.json diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Controllers/IndexController.cs b/demo/9/Demo9.SwaggerVersion/Controllers/IndexController.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Controllers/IndexController.cs rename to demo/9/Demo9.SwaggerVersion/Controllers/IndexController.cs diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Demo9.SwaggerVersion.csproj b/demo/9/Demo9.SwaggerVersion/Demo9.SwaggerVersion.csproj similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Demo9.SwaggerVersion.csproj rename to demo/9/Demo9.SwaggerVersion/Demo9.SwaggerVersion.csproj diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Program.cs b/demo/9/Demo9.SwaggerVersion/Program.cs similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Program.cs rename to demo/9/Demo9.SwaggerVersion/Program.cs diff --git a/src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Properties/launchSettings.json b/demo/9/Demo9.SwaggerVersion/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/demo/9/Demo9.SwaggerVersion/Properties/launchSettings.json rename to demo/9/Demo9.SwaggerVersion/Properties/launchSettings.json diff --git a/demo/9/Demo9.SwaggerVersion/appsettings.Development.json b/demo/9/Demo9.SwaggerVersion/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/demo/9/Demo9.SwaggerVersion/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/appsettings.json b/demo/9/Demo9.SwaggerVersion/appsettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/appsettings.json rename to demo/9/Demo9.SwaggerVersion/appsettings.json diff --git a/framework/Maomi.Core/Abstractions/IModule.cs b/framework/Maomi.Core/Abstractions/IModule.cs new file mode 100644 index 0000000..6fcc59f --- /dev/null +++ b/framework/Maomi.Core/Abstractions/IModule.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi; + +/// +/// 模块接口. +/// +public interface IModule +{ + /// + /// 模块中的依赖注入. + /// + /// 模块服务上下文. + void ConfigureServices(ServiceContext context); +} diff --git a/framework/Maomi.Core/Abstractions/ModuleCore.cs b/framework/Maomi.Core/Abstractions/ModuleCore.cs new file mode 100644 index 0000000..81061ef --- /dev/null +++ b/framework/Maomi.Core/Abstractions/ModuleCore.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 模块过滤器接口. +/// +public abstract class ModuleCore : IModule +{ + /// + public abstract void ConfigureServices(ServiceContext context); + + /// + /// 扫描每个类型时会调用该接口. + /// + /// + public abstract void TypeFilter(Type type); +} \ No newline at end of file diff --git a/framework/Maomi.Core/Attributes/InjectModuleAttribute.cs b/framework/Maomi.Core/Attributes/InjectModuleAttribute.cs new file mode 100644 index 0000000..d0d4fee --- /dev/null +++ b/framework/Maomi.Core/Attributes/InjectModuleAttribute.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi; + +/// +/// 注册依赖的模块. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] +public class InjectModuleAttribute : Attribute +{ + /// + /// 依赖的模块. + /// + public Type ModuleType { get; private init; } + + /// + /// Initializes a new instance of the class. + /// + /// + public InjectModuleAttribute(Type type) + { + ModuleType = type; + } +} \ No newline at end of file diff --git a/framework/Maomi.Core/Attributes/InjectModuleAttribute{TModule}.cs b/framework/Maomi.Core/Attributes/InjectModuleAttribute{TModule}.cs new file mode 100644 index 0000000..90d42f7 --- /dev/null +++ b/framework/Maomi.Core/Attributes/InjectModuleAttribute{TModule}.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi; + +/// +/// 注册依赖的模块. +/// +/// 模块类. +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] +public sealed class InjectModuleAttribute : InjectModuleAttribute + where TModule : IModule +{ + /// + /// Initializes a new instance of the class. + /// + public InjectModuleAttribute() + : base(typeof(TModule)) + { + } +} diff --git a/framework/Maomi.Core/Attributes/InjectOnAttribute.cs b/framework/Maomi.Core/Attributes/InjectOnAttribute.cs new file mode 100644 index 0000000..eb15a4b --- /dev/null +++ b/framework/Maomi.Core/Attributes/InjectOnAttribute.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 将当前类型自动注册到容器中. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InjectOnAttribute : Attribute +{ + /// + /// 要注册的服务类型. + /// + public Type[]? ServiceTypes { get; set; } = Array.Empty(); + + /// + /// 服务的生命周期. + /// + public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Scoped; + + /// + /// 服务注册模式. + /// + public InjectScheme Scheme { get; set; } + + /// + /// 将自己也注册到容器中. + /// + public bool Own { get; set; } = false; + + /// + /// ServiceKey. + /// + public object? ServiceKey { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// 服务生命周期. + /// 服务注册模式. + public InjectOnAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped, InjectScheme scheme = InjectScheme.OnlyInterfaces) + { + Lifetime = lifetime; + Scheme = scheme; + } +} diff --git a/framework/Maomi.Core/Attributes/InjectOnScopedAttribute.cs b/framework/Maomi.Core/Attributes/InjectOnScopedAttribute.cs new file mode 100644 index 0000000..efbcc27 --- /dev/null +++ b/framework/Maomi.Core/Attributes/InjectOnScopedAttribute.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 将当前类型自动注册到容器中. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InjectOnScopedAttribute : InjectOnAttribute +{ + /// + /// Initializes a new instance of the class. + /// + /// 服务注册模式. + public InjectOnScopedAttribute(InjectScheme scheme = InjectScheme.OnlyInterfaces) + : base(ServiceLifetime.Scoped, scheme) + { + } +} diff --git a/framework/Maomi.Core/Attributes/InjectOnSingletonAttribute.cs b/framework/Maomi.Core/Attributes/InjectOnSingletonAttribute.cs new file mode 100644 index 0000000..8563883 --- /dev/null +++ b/framework/Maomi.Core/Attributes/InjectOnSingletonAttribute.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 将当前类型自动注册到容器中. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InjectOnSingletonAttribute : InjectOnAttribute +{ + /// + /// Initializes a new instance of the class. + /// + /// 服务注册模式. + public InjectOnSingletonAttribute(InjectScheme scheme = InjectScheme.OnlyInterfaces) + : base(ServiceLifetime.Singleton, scheme) + { + } +} diff --git a/framework/Maomi.Core/Attributes/InjectOnTransientAttribute.cs b/framework/Maomi.Core/Attributes/InjectOnTransientAttribute.cs new file mode 100644 index 0000000..bf39f55 --- /dev/null +++ b/framework/Maomi.Core/Attributes/InjectOnTransientAttribute.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 将当前类型自动注册到容器中. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InjectOnTransientAttribute : InjectOnAttribute +{ + /// + /// Initializes a new instance of the class. + /// + /// 服务注册模式. + public InjectOnTransientAttribute(InjectScheme scheme = InjectScheme.OnlyInterfaces) + : base(ServiceLifetime.Transient, scheme) + { + } +} diff --git a/framework/Maomi.Core/DefaultServiceContext.cs b/framework/Maomi.Core/DefaultServiceContext.cs new file mode 100644 index 0000000..e2eb6e2 --- /dev/null +++ b/framework/Maomi.Core/DefaultServiceContext.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +#pragma warning disable CS1591 +#pragma warning disable SA1401 +#pragma warning disable SA1600 + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 模块上下文. +/// +public class DefaultServiceContext : ServiceContext +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// + public DefaultServiceContext(IServiceCollection serviceCollection, IConfiguration configuration) + : base(serviceCollection, configuration) + { + } + + public void AddModule(ModuleRecord initRecord) + { + _modules.Add(initRecord); + } +} \ No newline at end of file diff --git a/framework/Maomi.Core/InjectScheme.cs b/framework/Maomi.Core/InjectScheme.cs new file mode 100644 index 0000000..774b5b6 --- /dev/null +++ b/framework/Maomi.Core/InjectScheme.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 服务注册模式. +/// +public enum InjectScheme +{ + /// + /// 包括父类、接口. + /// + /// public class C: CBase, IC {}
+ /// services.AddScoped<CBase, C>();
+ /// services.AddScoped<IC, C>();
+ ///
+ ///
+ Any, + + /// + /// 自定义 服务列表. + /// + Some, + + /// + /// 只注入父类. + /// + OnlyBaseClass, + + /// + /// 只注册接口. + /// + OnlyInterfaces, + + /// + /// 此服务不会被注入到容器中. + /// + None +} diff --git a/framework/Maomi.Core/Maomi.Core.csproj b/framework/Maomi.Core/Maomi.Core.csproj new file mode 100644 index 0000000..56ac4da --- /dev/null +++ b/framework/Maomi.Core/Maomi.Core.csproj @@ -0,0 +1,66 @@ + + + + Library + net7.0;net8.0;net9.0 + enable + enable + Maomi + true + 2.2.0 + Codestin Search App + Maomi 框架是一个简洁的 .NET 开发框架,具有模块化、自动服务注册等功能。 + True + true + MIT + package.png + https://maonmi.whuanle.cn + https://github.com/whuanle/maonmi + README.md + git + + Maomi 框架是一个简洁的 .NET 开发框架,具有模块化、自动服务注册等功能。 + + True + snupkg + True + + + + + + + + + + + + + True + \ + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + diff --git a/framework/Maomi.Core/ModuleBuilder.cs b/framework/Maomi.Core/ModuleBuilder.cs new file mode 100644 index 0000000..6b6cb3e --- /dev/null +++ b/framework/Maomi.Core/ModuleBuilder.cs @@ -0,0 +1,326 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +#pragma warning disable CS1591 +#pragma warning disable SA1401 +#pragma warning disable SA1600 + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace Maomi; + +/// +/// 模块构建服务. +/// +public partial class ModuleBuilder +{ + protected readonly ModuleBuilderParamters _modulerParamters; + protected readonly IServiceCollection _services; + protected readonly IServiceProvider _serviceProvider; + protected readonly DefaultServiceContext _serviceContext; + + // 已经初始化的模块 + protected readonly HashSet _initializedModules = new(); + + // 已经初始化的程序集 + protected readonly HashSet _initializedAssemblys = new(); + + // 所有模块类实例 + protected readonly HashSet _moduleInstances = new(); + + // ModuleCore 模块类实例 + protected readonly HashSet _moduleCoreInstances = new(); + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public ModuleBuilder(IServiceCollection services, ModuleBuilderParamters modulerParamters) + { + _services = services; + _modulerParamters = modulerParamters; + + foreach (var module in _modulerParamters.ModuleTypes) + { + _services.AddSingleton(module); + } + + // 初始化配置 + _serviceProvider = _services.BuildServiceProvider(); + _serviceContext = new DefaultServiceContext(_services, _serviceProvider.GetService()!); + + // 将程序集模块添加到上下文中 + foreach (var moduleType in _modulerParamters.ModuleTypes) + { + _serviceContext.AddModule(new ModuleRecord + { + Type = moduleType, + Assembly = moduleType.Assembly + }); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public ModuleBuilder(IServiceCollection services, ModuleOptions options, Type rootModuleType) + : this(services, new ModuleBuilderParamters(options, rootModuleType)) + { + } + + /// + /// 构建模块. + /// + public virtual void Build() + { + var moduleTypes = _modulerParamters.ModuleTypes; + var customModuleTypes = _modulerParamters.CustomModuleTypes; + var rootModuleNode = _modulerParamters.RootModuleNode; + + // 开始初始化模块依赖树 + InstantiationModuleTree(_modulerParamters.RootModuleNode); + + // 首先实例化程序集中的模块类 + foreach (var moduleType in customModuleTypes) + { + if (_initializedModules.Contains(moduleType)) + { + continue; + } + + InstantiationModule(moduleType); + _initializedModules.Add(moduleType); + } + + foreach (var moduleCore in _moduleInstances.OfType().ToHashSet()) + { + _moduleCoreInstances.Add(moduleCore); + } + + // 扫描模块树中的程序集 + ScanServiceFromModuleTree(rootModuleNode); + + // 首先扫描程序集中的类型 + foreach (var moduleType in customModuleTypes) + { + if (_initializedAssemblys.Contains(moduleType.Assembly)) + { + continue; + } + + ScanServiceFromAssembly(moduleType.Assembly); + _initializedAssemblys.Add(moduleType.Assembly); + } + } + + /// + /// 实例化模块树. + /// + /// + protected virtual void InstantiationModuleTree(ModuleNode currentNode) + { + foreach (var childNode in currentNode.Childs) + { + if (_initializedModules.Contains(childNode.ModuleType)) + { + continue; + } + + InstantiationModuleTree(childNode); + } + + InstantiationModule(currentNode.ModuleType); + } + + /// + /// 从模块树中扫描程序集. + /// + /// + protected virtual void ScanServiceFromModuleTree(ModuleNode currentNode) + { + foreach (var childNode in currentNode.Childs) + { + ScanServiceFromModuleTree(childNode); + } + + // 跳过重复扫描的程序集 + if (_initializedAssemblys.Contains(currentNode.ModuleType.Assembly)) + { + return; + } + + ScanServiceFromAssembly(currentNode.ModuleType.Assembly); + _initializedAssemblys.Add(currentNode.ModuleType.Assembly); + } + + /// + /// 实例化模块类. + /// + /// + protected virtual void InstantiationModule(Type moduleType) + { + // 实例化此模块 + var module = (IModule)_serviceProvider.GetRequiredService(moduleType); + module.ConfigureServices(_serviceContext); + _moduleInstances.Add(module); + _initializedModules.Add(moduleType); + } + + /// + /// 扫描此模块(程序集)中需要依赖注入的服务. + /// + /// + protected virtual void ScanServiceFromAssembly(Assembly assembly) + { + // 只扫描可实例化的类,不扫描静态类、接口、抽象类、嵌套类、非公开类等 + foreach (var currentType in assembly.GetTypes().Where(x => x.IsClass && !x.IsAbstract && !x.IsNestedPublic)) + { + foreach (var filter in _moduleCoreInstances) + { + filter.TypeFilter(currentType); + } + + if (currentType.IsAssignableTo(typeof(IModule))) + { + continue; + } + + var inject = currentType.GetCustomAttributes().OfType().FirstOrDefault(); + if (inject == null) + { + continue; + } + + // 将自身注册到容器中 + bool hasInterface = false; + bool hasBaseClass = false; + + // 不注册任何服务 + if (inject.Scheme == InjectScheme.None) + { + if (inject.Own) + { + RegisterService(inject, currentType, currentType); + } + + continue; + } + + // 注册接口 + if (inject.Scheme == InjectScheme.OnlyInterfaces || inject.Scheme == InjectScheme.Any) + { + var interfaces = currentType.GetInterfaces().Where(x => !_modulerParamters.Options.FilterServiceTypes.Contains(x)).ToList(); + + if (interfaces.Count == 0) + { + hasInterface = false; + } + else + { + foreach (var interfaceType in interfaces) + { + RegisterService(inject, interfaceType, currentType); + } + } + } + + // 注册父类 + if (inject.Scheme == InjectScheme.OnlyBaseClass || inject.Scheme == InjectScheme.Any) + { + var baseType = currentType.BaseType; + if (baseType == typeof(object) || baseType == null || _modulerParamters.Options.FilterServiceTypes.Contains(baseType)) + { + hasBaseClass = false; + } + else + { + RegisterService(inject, baseType, currentType); + } + } + + // 自定义注册时,其它规则失效 + if (inject.Scheme == InjectScheme.Some) + { + if (inject.ServiceTypes == null) + { + continue; + } + + foreach (var interfaceType in inject.ServiceTypes) + { + RegisterService(inject, interfaceType, currentType); + } + + continue; + } + + // 当没有找到任何父类或者可用接口时,将自身注册到容器中 + if (inject.Own || (!hasBaseClass && !hasInterface)) + { + RegisterService(inject, currentType, currentType); + } + } + } + + /// + /// 注册服务. + /// + /// + /// + /// + protected virtual void RegisterService(InjectOnAttribute injectOnAttribute, Type serviceType, Type implementationType) + { + if (injectOnAttribute.ServiceKey != null) + { + AddKeyedService(injectOnAttribute.Lifetime, injectOnAttribute.ServiceKey, serviceType, implementationType); + } + else + { + AddService(injectOnAttribute.Lifetime, serviceType, implementationType); + } + } + + /// + /// 注册服务. + /// + /// + /// + /// + protected virtual void AddService(ServiceLifetime lifetime, Type serviceType, Type implementationType) + { + switch (lifetime) + { + case ServiceLifetime.Transient: _services.AddTransient(serviceType, implementationType); break; + case ServiceLifetime.Scoped: _services.AddScoped(serviceType, implementationType); break; + case ServiceLifetime.Singleton: _services.AddSingleton(serviceType, implementationType); break; + } + } + + /// + /// 注册服务. + /// + /// + /// + /// + /// + protected virtual void AddKeyedService(ServiceLifetime lifetime, object key, Type serviceType, Type implementationType) + { +#if NET8_0_OR_GREATER + switch (lifetime) + { + case ServiceLifetime.Transient: _services.AddKeyedTransient(serviceKey: key, serviceType: serviceType, implementationType: implementationType); break; + case ServiceLifetime.Scoped: _services.AddKeyedScoped(serviceKey: key, serviceType: serviceType, implementationType: implementationType); break; + case ServiceLifetime.Singleton: _services.AddKeyedSingleton(serviceKey: key, serviceType: serviceType, implementationType: implementationType); break; + } +#endif + } +} \ No newline at end of file diff --git a/framework/Maomi.Core/ModuleBuilderParamters.Static.cs b/framework/Maomi.Core/ModuleBuilderParamters.Static.cs new file mode 100644 index 0000000..ee181b4 --- /dev/null +++ b/framework/Maomi.Core/ModuleBuilderParamters.Static.cs @@ -0,0 +1,157 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Reflection; + +namespace Maomi; + +/// +/// 模块构建器参数. +/// +public class ModuleBuilderParamters +{ + private readonly ModuleOptions _options; + private readonly ModuleNode _rootModuleNode; + private readonly HashSet _moduleTypes = new(); + private readonly HashSet _customModuleTypes = new(); + + /// + /// 初始化配置. + /// + public ModuleOptions Options => _options; + + /// + /// 模块依赖树. + /// + public ModuleNode RootModuleNode => _rootModuleNode; + + /// + /// 所有模块类. + /// + public HashSet ModuleTypes => _moduleTypes; + + /// + /// 自定义的模块类. + /// + public HashSet CustomModuleTypes => _customModuleTypes; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public ModuleBuilderParamters(ModuleOptions options, Type rootModuleType) + { + _options = options; + _rootModuleNode = new ModuleNode(rootModuleType); + + // 构建模块依赖树 + BuildModuleTree(_moduleTypes, _rootModuleNode); + + // 导入自定义模块 + ImportModulesFromAssemblies(_customModuleTypes, _options.CustomAssembies); + + foreach (var module in _moduleTypes) + { + if (_moduleTypes.Contains(module)) + { + _customModuleTypes.Remove(module); + } + } + + foreach (var module in _customModuleTypes) + { + _moduleTypes.Add(module); + } + } + + /// + /// 从程序集中导入模块类. + /// + /// + /// + public static void ImportModulesFromAssemblies(HashSet moduleTypes, IEnumerable assemblies) + { + foreach (var assembly in assemblies) + { + if (moduleTypes.Any(x => x.Assembly == assembly)) + { + continue; + } + + var moduleType = FindModuleFromModule(assembly); + if (moduleType == null) + { + continue; + } + + if (moduleTypes.Any(x => x == moduleType)) + { + continue; + } + + moduleTypes.Add(moduleType); + } + } + + /// + /// 构建模块依赖树. + /// + /// 已记录的模块. + /// 父节点. + public static void BuildModuleTree(HashSet moduleTypes, ModuleNode parentModuleNode) + { + if (moduleTypes.Contains(parentModuleNode.ModuleType)) + { + return; + } + + moduleTypes.Add(parentModuleNode.ModuleType); + + var moduleDependences = parentModuleNode.ModuleType.GetCustomAttributes(false) + .Where(x => x.GetType().IsSubclassOf(typeof(InjectModuleAttribute))) + .OfType(); + + foreach (var module in moduleDependences) + { + ModuleNode moduleNode = new ModuleNode(module.ModuleType, parentModuleNode); + parentModuleNode.Childs.Add(moduleNode); + + // 循环依赖检测 + // 检查当前模块(parentTree)依赖的模块(childTree)是否在之前出现过,如果是,则说明是循环依赖 + var isLoop = parentModuleNode.ContainsTree(moduleNode); + if (isLoop) + { + throw new InvalidOperationException($"Loop dependent reference or duplicate reference detected.{module.ModuleType.Name} -> {parentModuleNode.ModuleType.Name} -> {module.ModuleType.Name}."); + } + + BuildModuleTree(moduleTypes, moduleNode); + } + } + + /// + /// 从程序集中获取模块类. + /// + /// + /// 模块类. + public static Type? FindModuleFromModule(Assembly assembly) + { + foreach (var type in assembly.GetTypes()) + { + if (!type.IsClass || type.IsAbstract) + { + continue; + } + + if (type.GetInterface(nameof(IModule)) != null) + { + return type; + } + } + + return default!; + } +} \ No newline at end of file diff --git a/framework/Maomi.Core/ModuleExtensions.cs b/framework/Maomi.Core/ModuleExtensions.cs new file mode 100644 index 0000000..bd26a92 --- /dev/null +++ b/framework/Maomi.Core/ModuleExtensions.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +#pragma warning disable CS1591 +#pragma warning disable SA1401 +#pragma warning disable SA1600 + +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 模块扩展. +/// +public static class ModuleExtensions +{ + /// + /// 注册模块化服务. + /// + /// 入口模块. + /// . + /// 配置. + public static void AddModule(this IServiceCollection services, Action? options = null) + where TModule : IModule + { + AddModule(services, typeof(TModule)); + } + + /// + /// 注册模块化服务. + /// + /// . + /// 入口模块. + /// 配置. + public static void AddModule(this IServiceCollection services, Type startupModule, Action? optionBuilder = null) + { + ArgumentNullException.ThrowIfNull(startupModule, nameof(startupModule)); + + if (startupModule.GetInterface(nameof(IModule)) == null) + { + throw new TypeLoadException($"{startupModule?.Name} does not implement {nameof(IModule)}"); + } + + ModuleOptions initOptions = new(); + if (optionBuilder != null) + { + optionBuilder.Invoke(initOptions); + } + + var ioc = services.BuildServiceProvider(); + ModuleBuilder moduleBuilder = new(services, initOptions, startupModule); + moduleBuilder.Build(); + } +} \ No newline at end of file diff --git a/framework/Maomi.Core/ModuleNode.cs b/framework/Maomi.Core/ModuleNode.cs new file mode 100644 index 0000000..8dadebc --- /dev/null +++ b/framework/Maomi.Core/ModuleNode.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi; + +/// +/// 模块节点. +/// +public class ModuleNode +{ + /// + /// Initializes a new instance of the class. + /// + /// + public ModuleNode(Type moduleType) + { + ModuleType = moduleType; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public ModuleNode(Type moduleType, ModuleNode parentModuleNode) + { + ModuleType = moduleType; + Parent = parentModuleNode; + } + + /// + /// 模块类型. + /// + public Type ModuleType { get; set; } = null!; + + /// + /// 链表,指向父模块节点,用于循环引用检测. + /// + public ModuleNode? Parent { get; set; } + + /// + /// 依赖的其它模块. + /// + public HashSet Childs { get; private set; } = new(); + + /// + /// 向上搜索,当前模块的上层是否已存在此模块. + /// + /// 如果存在此模块,说明是循环引用. + /// + /// 搜索结果. + public bool ContainsTree(ModuleNode childModule) + { + if (childModule.ModuleType == ModuleType) + { + return true; + } + + if (this.Parent == null) + { + return false; + } + + // 如果当前模块找不到记录,则继续从父模块向上查找 + return this.Parent.ContainsTree(childModule); + } + + /// + public override int GetHashCode() + { + return ModuleType.GetHashCode(); + } + + /// + public override bool Equals(object? obj) + { + if (obj == null) + { + return false; + } + + if (obj is ModuleNode module) + { + return GetHashCode() == module.GetHashCode(); + } + + return false; + } +} diff --git a/framework/Maomi.Core/ModuleOptions.cs b/framework/Maomi.Core/ModuleOptions.cs new file mode 100644 index 0000000..a6b08b7 --- /dev/null +++ b/framework/Maomi.Core/ModuleOptions.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Reflection; + +namespace Maomi; + +/// +/// 初始化配置. +/// +public class ModuleOptions +{ + /// + /// 注册服务时要过滤的类型或接口,这些类型不会被注册到容器中. + /// + public ICollection FilterServiceTypes { get; private set; } = new List() + { + typeof(IDisposable), + typeof(ICloneable), + typeof(IComparable), + typeof(object) + }; + + /// + /// 自定义要注册的程序集. + /// + public ICollection CustomAssembies { get; private set; } = new List(); +} diff --git a/framework/Maomi.Core/ModuleRecord.cs b/framework/Maomi.Core/ModuleRecord.cs new file mode 100644 index 0000000..6e1e47c --- /dev/null +++ b/framework/Maomi.Core/ModuleRecord.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Reflection; + +namespace Maomi; + +/// +/// 模块记录. +/// +public class ModuleRecord : IComparable +{ + /// + /// 模块所在的程序集. + /// + public Assembly Assembly { get; init; } = default!; + + /// + /// 模块类. + /// + public Type Type { get; init; } = default!; + + /// + public override int GetHashCode() + { + return Type.GetHashCode(); + } + + /// + public override bool Equals(object? obj) + { + if (obj != null && obj is ModuleRecord record) + { + return CompareTo(record) == 0; + } + + return false; + } + + /// + public int CompareTo(ModuleRecord? other) + { + if (other == null) + { + return -1; + } + + return other.Type == Type ? 0 : string.Compare(Type.FullName, other.Type.FullName); + } +} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Core/Properties/launchSettings.json b/framework/Maomi.Core/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.Core/Properties/launchSettings.json rename to framework/Maomi.Core/Properties/launchSettings.json diff --git a/framework/Maomi.Core/ServiceContext.cs b/framework/Maomi.Core/ServiceContext.cs new file mode 100644 index 0000000..c86a12d --- /dev/null +++ b/framework/Maomi.Core/ServiceContext.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +#pragma warning disable CS1591 +#pragma warning disable SA1401 +#pragma warning disable SA1600 + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi; + +/// +/// 模块上下文. +/// +public abstract class ServiceContext +{ + protected readonly IServiceCollection _serviceCollection; + protected readonly IConfiguration _configuration; + protected readonly List _modules; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + internal ServiceContext(IServiceCollection serviceCollection, IConfiguration configuration) + { + _serviceCollection = serviceCollection; + _configuration = configuration; + _modules = new List(); + } + + /// + /// 容器服务集合. + /// + public IServiceCollection Services => _serviceCollection; + + /// + /// 配置. + /// + public IConfiguration Configuration => _configuration; + + /// + /// 已识别到的模块列表. + /// + public IReadOnlyList Modules => _modules; +} diff --git a/framework/Maomi.Core/package.png b/framework/Maomi.Core/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/framework/Maomi.Core/package.png differ diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/CancelCheckImageCodeEventHandler.cs b/framework/Maomi.EventBus.Tests/CancelCheckImageCodeEventHandler.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/CancelCheckImageCodeEventHandler.cs rename to framework/Maomi.EventBus.Tests/CancelCheckImageCodeEventHandler.cs diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/CancelUserRegisterEventHandler.cs b/framework/Maomi.EventBus.Tests/CancelUserRegisterEventHandler.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/CancelUserRegisterEventHandler.cs rename to framework/Maomi.EventBus.Tests/CancelUserRegisterEventHandler.cs diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/CheckImageCodeEventHandler.cs b/framework/Maomi.EventBus.Tests/CheckImageCodeEventHandler.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/CheckImageCodeEventHandler.cs rename to framework/Maomi.EventBus.Tests/CheckImageCodeEventHandler.cs diff --git a/framework/Maomi.EventBus.Tests/EventBusTest.cs b/framework/Maomi.EventBus.Tests/EventBusTest.cs new file mode 100644 index 0000000..b8c053b --- /dev/null +++ b/framework/Maomi.EventBus.Tests/EventBusTest.cs @@ -0,0 +1,139 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; + +namespace Maomi.EventBus.Tests +{ + public class EventBusTest + { + private readonly IServiceCollection _ioc; + public EventBusTest() + { + var ioc = new ServiceCollection(); + ioc.AddEventBus(); + ioc.AddLogging(builder => builder.AddConsole()); + ioc.AddScoped(); + ioc.AddScoped(s => new EventStats { Names = new() }); + _ioc = ioc; + } + + [Fact] + public async Task PublishEvent() + { + var provider = _ioc.BuildServiceProvider(); + var eventBus = provider.GetRequiredService(); + var setException = provider.GetRequiredService(); + var eventStats = provider.GetRequiredService(); + setException.Node = 1; + try + { + await eventBus.PublishAsync(new MyEvent() + { + Name = "ھ", + Book = "Hive ŵ" + }); + } + catch (Exception ex) + { + Assert.Equal(" дûϢݿʧ", ex.Message); + } + + + Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); + Assert.Equal(nameof(UserRegisterEventHandler.CancelInsertDb), eventStats.Names[2]); + + provider = _ioc.BuildServiceProvider(); + eventBus = provider.GetRequiredService(); + setException = provider.GetRequiredService(); + eventStats = provider.GetRequiredService(); + setException.Node = 2; + + try + { + await eventBus.PublishAsync(new MyEvent() + { + Name = "ھ", + Book = "Hive ŵ" + }); + } + catch (Exception ex) + { + Assert.Equal(" ʼûʧ", ex.Message); + } + + Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); + Assert.Equal(nameof(UserRegisterEventHandler.InitUser), eventStats.Names[2]); + Assert.Equal(nameof(UserRegisterEventHandler.CancelInitUser), eventStats.Names[3]); + Assert.Equal(nameof(UserRegisterEventHandler.CancelInsertDb), eventStats.Names[4]); + + provider = _ioc.BuildServiceProvider(); + eventBus = provider.GetRequiredService(); + setException = provider.GetRequiredService(); + eventStats = provider.GetRequiredService(); + setException.Node = 3; + + try + { + await eventBus.PublishAsync(new MyEvent() + { + Name = "ھ", + Book = "Hive ŵ" + }); + } + catch (Exception ex) + { + Assert.Equal(" ֤ʼʧ", ex.Message); + } + + Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); + Assert.Equal(nameof(UserRegisterEventHandler.InitUser), eventStats.Names[2]); + Assert.Equal(nameof(UserRegisterEventHandler.SendEmail), eventStats.Names[3]); + Assert.Equal(nameof(UserRegisterEventHandler.CancelSendEmail), eventStats.Names[4]); + Assert.Equal(nameof(UserRegisterEventHandler.CancelInitUser), eventStats.Names[5]); + Assert.Equal(nameof(UserRegisterEventHandler.CancelInsertDb), eventStats.Names[6]); + + } + + + [Fact] + public async Task CancelPublishEvent() + { + var provider = _ioc.BuildServiceProvider(); + var eventBus = provider.GetRequiredService(); + var setException = provider.GetRequiredService(); + var eventStats = provider.GetRequiredService(); + + await eventBus.PublishAsync(new CancelMyEvent() + { + Name = "ھ", + Book = "Hive ŵ" + }); + + // ȡ֮ǰִк 4 + Assert.Equal(4, eventStats.Names.Count); + Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); + Assert.Equal(nameof(UserRegisterEventHandler.SendEmail), eventStats.Names[3]); + + provider = _ioc.BuildServiceProvider(); + eventBus = provider.GetRequiredService(); + setException = provider.GetRequiredService(); + eventStats = provider.GetRequiredService(); + + CancellationTokenSource source = new CancellationTokenSource(); + new Thread(() => + { + Thread.Sleep(600); + source.Cancel(); + }).Start(); + await eventBus.PublishAsync(new CancelMyEvent() + { + Name = "ھ", + Book = "Hive ŵ" + }, source.Token); + + // ȡִ֮к < 4 + Assert.NotEqual(4, eventStats.Names.Count); + } + } +} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/EventStats.cs b/framework/Maomi.EventBus.Tests/EventStats.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/EventStats.cs rename to framework/Maomi.EventBus.Tests/EventStats.cs diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/Maomi.EventBus.Tests.csproj b/framework/Maomi.EventBus.Tests/Maomi.EventBus.Tests.csproj similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/Maomi.EventBus.Tests.csproj rename to framework/Maomi.EventBus.Tests/Maomi.EventBus.Tests.csproj diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/MyEvent.cs b/framework/Maomi.EventBus.Tests/MyEvent.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/MyEvent.cs rename to framework/Maomi.EventBus.Tests/MyEvent.cs diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/Properties/launchSettings.json b/framework/Maomi.EventBus.Tests/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/Properties/launchSettings.json rename to framework/Maomi.EventBus.Tests/Properties/launchSettings.json diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/SetException.cs b/framework/Maomi.EventBus.Tests/SetException.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/SetException.cs rename to framework/Maomi.EventBus.Tests/SetException.cs diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/UserRegisterEventHandler.cs b/framework/Maomi.EventBus.Tests/UserRegisterEventHandler.cs similarity index 71% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/UserRegisterEventHandler.cs rename to framework/Maomi.EventBus.Tests/UserRegisterEventHandler.cs index 5ab0133..20b0f94 100644 --- a/src/MaomiFramework/framework/Maomi.EventBus.Tests/UserRegisterEventHandler.cs +++ b/framework/Maomi.EventBus.Tests/UserRegisterEventHandler.cs @@ -16,8 +16,13 @@ public void InsertDb(MyEvent @event) { _stats.Names.Add(nameof(UserRegisterEventHandler.InsertDb)); if (_setException.Node == 1) - throw new Exception("× 写入用户信息到数据库失败"); - else Console.WriteLine("√ 用户信息已添加到数据库"); + { + throw new Exception("× 写入用户信息到数据库失败"); + } + else + { + Console.WriteLine("√ 用户信息已添加到数据库"); + } } @@ -33,8 +38,13 @@ public void InitUser(MyEvent @event) { _stats.Names.Add(nameof(UserRegisterEventHandler.InitUser)); if (_setException.Node == 2) - throw new Exception("× 初始化用户数据失败"); - else Console.WriteLine("√ 初始化用户数据,系统生成默认用户权限、数据"); + { + throw new Exception("× 初始化用户数据失败"); + } + else + { + Console.WriteLine("√ 初始化用户数据,系统生成默认用户权限、数据"); + } } @@ -50,8 +60,13 @@ public void SendEmail(MyEvent @event) { _stats.Names.Add(nameof(UserRegisterEventHandler.SendEmail)); if (_setException.Node == 3) - throw new Exception("× 发送验证邮件失败"); - else Console.WriteLine("√ 发送验证邮件成功"); + { + throw new Exception("× 发送验证邮件失败"); + } + else + { + Console.WriteLine("√ 发送验证邮件成功"); + } } [EventHandler(Order = 3, IsCancel = true)] diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/Usings.cs b/framework/Maomi.EventBus.Tests/Usings.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus.Tests/Usings.cs rename to framework/Maomi.EventBus.Tests/Usings.cs diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/appsettings.json b/framework/Maomi.EventBus.Tests/appsettings.json similarity index 100% rename from src/MaomiFramework/templates/MaomiDemo.Api/appsettings.json rename to framework/Maomi.EventBus.Tests/appsettings.json diff --git a/framework/Maomi.EventBus/Event.cs b/framework/Maomi.EventBus/Event.cs new file mode 100644 index 0000000..77985be --- /dev/null +++ b/framework/Maomi.EventBus/Event.cs @@ -0,0 +1,42 @@ +namespace Maomi.EventBus; + +/// +/// 事件模型类,作为事件的参数使用 +/// +public abstract record Event : IEvent +{ + private Guid _eventId; + private DateTime _creationTime; + + protected Event() : this(Guid.NewGuid(), DateTime.UtcNow) { } + + protected Event(Guid eventId, DateTime creationTime) + { + _eventId = eventId; + _creationTime = creationTime; + } + + /// + /// 事件 id + /// + /// + public Guid GetEventId() => _eventId; + + /// + /// 设置事件 id + /// + /// + public void SetEventId(Guid eventId) => _eventId = eventId; + + /// + /// 事件创建时间 + /// + /// + public DateTime GetCreationTime() => _creationTime; + + /// + /// 设置时间创建时间 + /// + /// + public void SetCreationTime(DateTime creationTime) => _creationTime = creationTime; +} diff --git a/framework/Maomi.EventBus/EventBus.cs b/framework/Maomi.EventBus/EventBus.cs new file mode 100644 index 0000000..ebbd895 --- /dev/null +++ b/framework/Maomi.EventBus/EventBus.cs @@ -0,0 +1,195 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System.Reflection; + +namespace Maomi.EventBus; + +public partial class EventBus +{ + #region static + + // 拦截器 + private static Type? Middleware; + // 缓存所有事件执行器 + private static readonly Dictionary> EventCache = new(); + // 调用链缓存 + private static readonly Dictionary HandlerDelegateCache = new(); + + // 设置拦截器 + public static void SetMiddleware(Type type) + { + Middleware = type; + } + + // 给一个事件添加执行器 + public static void AddEventHandler( + Type declaringType, // 执行器方法所在的类 + int order, + Type eventType, // 绑定了哪个事件 + MethodInfo method) // 执行器方法 + { + if (!EventCache.TryGetValue(eventType, out var events)) + { + events = new HashSet(); + EventCache[eventType] = events; + } + var info = new EventInfo + { + DeclaringType = declaringType, + EventType = eventType, + MethodInfo = method, + IsCancel = false, + Order = order, + // 封装方法为统一的格式 + TaskInvoke = InvokeBuilder.Build(method, declaringType) + }; + events.Add(info); + // 绑定对应的撤销器 + var cancelInfo = events.FirstOrDefault(x => x.EventType == eventType && x.Order == order && x.IsCancel == true); + if (cancelInfo != null) + { + info.CancelInfo = cancelInfo; + } + } + + // 添加撤销事件执行器 + public static void AddCancelEventHandler(Type declaringType, int order, Type eventType, MethodInfo method) + { + if (!EventCache.TryGetValue(eventType, out var events)) + { + events = new HashSet(); + EventCache[eventType] = events; + } + + var cancelInfo = new EventInfo + { + DeclaringType = declaringType, + EventType = eventType, + MethodInfo = method, + IsCancel = true, + Order = order, + TaskInvoke = InvokeBuilder.Build(method, declaringType) + }; + events.Add(cancelInfo); + // 该撤销器绑定对应的执行器 + var info = events.FirstOrDefault(x => x.EventType == eventType && x.Order == order && x.IsCancel == false); + if (info != null) + { + info.CancelInfo = cancelInfo; + } + } + + // 构建事件执行链 + private static ServiceEventHandlerDelegate BuildHandler() where TEvent : IEvent + { + if (HandlerDelegateCache.TryGetValue(typeof(TEvent), out var handler)) + { + return handler; + } + + ServiceEventHandlerDelegate next = async (provider, @params) => + { + var eventData = @params.OfType().FirstOrDefault(); + var cancel = @params.OfType().FirstOrDefault(); + + var logger = provider.GetRequiredService>(); + logger.LogDebug("开始执行事件: {0},{1}", typeof(TEvent).Name, @params[0]); + + if (!EventCache.TryGetValue(typeof(TEvent), out var eventInfos)) + { + return; + } + + var infos = eventInfos.Where(x => x.IsCancel == false).OrderBy(x => x.Order).ToArray(); + + Exception? exception = null; + + // 包装调用链和撤销链 + for (int i = 0; i < infos.Length; i++) + { + var info = infos[i]; + + if (cancel.IsCancellationRequested) + { + logger.LogDebug("事件已被取消执行: {0},位置:{1}", typeof(TEvent).Name, info.MethodInfo.Name); + return; + } + + logger.LogDebug("事件: {0},=> {1}", typeof(TEvent).Name, info.MethodInfo.Name); + + // 构建执行链 + var currentService = provider.GetRequiredService(info.DeclaringType); + try + { + await info.TaskInvoke(currentService, @params); + } + // 执行失败,开始回退 + catch (Exception ex) + { + exception = ex; + + logger.LogError(ex, "执行事件失败: {0},执行器:{1},{2}", typeof(TEvent).Name, info.MethodInfo.Name, @params[0]); + for (int j = i; j >= 0; j--) + { + var backInfo = infos[j]; + if (backInfo.CancelInfo is not null) + { + await backInfo.CancelInfo.TaskInvoke(currentService, @params); + } + } + + break; + } + } + + // 如果出现了异常,在执行撤销链完成后,重新抛出异常 + if (exception != null) + { + throw exception; + } + }; + + // 存到缓存 + HandlerDelegateCache[typeof(TEvent)] = next; + return next; + } + + #endregion + +} + +// 事件总线 +public partial class EventBus : IEventBus +{ + + private readonly IServiceProvider _provider; + + public EventBus(IServiceProvider serviceProvider) + { + _provider = serviceProvider; + } + + + public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) + where TEvent : IEvent + { + var handler = BuildHandler(); + + EventHandlerDelegate executionPipeline = async () => + { + await handler(_provider, @event, cancellationToken); + }; + + if (Middleware != null) + { + // 获取特定于事件类型的中间件 + var mid = _provider.GetRequiredService>(); + await mid.HandleAsync(@event, executionPipeline); + } + else + { + await executionPipeline(); + } + } + +} diff --git a/framework/Maomi.EventBus/EventBus/EventBusExtensions.cs b/framework/Maomi.EventBus/EventBus/EventBusExtensions.cs new file mode 100644 index 0000000..50d67c5 --- /dev/null +++ b/framework/Maomi.EventBus/EventBus/EventBusExtensions.cs @@ -0,0 +1,94 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Maomi.EventBus; + +public static class EventBusExtensions +{ + // 添加事件总线扩展 + public static void AddEventBus(this IServiceCollection services, Type? middleware = null) + { + services.AddScoped(); + if (middleware is not null) + { + EventBus.SetMiddleware(middleware); + services.TryAddEnumerable(new ServiceDescriptor(typeof(IEventMiddleware<>), middleware, lifetime: ServiceLifetime.Transient)); + } + + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + try + { + foreach (var type in assembly.GetTypes()) + { + // 使用 LINQ 简化属性检查 + if (type.GetCustomAttributes(typeof(EventAttribute), inherit: false).Length != 0) + { + GetEventHandler(services, type); + } + } + } + catch (ReflectionTypeLoadException ex) + { + Console.Error.WriteLine("无法加载某些类型: " + ex.Message); + } + } + foreach (var assembly in assemblies) + { + foreach (var type in assembly.GetTypes()) + { + if (type.CustomAttributes.Any(x => x.AttributeType == typeof(EventAttribute))) + { + GetEventHandler(services, type); + } + } + } + } + + // 扫描类中的执行器 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetEventHandler(IServiceCollection services, Type type) + { + services.AddScoped(type); + + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); + foreach (var method in methods) + { + // 提前检查 EventHandlerAttribute 是否存在 + var attr = method.GetCustomAttribute(); + if (attr == null) + { + continue; + } + + // 检查方法参数数量 + var parameters = method.GetParameters(); + if (parameters.Length == 0) + { + throw new ArgumentException($"{method.Name} 的定义不正确,至少包含一个参数"); + } + + // 检查第一个参数是否为事件类型 + var eventType = parameters[0].ParameterType; + bool isValidEventType = eventType.IsSubclassOf(typeof(Event)) || + eventType.GetInterfaces().Any(i => i == typeof(IEvent)); + if (!isValidEventType) + { + throw new ArgumentException($"{method.Name} 的定义不正确,第一个参数必须为事件"); + } + + // 根据 IsCancel 属性决定如何注册事件处理器 + if (!attr.IsCancel) + { + EventBus.AddEventHandler(type, attr.Order, eventType, method); + } + else + { + EventBus.AddCancelEventHandler(type, attr.Order, eventType, method); + } + } + } +} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/EventHandlerAttribute.cs b/framework/Maomi.EventBus/EventHandlerAttribute.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus/EventHandlerAttribute.cs rename to framework/Maomi.EventBus/EventHandlerAttribute.cs diff --git a/framework/Maomi.EventBus/EventInfo.cs b/framework/Maomi.EventBus/EventInfo.cs new file mode 100644 index 0000000..2dcf1eb --- /dev/null +++ b/framework/Maomi.EventBus/EventInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; + +namespace Maomi.EventBus; + +// 用来记录一个 Handler +internal class EventInfo +{ + // 执行器所在的类 + public Type DeclaringType { get; set; } + // 执行序号 + public int Order { get; set; } + // 事件 + public Type EventType { get; set; } + // 执行器方法 + public MethodInfo MethodInfo { get; set; } + // 委托封装的执行器方法 + public TaskInvokeDelegate TaskInvoke { get; set; } + // 撤销时执行 + public bool IsCancel { get; set; } + // 撤销执行器对应的信息 + public EventInfo? CancelInfo { get; set; } + + public override int GetHashCode() + { + return MethodInfo.GetHashCode(); + } + + public override bool Equals(object? obj) + { + if (obj is not EventInfo info) + { + return false; + } + + return this.GetHashCode() == info.GetHashCode(); + } +} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/IEvent.cs b/framework/Maomi.EventBus/IEvent.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus/IEvent.cs rename to framework/Maomi.EventBus/IEvent.cs diff --git a/framework/Maomi.EventBus/IEventBus.cs b/framework/Maomi.EventBus/IEventBus.cs new file mode 100644 index 0000000..0e7e916 --- /dev/null +++ b/framework/Maomi.EventBus/IEventBus.cs @@ -0,0 +1,8 @@ +namespace Maomi.EventBus; + +// 事件总线服务 +public interface IEventBus +{ + Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) + where TEvent : IEvent; +} diff --git a/framework/Maomi.EventBus/IEventMiddleware.cs b/framework/Maomi.EventBus/IEventMiddleware.cs new file mode 100644 index 0000000..3df9a3b --- /dev/null +++ b/framework/Maomi.EventBus/IEventMiddleware.cs @@ -0,0 +1,16 @@ +namespace Maomi.EventBus; + +// 定义事件委托,用于构建执行链 +public delegate Task EventHandlerDelegate(); + +// 带依赖注入的事件委托,用于构建执行链 +internal delegate Task ServiceEventHandlerDelegate(IServiceProvider provider, params object?[] parameters); + +// 事件执行中间件,即执行事件时的拦截器 +public interface IEventMiddleware +where TEvent : IEvent +{ + // @event: 事件 + // next: 下一个要执行的函数 + Task HandleAsync(TEvent @event, EventHandlerDelegate next); +} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/InvokeBuilder.cs b/framework/Maomi.EventBus/InvokeBuilder.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus/InvokeBuilder.cs rename to framework/Maomi.EventBus/InvokeBuilder.cs diff --git a/framework/Maomi.EventBus/Maomi.EventBus.csproj b/framework/Maomi.EventBus/Maomi.EventBus.csproj new file mode 100644 index 0000000..ae05a76 --- /dev/null +++ b/framework/Maomi.EventBus/Maomi.EventBus.csproj @@ -0,0 +1,67 @@ + + + + Library + net7.0;net8.0;net9.0 + enable + enable + Maomi + true + 2.2.0 + Codestin Search App + Maomi.EventBus 框架是一个事件总线框架。 + True + true + MIT + package.png + https://maonmi.whuanle.cn + https://github.com/whuanle/maonmi + README.md + git + + Maomi 框架是一个简洁的 .NET 开发框架,具有模块化、自动服务注册等功能。 + + True + snupkg + True + + + + + + + + + + + + True + \ + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaomiFramework/framework/Maomi.EventBus/Properties/launchSettings.json b/framework/Maomi.EventBus/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.EventBus/Properties/launchSettings.json rename to framework/Maomi.EventBus/Properties/launchSettings.json diff --git a/framework/Maomi.EventBus/package.png b/framework/Maomi.EventBus/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/framework/Maomi.EventBus/package.png differ diff --git a/framework/Maomi.I18n.AspNetCore/DataAnnotationsExtensions.cs b/framework/Maomi.I18n.AspNetCore/DataAnnotationsExtensions.cs new file mode 100644 index 0000000..639a51c --- /dev/null +++ b/framework/Maomi.I18n.AspNetCore/DataAnnotationsExtensions.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi.I18n; + +/// +/// 模型验证使用多语言. +/// +public static partial class DataAnnotationsExtensions +{ + /// + /// 为 API 模型验证注入 i18n 服务. + /// + /// + /// . + public static IMvcBuilder AddI18nDataAnnotation(this IMvcBuilder builder) + { + builder + .AddDataAnnotationsLocalization(options => + { + options.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) => + stringLocalizerFactory.Create(modelType); + }); + return builder; + } +} diff --git a/framework/Maomi.I18n.AspNetCore/HttpI18nContext.cs b/framework/Maomi.I18n.AspNetCore/HttpI18nContext.cs new file mode 100644 index 0000000..8e3089d --- /dev/null +++ b/framework/Maomi.I18n.AspNetCore/HttpI18nContext.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.Options; + +namespace Maomi.I18n; + +/// +/// 从 http 请求上下文中获取多语言信息. +/// +public class HttpI18nContext : I18nContext +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// + public HttpI18nContext(IHttpContextAccessor httpContextAccessor, IOptions requestLocalizationOptions) + { + if (httpContextAccessor == null || httpContextAccessor.HttpContext == null) + { + Culture = requestLocalizationOptions.Value.DefaultRequestCulture.Culture; + return; + } + + var requestCultureFeature = httpContextAccessor.HttpContext!.Features.Get(); + var requestCulture = requestCultureFeature?.RequestCulture; + if (requestCulture != null) + { + Culture = requestCulture.Culture; + } + else + { + Culture = requestLocalizationOptions.Value.DefaultRequestCulture.Culture; + } + } +} diff --git a/framework/Maomi.I18n.AspNetCore/I18nExtensions.cs b/framework/Maomi.I18n.AspNetCore/I18nExtensions.cs new file mode 100644 index 0000000..b3b04f4 --- /dev/null +++ b/framework/Maomi.I18n.AspNetCore/I18nExtensions.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.Options; + +namespace Maomi.I18n; + +/// +/// i18n 扩展. +/// +public static class I18nExtensions +{ + /// + /// 添加 i18n 支持服务. + /// + /// + /// 默认语言. + public static void AddI18nAspNetCore(this IServiceCollection services, string defaultLanguage = "zh-CN") + { + services.AddI18n(defaultLanguage); + + var resourceFactory = services.BuildServiceProvider().GetRequiredService(); + + // ASP.NET Core 自带的 + services.AddLocalization(); + + // 配置 ASP.NET Core 的本地化服务 + services.Configure(options => + { + options.ApplyCurrentCultureToResponseHeaders = true; + options.DefaultRequestCulture = new RequestCulture(culture: defaultLanguage, uiCulture: defaultLanguage); + options.SupportedCultures = resourceFactory.SupportedCultures.ToList(); + options.SupportedUICultures = resourceFactory.SupportedCultures.ToList(); + + // 默认自带了三个请求语言提供器,会先从这些提供器识别要使用的语言。 + // QueryStringRequestCultureProvider + // CookieRequestCultureProvider + // AcceptLanguageHeaderRequestCultureProvider + // 自定义请求请求语言提供器 + options.RequestCultureProviders.Add(new InternalRequestCultureProvider(options)); + }); + + // i18n 上下文 + services.AddScoped(); + } + + /// + /// i18n 中间件. + /// + /// + public static void UseI18n(this IApplicationBuilder app) + { + var options = app.ApplicationServices.GetRequiredService>(); + app.UseRequestLocalization(options.Value); + } +} diff --git a/framework/Maomi.I18n.AspNetCore/InternalRequestCultureProvider.cs b/framework/Maomi.I18n.AspNetCore/InternalRequestCultureProvider.cs new file mode 100644 index 0000000..58e5162 --- /dev/null +++ b/framework/Maomi.I18n.AspNetCore/InternalRequestCultureProvider.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.AspNetCore.Localization; + +namespace Maomi.I18n; + +/// +/// 自定义如何从请求中解析请求语言. +/// +internal class InternalRequestCultureProvider : RequestCultureProvider +{ + private const string RouteValueKey = "c"; + private const string UIRouteValueKey = "uic"; + + private readonly RequestLocalizationOptions _requestLocalizationOptions; + + /// + /// Initializes a new instance of the class. + /// + /// + public InternalRequestCultureProvider(RequestLocalizationOptions requestLocalizationOptions) + { + _requestLocalizationOptions = requestLocalizationOptions; + } + + /// + public override Task DetermineProviderCultureResult(HttpContext httpContext) + { + var request = httpContext.Request; + if (!request.RouteValues.Any()) + { + return NullProviderCultureResult; + } + + string? queryCulture = null; + string? queryUICulture = null; + + // 从路由中解析 + if (!string.IsNullOrWhiteSpace(RouteValueKey)) + { + queryCulture = request.RouteValues[RouteValueKey]?.ToString(); + } + + if (!string.IsNullOrWhiteSpace(UIRouteValueKey)) + { + queryUICulture = request.RouteValues[UIRouteValueKey]?.ToString() ?? queryCulture; + } + + if (queryCulture == null && queryUICulture == null) + { + return NullProviderCultureResult; + } + + var providerResultCulture = new ProviderCultureResult(queryCulture, queryUICulture); + + return Task.FromResult(providerResultCulture); + } +} diff --git a/framework/Maomi.I18n.AspNetCore/Maomi.I18n.AspNetCore.csproj b/framework/Maomi.I18n.AspNetCore/Maomi.I18n.AspNetCore.csproj new file mode 100644 index 0000000..f61c5a0 --- /dev/null +++ b/framework/Maomi.I18n.AspNetCore/Maomi.I18n.AspNetCore.csproj @@ -0,0 +1,73 @@ + + + + Library + net7.0;net8.0;net9.0 + enable + enable + Maomi + true + 2.2.2 + Codestin Search App + Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 + True + true + MIT + package.png + https://maomi.whuanle.cn + https://github.com/whuanle/maomi + README.md + git + + Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 + + True + snupkg + True + + + + + + + + + + + + + True + \ + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaomiFramework/framework/Maomi.I18n.Redis/Properties/launchSettings.json b/framework/Maomi.I18n.AspNetCore/Properties/launchSettings.json similarity index 63% rename from src/MaomiFramework/framework/Maomi.I18n.Redis/Properties/launchSettings.json rename to framework/Maomi.I18n.AspNetCore/Properties/launchSettings.json index 8b4f09f..11a48b2 100644 --- a/src/MaomiFramework/framework/Maomi.I18n.Redis/Properties/launchSettings.json +++ b/framework/Maomi.I18n.AspNetCore/Properties/launchSettings.json @@ -1,12 +1,12 @@ { "profiles": { - "Maomi.I18n.Redis": { + "Maomi.I18n.AspNetCore": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:14282;http://localhost:14284" + "applicationUrl": "https://localhost:57837;http://localhost:57838" } } } \ No newline at end of file diff --git a/framework/Maomi.I18n.AspNetCore/package.png b/framework/Maomi.I18n.AspNetCore/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/framework/Maomi.I18n.AspNetCore/package.png differ diff --git a/framework/Maomi.I18n.Tests/I18nStringLocalizerTests.cs b/framework/Maomi.I18n.Tests/I18nStringLocalizerTests.cs new file mode 100644 index 0000000..8c319e3 --- /dev/null +++ b/framework/Maomi.I18n.Tests/I18nStringLocalizerTests.cs @@ -0,0 +1,73 @@ +using AutoFixture; +using AutoFixture.AutoMoq; +using Microsoft.Extensions.Localization; +using Moq; +using System.Globalization; + +namespace Maomi.I18n.Tests; + +public class I18nStringLocalizerTests +{ + private readonly Mock _serviceProviderMock; + private readonly Mock _contextMock; + private readonly Mock _resourceFactoryMock; + private readonly Mock _localizationOptionsMock; + private readonly I18nStringLocalizer _localizer; + + public I18nStringLocalizerTests() + { + var fixture = new AutoFixture.Fixture(); + fixture.Customize(new AutoMoqCustomization()); + + _serviceProviderMock = fixture.Freeze>(); + _contextMock = fixture.Freeze>(); + _resourceFactoryMock = fixture.Freeze>(); + _localizationOptionsMock = fixture.Freeze>(); + + _localizer = new I18nStringLocalizer( + _contextMock.Object, + _resourceFactoryMock.Object, + _serviceProviderMock.Object, + _localizationOptionsMock.Object + ); + } + + [Theory] + [InlineData("TestString", "测试字符串", "zh-CN")] + [InlineData("TestString", "Test String", "en-US")] + [InlineData("TestString", "Prueba de cadena", "es-ES")] + public void Indexer_ReturnsLocalizedString_ForMultipleCultures(string name, string expectedValue, string culture) + { + var localizedString = new LocalizedString(name, expectedValue, false); + + _resourceFactoryMock.Setup(r => r.Resources).Returns(new List() + { + new DictionaryResource(new CultureInfo(culture), new Dictionary { { name, expectedValue } }) + }); + _contextMock.Setup(c => c.Culture).Returns(new CultureInfo(culture)); + + var result = _localizer[name]; + + Assert.Equal(localizedString.ToString(), result.ToString()); + } + + [Fact] + public void GetAllStrings_ReturnsAllLocalizedStrings() + { + var localizedString = new LocalizedString("TestString", "测试字符串", false); + var resourceMock = new Mock(); + resourceMock.Setup(r => r.GetAllStrings(It.IsAny())).Returns(new List { localizedString }); + _resourceFactoryMock.Setup(r => r.Resources).Returns(new List { resourceMock.Object }); + + var result = _localizer.GetAllStrings(false); + + Assert.Contains(localizedString, result); + } +} + +public static class I18NStringLocalizerHelper +{ + public static void SetupFind(LocalizedString localizedString) + { + } +} diff --git a/framework/Maomi.I18n.Tests/I18nTest.cs b/framework/Maomi.I18n.Tests/I18nTest.cs new file mode 100644 index 0000000..0a21291 --- /dev/null +++ b/framework/Maomi.I18n.Tests/I18nTest.cs @@ -0,0 +1,155 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Localization; +using System.Net.Http.Headers; + +namespace Maomi.I18n.Tests; + +public class I18nTest +{ + [Fact] + public async Task I18n_Request() + { + using var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder + .UseTestServer() + .ConfigureServices(services => + { + services.AddHttpContextAccessor(); + services.AddControllers(); + services.AddI18nAspNetCore(defaultLanguage: "zh-CN"); + services.AddI18nResource(option => + { + var basePath = "i18n"; + option.ParseDirectory(basePath); + }); + }) + .Configure(app => + { + app.UseI18n(); + app.UseRouting(); + app.Use(async (HttpContext context, RequestDelegate next) => + { + var localizer = context.RequestServices.GetRequiredService>(); + await context.Response.WriteAsync(localizer["购物车:商品名称"]); + return; + }); + }); + }) + .StartAsync(); + + var httpClient = host.GetTestClient(); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + var response = await httpClient.GetStringAsync("/test?culture=en-US&ui-culture=en-US"); + Assert.Equal("Product name", response); + + response = await httpClient.GetStringAsync("/test?culture=zh-CN&ui-culture=zh-CN"); + Assert.Equal("商品名称", response); + + httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=en-US|uic=en-US"); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("Product name", response); + + httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=zh-CN|uic=zh-CN"); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("商品名称", response); + httpClient.DefaultRequestHeaders.Remove("Cookie"); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh-CN")); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh", 0.9)); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("商品名称", response); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US")); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en", 0.9)); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("Product name", response); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("sv")); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US", 0.9)); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("Product name", response); + + } + + [Fact] + public async Task I18n_Assembly_Request() + { + using var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder + .UseTestServer() + .ConfigureServices(services => + { + services.AddHttpContextAccessor(); + services.AddControllers(); + services.AddI18nAspNetCore(defaultLanguage: "zh-CN"); + services.AddI18nResource(option => + { + var basePath = "i18n"; + option.ParseDirectory(basePath); + }); + }) + .Configure(app => + { + app.UseI18n(); + app.UseRouting(); + app.Use(async (HttpContext context, RequestDelegate next) => + { + var localizer = context.RequestServices.GetRequiredService>(); + await context.Response.WriteAsync(localizer["购物车:商品名称"]); + return; + }); + }); + }) + .StartAsync(); + + var httpClient = host.GetTestClient(); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + var response = await httpClient.GetStringAsync("/test?culture=en-US&ui-culture=en-US"); + Assert.Equal("Product name", response); + + response = await httpClient.GetStringAsync("/test?culture=zh-CN&ui-culture=zh-CN"); + Assert.Equal("商品名称", response); + + httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=en-US|uic=en-US"); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("Product name", response); + + httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=zh-CN|uic=zh-CN"); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("商品名称", response); + httpClient.DefaultRequestHeaders.Remove("Cookie"); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh-CN")); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh", 0.9)); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("商品名称", response); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US")); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en", 0.9)); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("Product name", response); + + httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("sv")); + httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US", 0.9)); + response = await httpClient.GetStringAsync("/test"); + Assert.Equal("Product name", response); + + } +} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/Maomi.I18n.Tests.csproj b/framework/Maomi.I18n.Tests/Maomi.I18n.Tests.csproj similarity index 88% rename from src/MaomiFramework/framework/Maomi.I18n.Tests/Maomi.I18n.Tests.csproj rename to framework/Maomi.I18n.Tests/Maomi.I18n.Tests.csproj index 93d8f68..21728b0 100644 --- a/src/MaomiFramework/framework/Maomi.I18n.Tests/Maomi.I18n.Tests.csproj +++ b/framework/Maomi.I18n.Tests/Maomi.I18n.Tests.csproj @@ -38,8 +38,11 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -52,6 +55,7 @@ + diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/Properties/launchSettings.json b/framework/Maomi.I18n.Tests/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.I18n.Tests/Properties/launchSettings.json rename to framework/Maomi.I18n.Tests/Properties/launchSettings.json diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/Usings.cs b/framework/Maomi.I18n.Tests/Usings.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.I18n.Tests/Usings.cs rename to framework/Maomi.I18n.Tests/Usings.cs diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/en-US.json b/framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/en-US.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/en-US.json rename to framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/en-US.json diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/zh-CN.json b/framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/zh-CN.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/zh-CN.json rename to framework/Maomi.I18n.Tests/i18n/Maomi.I18n.Tests/zh-CN.json diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/en-US.json b/framework/Maomi.I18n.Tests/i18n/en-US.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/en-US.json rename to framework/Maomi.I18n.Tests/i18n/en-US.json diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/zh-CN.json b/framework/Maomi.I18n.Tests/i18n/zh-CN.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.I18n.Tests/i18n/zh-CN.json rename to framework/Maomi.I18n.Tests/i18n/zh-CN.json diff --git a/framework/Maomi.I18n.Wpf/I18nExtensions.cs b/framework/Maomi.I18n.Wpf/I18nExtensions.cs new file mode 100644 index 0000000..9864811 --- /dev/null +++ b/framework/Maomi.I18n.Wpf/I18nExtensions.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; +using System.Collections; +using System.Globalization; +using System.IO; +using System.Resources; +using System.Windows; + +namespace Maomi.I18n; + +/// +/// i18n 扩展方法. +/// +public static class I18nExtensions +{ + /// + /// 添加 wpf 多语言支持. + /// + /// + /// 程序名称. + /// 多语言资源文件路径. + public static void AddI18nWpf(this IServiceCollection services, string appName, string localization = "localization") + { + var wpfI18NOptions = new WpfI18nOptions + { + AppName = appName, + Localization = localization + }; + + services.AddSingleton(wpfI18NOptions); + services.AddScoped(); + services.AddScoped(s => + { + return s.GetRequiredService(); + }); + + Dictionary xamlFiles = new(); + + // 读取 Localization 目录下的所有资源字典 + foreach (string resName in System.Windows.Application.ResourceAssembly.GetManifestResourceNames()) + { + using var resStream = System.Windows.Application.ResourceAssembly.GetManifestResourceStream(resName); + if (resStream == null) + { + continue; + } + + using ResourceReader resourceReader = new ResourceReader(resStream); + + // 检查是否多语言资源文件 + foreach (DictionaryEntry resourceEntry in resourceReader) + { + var fileName = resourceEntry.Key.ToString(); + if (string.IsNullOrEmpty(fileName)) + { + continue; + } + + if (fileName.StartsWith(localization, StringComparison.CurrentCultureIgnoreCase)) + { + xamlFiles.Add(Path.GetFileNameWithoutExtension(fileName), fileName); + } + } + } + + services.AddI18nResource(f => + { + // 读取每个多语言资源文件 + foreach (var item in xamlFiles) + { + string resourceDictionaryPath = $"pack://application:,,,/{localization}/{item.Key}.xaml"; + var resourceDictionary = new ResourceDictionary + { + Source = new Uri(resourceDictionaryPath, UriKind.RelativeOrAbsolute) + }; + + Dictionary dictionary = ResourceDictionaryToDictionary(resourceDictionary); + f.Add(new DictionaryResource(new CultureInfo(item.Key), dictionary)); + } + }); + + // 解析 xaml 资源字典 + Dictionary ResourceDictionaryToDictionary(ResourceDictionary resourceDictionary) + { + var dictionary = new Dictionary(); + + foreach (var key in resourceDictionary.Keys) + { + dictionary[key.ToString()!] = resourceDictionary[key]; + } + + return dictionary; + } + } +} \ No newline at end of file diff --git a/framework/Maomi.I18n.Wpf/Maomi.I18n.Wpf.csproj b/framework/Maomi.I18n.Wpf/Maomi.I18n.Wpf.csproj new file mode 100644 index 0000000..14b129d --- /dev/null +++ b/framework/Maomi.I18n.Wpf/Maomi.I18n.Wpf.csproj @@ -0,0 +1,74 @@ + + + + Library + net7.0-windows;net8.0-windows;net9.0-windows + true + enable + enable + Maomi + true + 2.2.2 + Codestin Search App + Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 + True + true + MIT + package.png + https://maomi.whuanle.cn + https://github.com/whuanle/maomi + README.md + git + + Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 + + True + snupkg + True + + + + + + + + + + + + + True + \ + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/Maomi.I18n.Wpf/WpfI18nContext.cs b/framework/Maomi.I18n.Wpf/WpfI18nContext.cs new file mode 100644 index 0000000..b7c27b0 --- /dev/null +++ b/framework/Maomi.I18n.Wpf/WpfI18nContext.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +#pragma warning disable SA1401 // Fields should be private + +using System.Globalization; +using System.Windows; + +namespace Maomi.I18n; + +/// +/// i18n 上下文设置. +/// +public class WpfI18nContext : I18nContext +{ + /// + /// 配置. + /// + protected readonly WpfI18nOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// + public WpfI18nContext(WpfI18nOptions options) + { + _options = options; + } + + /// + /// 设置当前程序所用语言. + /// + /// + public virtual void SetLanguage(string language) + { + var currentCultureInfo = GetValidCulture(language); + + Culture = currentCultureInfo; + + CultureInfo.CurrentCulture = currentCultureInfo; + CultureInfo.DefaultThreadCurrentUICulture = currentCultureInfo; + + Thread.CurrentThread.CurrentCulture = currentCultureInfo; + Thread.CurrentThread.CurrentUICulture = currentCultureInfo; + + // 切换 xaml 资源字典 + LoadLocalizationResource(Application.Current.Resources, currentCultureInfo); + } + + /// + /// 将语言名称转为 CultureInfo. + /// + /// + /// . + protected virtual CultureInfo GetValidCulture(string cultureName) + { + var name = cultureName switch + { + "zh-Hant" or "zh-HK" or "zh-MO" or "zh-TW" or "zh-CHT" => "zh-HK", + "zh" or "zh-CN" or "zh-Hans" or "zh-CHS" or "zh-SG" => "zh-CN", + "ja" or "ja-JP" => "jp", + "ko" or "ko-KP" or "ko-KR" => "ko", + "ru" or "ru-RU" => "ru", + "en-US" or "en" => "en-US", + _ => "zh-CN" + }; + + CultureInfo culture = CultureInfo.CreateSpecificCulture(name); + return culture; + } + + /// + /// 加载字典文件到资源字典中. + /// + /// + /// + protected virtual void LoadLocalizationResource(ResourceDictionary resources, CultureInfo cultureInfo) + { + var origin = resources.MergedDictionaries.FirstOrDefault(x => x.Source.OriginalString.Contains(_options.Localization)); + + if (origin != null) + { + resources.MergedDictionaries.Remove(origin); + } + + var uri = new Uri(@$"pack://application:,,,/{_options.AppName};component/{_options.Localization}/{cultureInfo.Name}.xaml"); + resources.MergedDictionaries.Add(new ResourceDictionary + { + Source = uri + }); + } +} diff --git a/framework/Maomi.I18n.Wpf/WpfI18nOptions.cs b/framework/Maomi.I18n.Wpf/WpfI18nOptions.cs new file mode 100644 index 0000000..acdfd4f --- /dev/null +++ b/framework/Maomi.I18n.Wpf/WpfI18nOptions.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi.I18n; + +/// +/// 配置. +/// +public class WpfI18nOptions +{ + /// + /// 程序名称. + /// + public string AppName { get; init; } = default!; + + /// + /// 路径. + /// + public string Localization { get; init; } = default!; +} diff --git a/framework/Maomi.I18n.Wpf/package.png b/framework/Maomi.I18n.Wpf/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/framework/Maomi.I18n.Wpf/package.png differ diff --git a/framework/Maomi.I18n/Abstractions/I18nContext.cs b/framework/Maomi.I18n/Abstractions/I18nContext.cs new file mode 100644 index 0000000..5c038db --- /dev/null +++ b/framework/Maomi.I18n/Abstractions/I18nContext.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +#pragma warning disable SA1401 // Fields should be private + +using System.Globalization; + +namespace Maomi.I18n; + +/// +/// 记录当前请求的 i18n 信息. +/// +public class I18nContext +{ + /// + /// 当前用户请求的语言. + /// + protected CultureInfo? _culture = default!; + + /// + /// 当前用户请求的语言. + /// + public virtual CultureInfo Culture + { + get + { + if (_culture == null) + { + return CultureInfo.CurrentCulture; + } + + return _culture; + } + + set + { + _culture = value; + } + } +} diff --git a/framework/Maomi.I18n/Abstractions/I18nResource.cs b/framework/Maomi.I18n/Abstractions/I18nResource.cs new file mode 100644 index 0000000..f18efd8 --- /dev/null +++ b/framework/Maomi.I18n/Abstractions/I18nResource.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.Localization; +using System.Globalization; + +namespace Maomi.I18n; + +/// +/// i18n 语言资源. +/// +/// 每个 I18nResource 对应一种语言的一个资源文件. +public interface I18nResource +{ + /// + /// 该资源提供的语言. + /// + CultureInfo SupportedCulture { get; } + + /// + /// 获取具有给定名称的字符串资源. + /// + /// 语言名字. + /// 字符串名称. + /// . + LocalizedString Get(string culture, string name); + + /// + /// 获取具有给定名称的字符串资源. + /// + /// 语言名字. + /// 字符串名称. + /// 字符串插值参数. + /// . + LocalizedString Get(string culture, string name, params object[] arguments); + + /// + /// 从 i18n 资源文件中获取所有字符串. + /// + /// + /// . + public IEnumerable GetAllStrings(bool includeParentCultures); +} + +/// +/// i18n 语言资源. +/// +/// 每个 I18nResource 对应一种语言的一个资源文件. +/// 类型. +public interface I18nResource : I18nResource +{ +} diff --git a/framework/Maomi.I18n/Abstractions/I18nResourceFactory.cs b/framework/Maomi.I18n/Abstractions/I18nResourceFactory.cs new file mode 100644 index 0000000..6a8b6a6 --- /dev/null +++ b/framework/Maomi.I18n/Abstractions/I18nResourceFactory.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Globalization; + +namespace Maomi.I18n; + +/// +/// I18n 资源工厂. +/// +public interface I18nResourceFactory +{ + /// + /// 当前支持的语言. + /// + ICollection SupportedCultures { get; } + + /// + /// 资源提供器实例. + /// + ICollection Resources { get; } + + /// + /// 在容器中的资源提供器. + /// + ICollection ServiceResources { get; } + + /// + /// 添加 i18n 语言资源,该类型将会被从容器中取出. + /// + /// i18n 语言资源. + /// . + I18nResourceFactory AddServiceType(Type resourceType); + + /// + /// 添加 i18n 语言资源. + /// + /// i18n 语言资源. + /// . + I18nResourceFactory Add(I18nResource resource); + + /// + /// 添加 i18n 语言资源. + /// + /// 类型. + /// i18n 语言资源. + /// . + I18nResourceFactory Add(I18nResource resource); +} diff --git a/framework/Maomi.I18n/Abstractions/I18nScope.cs b/framework/Maomi.I18n/Abstractions/I18nScope.cs new file mode 100644 index 0000000..5523886 --- /dev/null +++ b/framework/Maomi.I18n/Abstractions/I18nScope.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Globalization; + +namespace Maomi.I18n; + +/// +/// i18n 作用域. +/// +public class I18nScope : IDisposable +{ + private readonly CultureInfo _defaultCultureInfo; + + /// + /// Initializes a new instance of the class. + /// + /// + public I18nScope(string language) + { + _defaultCultureInfo = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language); + } + + /// + public void Dispose() + { + CultureInfo.CurrentCulture = _defaultCultureInfo; + } +} diff --git a/framework/Maomi.I18n/Extensions/I18nExtensions.cs b/framework/Maomi.I18n/Extensions/I18nExtensions.cs new file mode 100644 index 0000000..5ee439a --- /dev/null +++ b/framework/Maomi.I18n/Extensions/I18nExtensions.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Localization; + +namespace Maomi.I18n; + +/// +/// i18n 扩展. +/// +public static class I18nExtensions +{ + /// + /// 添加 i18n 支持服务. + /// + /// + /// 默认语言. + public static void AddI18n(this IServiceCollection services, string defaultLanguage = "zh-CN") + { + InternalI18nResourceFactory resourceFactory = new InternalI18nResourceFactory(); + + services.AddSingleton(new LocalizationOptions { DefaultLanguage = defaultLanguage }); + + // i18n 上下文 + services.AddScoped(); + + // 注入 i18n 服务 + services.AddSingleton(s => resourceFactory); + services.AddSingleton(); + services.AddScoped(); + services.TryAddEnumerable(new ServiceDescriptor(typeof(IStringLocalizer<>), typeof(I18nStringLocalizer<>), ServiceLifetime.Scoped)); + } + + /// + /// 添加 i18n 资源. + /// + /// + /// + public static void AddI18nResource(this IServiceCollection services, Action resourceFactory) + { + var service = services.BuildServiceProvider().GetRequiredService(); + resourceFactory.Invoke(service); + } +} \ No newline at end of file diff --git a/framework/Maomi.I18n/Extensions/JsonResourceExtensions.cs b/framework/Maomi.I18n/Extensions/JsonResourceExtensions.cs new file mode 100644 index 0000000..86d497d --- /dev/null +++ b/framework/Maomi.I18n/Extensions/JsonResourceExtensions.cs @@ -0,0 +1,171 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Buffers; +using System.Globalization; +using System.Text; +using System.Text.Json; + +namespace Maomi.I18n; + +/// +/// Json 多语言文件资源. +/// +public static class JsonResourceExtensions +{ + /// + /// 扫描目录下的所有子目录,自动区配对应的项目/程序集下,json 文件名称会被动作语言名称. + /// + /// + /// + /// . + public static I18nResourceFactory ParseDirectory( + this I18nResourceFactory resourceFactory, + string basePath) + { + var basePathDirectoryInfo = new DirectoryInfo(basePath); + Queue directoryInfos = new Queue(); + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var basePathFullName = basePathDirectoryInfo.FullName; + + directoryInfos.Enqueue(basePathDirectoryInfo); + + while (directoryInfos.Count > 0) + { + var curDirectory = directoryInfos.Dequeue(); + var lanDir = curDirectory.GetDirectories(); + foreach (var lan in lanDir) + { + directoryInfos.Enqueue(lan); + } + + var files = curDirectory.GetFiles().Where(x => x.Name.EndsWith(".json")).ToArray(); + if (files.Length == 0) + { + continue; + } + + // 移除路径的前部分 + var curPath = curDirectory.FullName[basePathFullName.Length..].Trim('/', '\\'); + + var assembly = assemblies.FirstOrDefault(x => string.Equals(curPath, x.GetName().Name, StringComparison.CurrentCultureIgnoreCase)); + if (assembly == null) + { + continue; + } + + foreach (var file in files) + { + var language = Path.GetFileNameWithoutExtension(file.Name); + var text = File.ReadAllText(file.FullName); + var dic = ReadJsonHelper.Read(new ReadOnlySequence(Encoding.UTF8.GetBytes(text)), new JsonReaderOptions { AllowTrailingCommas = true }); + + DictionaryResource jsonResource = (Activator.CreateInstance( + typeof(DictionaryResource<>).MakeGenericType(assembly.GetTypes()[0]), + new object[] { new CultureInfo(language), dic, assembly }) as DictionaryResource)!; + resourceFactory.Add(jsonResource); + } + } + + return resourceFactory; + } + + /// + /// 添加 json 文件资源,json 文件名称会被当作语言名称. + /// + /// + /// 基础路径. + /// . + public static I18nResourceFactory AddJsonDirectory( + this I18nResourceFactory resourceFactory, + string basePath) + { + var rootDir = new DirectoryInfo(basePath); + + var files = rootDir.GetFiles().Where(x => x.Name.EndsWith(".json")); + foreach (var file in files) + { + var language = Path.GetFileNameWithoutExtension(file.Name); + var text = File.ReadAllText(file.FullName); + var dic = ReadJsonHelper.Read(new ReadOnlySequence(Encoding.UTF8.GetBytes(text)), new JsonReaderOptions { AllowTrailingCommas = true }); + + DictionaryResource jsonResource = new DictionaryResource(new CultureInfo(language), dic); + resourceFactory.Add(jsonResource); + } + + return resourceFactory; + } + + /// + /// 添加 json 文件资源,将目录下的所有 json 文件都归类到此程序集下,json 文件名称会被当作语言名称. + /// + /// 类型. + /// + /// 基础路径. + /// . + public static I18nResourceFactory AddJsonDirectory( + this I18nResourceFactory resourceFactory, + string basePath) + where T : class + { + var rootDir = new DirectoryInfo(basePath); + + var files = rootDir.GetFiles().Where(x => x.Name.EndsWith(".json")); + foreach (var file in files) + { + var language = Path.GetFileNameWithoutExtension(file.Name); + var text = File.ReadAllText(file.FullName); + var dic = ReadJsonHelper.Read(new ReadOnlySequence(Encoding.UTF8.GetBytes(text)), new JsonReaderOptions { AllowTrailingCommas = true }); + + DictionaryResource jsonResource = new DictionaryResource(new CultureInfo(language), dic, typeof(T).Assembly); + resourceFactory.Add(jsonResource); + } + + return resourceFactory; + } + + /// + /// 添加 json 文件资源. + /// + /// + /// 语言. + /// json 文件路径. + /// . + public static I18nResourceFactory AddJsonFile(this I18nResourceFactory resourceFactory, string language, string jsonFile) + { + string s = File.ReadAllText(jsonFile); + Dictionary kvs = ReadJsonHelper.Read(new ReadOnlySequence(Encoding.UTF8.GetBytes(s)), new JsonReaderOptions + { + AllowTrailingCommas = true + }); + + DictionaryResource resource = new DictionaryResource(new CultureInfo(language), kvs); + resourceFactory.Add(resource); + return resourceFactory; + } + + /// + /// 添加 json 文件资源. + /// + /// 类型. + /// + /// 语言. + /// json 文件路径. + /// . + public static I18nResourceFactory AddJsonFile(this I18nResourceFactory resourceFactory, string language, string jsonFile) + where T : class + { + string s = File.ReadAllText(jsonFile); + Dictionary kvs = ReadJsonHelper.Read(new ReadOnlySequence(Encoding.UTF8.GetBytes(s)), new JsonReaderOptions + { + AllowTrailingCommas = true + }); + + DictionaryResource resource = new DictionaryResource(new CultureInfo(language), kvs, typeof(T).Assembly); + resourceFactory.Add(resource); + return resourceFactory; + } +} diff --git a/framework/Maomi.I18n/Extensions/LocalizationOptions.cs b/framework/Maomi.I18n/Extensions/LocalizationOptions.cs new file mode 100644 index 0000000..c1fea05 --- /dev/null +++ b/framework/Maomi.I18n/Extensions/LocalizationOptions.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Localization; + +namespace Maomi.I18n; + +/// +/// 多语言配置. +/// +public class LocalizationOptions +{ + /// + /// 多语言默认语言. + /// + public string DefaultLanguage { get; init; } = "zh-CN"; +} \ No newline at end of file diff --git a/framework/Maomi.I18n/I18NStringLocalizerHelper.cs b/framework/Maomi.I18n/I18NStringLocalizerHelper.cs new file mode 100644 index 0000000..d2e62d1 --- /dev/null +++ b/framework/Maomi.I18n/I18NStringLocalizerHelper.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; + +namespace Maomi.I18n; + +/// +/// I18nStringLocalizer 帮助类. +/// +internal static class I18NStringLocalizerHelper +{ + /// + /// 查找资源. + /// + /// + /// + /// + /// LocalizedString. + internal static LocalizedString Find(IEnumerable resources, string language, string name) + { + foreach (var resource in resources) + { + if (language != resource.SupportedCulture.Name) + { + continue; + } + + var result = resource.Get(language, name); + if (result == null || result.ResourceNotFound) + { + continue; + } + + return result; + } + + foreach (var resource in resources) + { + if (language != resource.SupportedCulture.Name) + { + continue; + } + + var result = resource.Get(language, name); + if (result == null || result.ResourceNotFound) + { + continue; + } + + return result; + } + + // 所有的资源都查找不到时,使用默认值 + return new LocalizedString(name, name, resourceNotFound: true); + } + + /// + /// 查找资源. + /// + /// + /// + /// + /// + /// LocalizedString. + internal static LocalizedString Find(IEnumerable resources, string language, string name, params object[] arguments) + { + foreach (var resource in resources) + { + if (language != resource.SupportedCulture.Name) + { + continue; + } + + var result = resource.Get(language, name, arguments); + if (result == null || result.ResourceNotFound) + { + continue; + } + + return result; + } + + foreach (var resource in resources) + { + if (language != resource.SupportedCulture.Name) + { + continue; + } + + var result = resource.Get(language, name, arguments); + if (result == null || result.ResourceNotFound) + { + continue; + } + + return result; + } + + // 所有的资源都查找不到时,使用默认值 + return new LocalizedString(name, string.Format(name, arguments), resourceNotFound: true); + } +} diff --git a/framework/Maomi.I18n/I18nHelper.cs b/framework/Maomi.I18n/I18nHelper.cs new file mode 100644 index 0000000..550d833 --- /dev/null +++ b/framework/Maomi.I18n/I18nHelper.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Maomi.I18n; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using System.Globalization; + +namespace Maomi; + +/// +/// i18n 帮助器. +/// +public static class I18nHelper +{ + /// + /// 创建语言资源工厂. + /// + /// . + public static I18nResourceFactory CreateFactory() + { + return new InternalI18nResourceFactory(); + } + + /// + /// 创建多语言翻译接口. + /// + /// 多语言上下文. + /// 多语言资源工厂. + /// 服务提供器. + /// 语言名称. + /// . + public static IStringLocalizer CreateStringLocalizer( + I18nContext context, + I18nResourceFactory? resourceFactory = null, + IServiceProvider? serviceProvider = null, + string? defaultLanguage = null) + { + resourceFactory ??= CreateFactory(); + + serviceProvider ??= new ServiceCollection().BuildServiceProvider(); + + defaultLanguage ??= CultureInfo.CurrentCulture.Name; + + return new I18nStringLocalizer(context, resourceFactory, serviceProvider, new LocalizationOptions + { + DefaultLanguage = defaultLanguage + }); + } +} diff --git a/framework/Maomi.I18n/I18nStringLocalizer.cs b/framework/Maomi.I18n/I18nStringLocalizer.cs new file mode 100644 index 0000000..93aed95 --- /dev/null +++ b/framework/Maomi.I18n/I18nStringLocalizer.cs @@ -0,0 +1,133 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; + +namespace Maomi.I18n; + +/// +/// 表示提供本地化字符串的服务. +/// +public class I18nStringLocalizer : IStringLocalizer +{ + private readonly IServiceProvider _serviceProvider; + private readonly I18nContext _context; + private readonly LocalizationOptions _localizationOptions; + private readonly I18nResourceFactory _resourceFactory; + private readonly Lazy> _iocLocalizerResources; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public I18nStringLocalizer(I18nContext context, I18nResourceFactory resourceFactory, IServiceProvider serviceProvider, LocalizationOptions localizationOptions) + { + _context = context; + _resourceFactory = resourceFactory; + _serviceProvider = serviceProvider; + _localizationOptions = localizationOptions; + + // 从容器中取出 + _iocLocalizerResources = new Lazy>(() => + { + List resources = new(); + foreach (var serviceType in _resourceFactory.ServiceResources) + { + var resource = _serviceProvider.GetRequiredService(serviceType) as I18nResource; + if (resource == null) + { + continue; + } + + resources.Add(resource); + } + + return resources; + }); + } + + /// + public LocalizedString this[string name] => Find(name); + + /// + public LocalizedString this[string name, params object[] arguments] => Find(name, arguments); + + /// + public IEnumerable GetAllStrings(bool includeParentCultures) + { + foreach (var resource in _iocLocalizerResources.Value) + { + foreach (var item in resource.GetAllStrings(includeParentCultures)) + { + yield return item; + } + } + + foreach (var resource in _resourceFactory.Resources) + { + foreach (var item in resource.GetAllStrings(includeParentCultures)) + { + yield return item; + } + } + } + + private LocalizedString Find(string name) + { + // 先查找静态实例 + var result = I18NStringLocalizerHelper.Find(_resourceFactory.Resources, _context.Culture.Name, name); + if (!result.ResourceNotFound) + { + return result; + } + + // 从容器中使用提供器查找 + result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _context.Culture.Name, name); + + if (!result.ResourceNotFound) + { + return result; + } + + // 降级使用默认语言 + if (result.ResourceNotFound == true && _localizationOptions.DefaultLanguage != _context.Culture.Name) + { + return result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _localizationOptions.DefaultLanguage, name); + } + + return result; + } + + private LocalizedString Find(string name, params object[] arguments) + { + // 先查找静态实例 + var result = I18NStringLocalizerHelper.Find(_resourceFactory.Resources, _context.Culture.Name, name, arguments); + if (!result.ResourceNotFound) + { + return result; + } + + // 从容器中使用提供器查找 + result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _context.Culture.Name, name); + + if (!result.ResourceNotFound) + { + return result; + } + + // 降级使用默认语言 + if (result.ResourceNotFound == true && _localizationOptions.DefaultLanguage != _context.Culture.Name) + { + return result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _localizationOptions.DefaultLanguage, name, arguments); + } + + return result; + } +} diff --git a/framework/Maomi.I18n/I18nStringLocalizerFactory.cs b/framework/Maomi.I18n/I18nStringLocalizerFactory.cs new file mode 100644 index 0000000..7991d61 --- /dev/null +++ b/framework/Maomi.I18n/I18nStringLocalizerFactory.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; + +namespace Maomi.I18n; + +/// +/// 表示创建 实例的工厂. +/// +public class I18nStringLocalizerFactory : IStringLocalizerFactory +{ + private readonly IServiceScopeFactory _serviceScope; + + /// + /// Initializes a new instance of the class. + /// + /// + public I18nStringLocalizerFactory(IServiceScopeFactory serviceScope) + { + _serviceScope = serviceScope; + } + + /// + public IStringLocalizer Create(Type resourceSource) + { + var ioc = _serviceScope.CreateScope().ServiceProvider; + var type = typeof(I18nStringLocalizer<>).MakeGenericType(resourceSource); + return (ioc.GetRequiredService(type) as IStringLocalizer)!; + } + + /// + public IStringLocalizer Create(string baseName, string location) + { + var ioc = _serviceScope.CreateScope().ServiceProvider; + + return ioc.GetRequiredService(); + } +} diff --git a/framework/Maomi.I18n/I18nStringLocalizer{T}.cs b/framework/Maomi.I18n/I18nStringLocalizer{T}.cs new file mode 100644 index 0000000..c040883 --- /dev/null +++ b/framework/Maomi.I18n/I18nStringLocalizer{T}.cs @@ -0,0 +1,162 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; + +namespace Maomi.I18n; + +/// +/// 表示提供本地化字符串的服务. +/// +/// 类型. +public class I18nStringLocalizer : IStringLocalizer +{ + private readonly IServiceProvider _serviceProvider; + private readonly I18nContext _context; + private readonly LocalizationOptions _localizationOptions; + private readonly I18nResourceFactory _resourceFactory; + private readonly Lazy> _iocLocalizerResources; + private readonly Lazy> _staticLocalizerResources; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public I18nStringLocalizer(I18nContext context, I18nResourceFactory resourceFactory, IServiceProvider serviceProvider, LocalizationOptions localizationOptions) + { + _context = context; + _resourceFactory = resourceFactory; + _serviceProvider = serviceProvider; + _localizationOptions = localizationOptions; + + _iocLocalizerResources = new Lazy>(() => + { + List resources = new(); + foreach (var serviceType in _resourceFactory.ServiceResources) + { + if (!serviceType.IsGenericType || serviceType.GenericTypeArguments[0].Assembly != typeof(T).Assembly) + { + continue; + } + + var resource = _serviceProvider.GetRequiredService(serviceType) as I18nResource; + if (resource == null) + { + continue; + } + + resources.Add(resource); + } + + return resources; + }); + + _staticLocalizerResources = new Lazy>(() => + { + List resources = new(); + foreach (var resource in _resourceFactory.Resources) + { + if (!resource.GetType().IsGenericType || resource.GetType().GenericTypeArguments[0].Assembly != typeof(T).Assembly) + { + continue; + } + + resources.Add(resource); + } + + return resources; + }); + } + + /// + public LocalizedString this[string name] => Find(name); + + /// + public LocalizedString this[string name, params object[] arguments] => Find(name, arguments); + + /// + public IEnumerable GetAllStrings(bool includeParentCultures) + { + foreach (var serviceType in _resourceFactory.ServiceResources) + { + var resource = _serviceProvider.GetRequiredService(serviceType) as I18nResource; + if (resource == null) + { + continue; + } + + foreach (var item in resource.GetAllStrings(includeParentCultures)) + { + yield return item; + } + } + + foreach (var resource in _resourceFactory.Resources) + { + foreach (var item in resource.GetAllStrings(includeParentCultures)) + { + yield return item; + } + } + } + + private LocalizedString Find(string name) + { + // 先查找静态实例 + var result = I18NStringLocalizerHelper.Find(_staticLocalizerResources.Value, _context.Culture.Name, name); + if (!result.ResourceNotFound) + { + return result; + } + + // 从容器中使用提供器查找 + result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _context.Culture.Name, name); + + if (!result.ResourceNotFound) + { + return result; + } + + // 降级使用默认语言 + if (result.ResourceNotFound == true && _localizationOptions.DefaultLanguage != _context.Culture.Name) + { + return result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _localizationOptions.DefaultLanguage, name); + } + + return result; + } + + private LocalizedString Find(string name, params object[] arguments) + { + // 先查找静态实例 + var result = I18NStringLocalizerHelper.Find(_staticLocalizerResources.Value, _context.Culture.Name, name, arguments); + if (!result.ResourceNotFound) + { + return result; + } + + // 从容器中使用提供器查找 + result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _context.Culture.Name, name, arguments); + + if (!result.ResourceNotFound) + { + return result; + } + + // 降级使用默认语言 + if (result.ResourceNotFound == true && _localizationOptions.DefaultLanguage != _context.Culture.Name) + { + return result = I18NStringLocalizerHelper.Find(_iocLocalizerResources.Value, _localizationOptions.DefaultLanguage, name, arguments); + } + + // 所有的资源都查找不到时,使用默认值 + return new LocalizedString(name, string.Format(name, arguments), resourceNotFound: true); + } +} \ No newline at end of file diff --git a/framework/Maomi.I18n/Internals/DefaultI18nContext.cs b/framework/Maomi.I18n/Internals/DefaultI18nContext.cs new file mode 100644 index 0000000..a4c908e --- /dev/null +++ b/framework/Maomi.I18n/Internals/DefaultI18nContext.cs @@ -0,0 +1,14 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi.I18n; + +/// +/// 从 http 请求上下文中获取多语言信息. +/// +public class DefaultI18nContext : I18nContext +{ +} diff --git a/framework/Maomi.I18n/Internals/InternalI18nResourceFactory.cs b/framework/Maomi.I18n/Internals/InternalI18nResourceFactory.cs new file mode 100644 index 0000000..f74b6ab --- /dev/null +++ b/framework/Maomi.I18n/Internals/InternalI18nResourceFactory.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Globalization; + +namespace Maomi.I18n; + +/// +/// i18n 语言资源管理器. +/// +public class InternalI18nResourceFactory : I18nResourceFactory +{ + private readonly HashSet _supportedCultures; + private readonly HashSet _resources; + private readonly HashSet _serviceResources; + + /// + /// Initializes a new instance of the class. + /// + public InternalI18nResourceFactory() + { + _supportedCultures = new(); + _resources = new HashSet(); + _serviceResources = new(); + } + + /// + public ICollection SupportedCultures => _supportedCultures; + + /// + public ICollection Resources => _resources; + + /// + public ICollection ServiceResources => _serviceResources; + + /// + public I18nResourceFactory Add(I18nResource resource) + { + _supportedCultures.Add(resource.SupportedCulture); + _resources.Add(resource); + return this; + } + + /// + public I18nResourceFactory Add(I18nResource resource) + { + _supportedCultures.Add(resource.SupportedCulture); + _resources.Add(resource); + return this; + } + + /// + public I18nResourceFactory AddServiceType(Type resourceType) + { + _serviceResources.Add(resourceType); + return this; + } +} diff --git a/framework/Maomi.I18n/Json/DictionaryResource.cs b/framework/Maomi.I18n/Json/DictionaryResource.cs new file mode 100644 index 0000000..0223a86 --- /dev/null +++ b/framework/Maomi.I18n/Json/DictionaryResource.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.Localization; +using System.Globalization; + +namespace Maomi.I18n; + +/// +/// 字典存储多语言文件资源. +/// +public class DictionaryResource : I18nResource +{ + /// + public CultureInfo SupportedCulture => _cultureInfo; + + private readonly CultureInfo _cultureInfo; + private readonly IReadOnlyDictionary _kvs; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public DictionaryResource(CultureInfo cultureInfo, IReadOnlyDictionary kvs) + { + _cultureInfo = cultureInfo; + _kvs = kvs.ToDictionary(x => x.Key, x => new LocalizedString(x.Key, x.Value.ToString()!)); + } + + /// + public virtual LocalizedString Get(string culture, string name) + { + if (culture != _cultureInfo.Name) + { + return new LocalizedString(name, name, resourceNotFound: true); + } + + var value = _kvs.GetValueOrDefault(name); + if (value == null) + { + return new LocalizedString(name, name, resourceNotFound: true); + } + + return value; + } + + /// + public virtual LocalizedString Get(string culture, string name, params object[] arguments) + { + if (culture != _cultureInfo.Name) + { + return new LocalizedString(name, name, resourceNotFound: true); + } + + var value = _kvs.GetValueOrDefault(name); + if (value == null) + { + return new LocalizedString(name, name, resourceNotFound: true); + } + + return new LocalizedString(name, string.Format(value, arguments)); + } + + /// + public virtual IEnumerable GetAllStrings(bool includeParentCultures) + { + return _kvs.Values; + } +} diff --git a/framework/Maomi.I18n/Json/DictionaryResource{TResource}.cs b/framework/Maomi.I18n/Json/DictionaryResource{TResource}.cs new file mode 100644 index 0000000..86ebeef --- /dev/null +++ b/framework/Maomi.I18n/Json/DictionaryResource{TResource}.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.Extensions.Localization; +using System.Globalization; +using System.Reflection; + +namespace Maomi.I18n; + +/// +/// 字典存储多语言文件资源. +/// +/// 类型. +public class DictionaryResource : DictionaryResource, I18nResource +{ + private readonly Assembly _assembly; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public DictionaryResource(CultureInfo cultureInfo, IReadOnlyDictionary kvs, Assembly assembly) + : base(cultureInfo, kvs) + { + _assembly = assembly; + } + + /// + public override LocalizedString Get(string culture, string name) + { + if (typeof(TResource).Assembly != _assembly) + { + return new LocalizedString(name, name, resourceNotFound: true); + } + + return base.Get(culture, name); + } + + /// + public override LocalizedString Get(string culture, string name, params object[] arguments) + { + if (typeof(TResource).Assembly != _assembly) + { + return new LocalizedString(name, name, resourceNotFound: true); + } + + return base.Get(culture, name, arguments); + } +} diff --git a/src/MaomiFramework/framework/Maomi.I18n/ReadJsonHelper.cs b/framework/Maomi.I18n/Json/ReadJsonHelper.cs similarity index 78% rename from src/MaomiFramework/framework/Maomi.I18n/ReadJsonHelper.cs rename to framework/Maomi.I18n/Json/ReadJsonHelper.cs index 83d9926..7a7ff60 100644 --- a/src/MaomiFramework/framework/Maomi.I18n/ReadJsonHelper.cs +++ b/framework/Maomi.I18n/Json/ReadJsonHelper.cs @@ -1,9 +1,25 @@ -using System.Buffers; +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Buffers; using System.Text.Json; namespace Maomi.I18n; + +/// +/// JSON 读取帮助类. +/// public static class ReadJsonHelper { + /// + /// 读取 json 字节流. + /// + /// + /// + /// 字典集合. public static Dictionary Read(ReadOnlySequence sequence, JsonReaderOptions jsonReaderOptions) { var reader = new Utf8JsonReader(sequence, jsonReaderOptions); @@ -22,7 +38,10 @@ private static void BuildJsonField(ref Utf8JsonReader reader, Dictionary + + + Library + net7.0;net8.0;net9.0 + enable + enable + Maomi + true + 2.2.2 + Codestin Search App + Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 + True + true + MIT + package.png + https://maomi.whuanle.cn + https://github.com/whuanle/maomi + README.md + git + + Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 + + True + snupkg + True + + + + + + + + + + + + + + + + + True + \ + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaomiFramework/framework/Maomi.I18n/Properties/launchSettings.json b/framework/Maomi.I18n/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.I18n/Properties/launchSettings.json rename to framework/Maomi.I18n/Properties/launchSettings.json diff --git a/framework/Maomi.I18n/package.png b/framework/Maomi.I18n/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/framework/Maomi.I18n/package.png differ diff --git a/framework/Maomi.Swagger/Maomi.Swagger.csproj b/framework/Maomi.Swagger/Maomi.Swagger.csproj new file mode 100644 index 0000000..30013df --- /dev/null +++ b/framework/Maomi.Swagger/Maomi.Swagger.csproj @@ -0,0 +1,71 @@ + + + Library + net7.0;net8.0;net9.0 + enable + enable + Maomi + true + 2.2.2 + Codestin Search App + Maomi.Web.Core 框架是一个简洁的 Web 开发框架,简化定制 ASP.NET Core 的代码复杂度,提供一些基础功能。 + True + true + MIT + package.png + https://maonmi.whuanle.cn + https://github.com/whuanle/maonmi + README.md + git + + Maomi.Web.Core 框架是一个简洁的 Web 开发框架,简化定制 ASP.NET Core 的代码复杂度,提供一些基础功能。 + + True + snupkg + True + + + + + + + + + + + + + True + \ + Always + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/framework/Maomi.Swagger/MaomiSwaggerOptions.cs b/framework/Maomi.Swagger/MaomiSwaggerOptions.cs new file mode 100644 index 0000000..f19e3d5 --- /dev/null +++ b/framework/Maomi.Swagger/MaomiSwaggerOptions.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +namespace Maomi.Web.Core; + +/// +/// swagger 配置. +/// +public class MaomiSwaggerOptions +{ + /// + /// 默认分组名称. + /// + public string DefaultGroupName { get; set; } = "default"; + + /// + /// 默认标题. + /// + public string DefaultGroupTitle { get; set; } = "default"; +} \ No newline at end of file diff --git a/framework/Maomi.Swagger/MaomiSwaggerSchemaFilter.cs b/framework/Maomi.Swagger/MaomiSwaggerSchemaFilter.cs new file mode 100644 index 0000000..1e3f9fa --- /dev/null +++ b/framework/Maomi.Swagger/MaomiSwaggerSchemaFilter.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Reflection; +using System.Text.Json.Serialization; + +namespace Maomi.Web.Core; + +/// +/// Swagger 模型类过滤器. +/// +public class MaomiSwaggerSchemaFilter : ISchemaFilter +{ + /// + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + // 模型类的类型 + var type = context.Type; + + // 如果 API 参数不是对象 + if (type.IsPrimitive || TypeInfo.GetTypeCode(type) != TypeCode.Object) + { + return; + } + + // 如果 API 参数是对象类型 + + // 获取类型的所有属性 + PropertyInfo[] ps = context.Type.GetProperties(); + + // 获取 swagger 文件显示的所有属性 + // 注意文档属性是已经已经生成的,这里进行后期转换,替换为需要显示的类型 + foreach (var property in schema.Properties) + { + var p = ps.FirstOrDefault(x => x.Name.ToLower() == property.Key.ToLower()); + if (p == null) + { + continue; + } + + var t = property.Value.Type; + var converter = p.GetCustomAttribute(); + if (converter == null || converter.ConverterType == null) + { + continue; + } + + var targetType = TypeInfo.GetTypeCode(converter.ConverterType); + + // 如果是基元类型或 Decimal、DateTime + if (targetType != TypeCode.Empty && + targetType != TypeCode.DBNull && + targetType != TypeCode.Object) + { + if (GetValueType(targetType, out var valueType)) + { + property.Value.Type = valueType; + } + } + } + + static bool GetValueType(TypeCode targetType, out string? valueType) + { + valueType = null; + switch (targetType) + { + case TypeCode.Boolean: valueType = "boolean"; break; + case TypeCode.Char: valueType = "string"; break; + case TypeCode.SByte: valueType = "integer"; break; + case TypeCode.Byte: valueType = "integer"; break; + case TypeCode.Int16: valueType = "integer"; break; + case TypeCode.UInt16: valueType = "integer"; break; + case TypeCode.Int32: valueType = "integer"; break; + case TypeCode.UInt32: valueType = "integer"; break; + case TypeCode.Int64: valueType = "integer"; break; + case TypeCode.UInt64: valueType = "integer"; break; + case TypeCode.Single: valueType = "number"; break; + case TypeCode.Double: valueType = "number"; break; + case TypeCode.Decimal: valueType = "number"; break; + case TypeCode.DateTime: valueType = "string"; break; + case TypeCode.String: valueType = "string"; break; + + // 一般不需要处理对象 + // case TypeCode.Object: valueType = p.PropertyType.Name; break; + default: return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Swagger/Properties/launchSettings.json b/framework/Maomi.Swagger/Properties/launchSettings.json similarity index 73% rename from src/MaomiFramework/framework/Maomi.Swagger/Properties/launchSettings.json rename to framework/Maomi.Swagger/Properties/launchSettings.json index b8e0a4a..caf443a 100644 --- a/src/MaomiFramework/framework/Maomi.Swagger/Properties/launchSettings.json +++ b/framework/Maomi.Swagger/Properties/launchSettings.json @@ -6,7 +6,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:6845;http://localhost:6846" + "applicationUrl": "https://localhost:63727;http://localhost:63728" } } } \ No newline at end of file diff --git a/framework/Maomi.Swagger/SwaggerExtensions.cs b/framework/Maomi.Swagger/SwaggerExtensions.cs new file mode 100644 index 0000000..a98f818 --- /dev/null +++ b/framework/Maomi.Swagger/SwaggerExtensions.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using Swashbuckle.AspNetCore.SwaggerUI; +using System.Reflection; + +namespace Maomi.Web.Core; + +/// +/// Swagger 扩展. +/// +public static class SwaggerExtensions +{ + private static readonly HashSet ApiAssemblies = new(); + + /// + /// Swagger 配置,用于生成 swwagger.json 文件. + /// + /// . + /// swagger 配置. + /// 自定义配置. + /// 设置 API 版本号. + /// 设置界面如何显示 API 版本号. + public static void AddMaomiSwaggerGen( + this IServiceCollection services, + Action? setupMaomiSwaggerAction = null, + Action? setupSwaggerAction = null, + Action? setupApiVersionAction = null, + Action? setupApiExplorerAction = null) + { + if (setupMaomiSwaggerAction == null) + { + setupMaomiSwaggerAction = option => { }; + } + + services.Configure(setupMaomiSwaggerAction); + + if (setupApiVersionAction != null) + { + // 配置 Api 版本信息 + Action defaultSetupApiVersionAction = (setup) => + { + // 全局默认 api 版本号 + setup.DefaultApiVersion = new ApiVersion(1, 0); + + // 用户请求未指定版本号时,使用默认版本号 + setup.AssumeDefaultVersionWhenUnspecified = true; + + // 响应时,在 header 中返回版本号 + setup.ReportApiVersions = true; + + // 从哪里读取版本号信息 + setup.ApiVersionReader = + ApiVersionReader.Combine( + new HeaderApiVersionReader("X-Api-Version"), + new QueryStringApiVersionReader("version")); + }; + + defaultSetupApiVersionAction += setupApiVersionAction; + services.AddApiVersioning(defaultSetupApiVersionAction); + } + + if (setupApiExplorerAction != null) + { + var defaultVersion = services.BuildServiceProvider() + .GetRequiredService>() + .Value.DefaultApiVersion; + + // 在 swagger 中显示版本信息, + // 进一步使用版本号进行隔分 + Action defaultSetupApiExplorerAction = setup => + { + }; + + defaultSetupApiExplorerAction += setupApiExplorerAction; + + services.AddVersionedApiExplorer(defaultSetupApiExplorerAction); + } + + services.AddSwaggerGen(options => + { + // 模型类过滤器 + options.SchemaFilter(); + + var ioc = services.BuildServiceProvider(); + + // 提供对程序中所有 ApiDescriptionGroup 对象的访问, + // ApiDescriptionGroup 记录 Controller 的分组描述信息 + var descriptionProvider = ioc.GetRequiredService(); + var apiVersionDescriptionProvider = ioc.GetRequiredService(); + var apiVersionoptions = ioc.GetRequiredService>(); + var maomiSwaggerOptions = ioc.GetRequiredService>(); + + // 配置分组信息 + // Items 是根据 ApiExplorerSettings.GroupName 进行分组的 + foreach (var description in descriptionProvider.ApiDescriptionGroups.Items) + { + // 如果 Controller 没有配置分组,则放到默认分组中 + if (description.GroupName == null) + { + options.SwaggerDoc(maomiSwaggerOptions.Value.DefaultGroupName, new OpenApiInfo + { + // 分组默认的 Api 版本号 + Version = apiVersionoptions.Value.DefaultApiVersion.ToString(), + Title = maomiSwaggerOptions.Value.DefaultGroupTitle + }); + + // 保存每个 Action 反射的 MethodInfo + foreach (var item in description.Items) + { + if (item.TryGetMethodInfo(out var methodInfo)) + { + var assembly = methodInfo.DeclaringType?.Assembly; + if (assembly != null) + { + ApiAssemblies.Add(assembly); + } + } + } + } + else + { + options.SwaggerDoc(description.GroupName, new OpenApiInfo + { + Version = apiVersionoptions.Value.DefaultApiVersion.ToString(), + Title = description.GroupName, + }); + } + } + + // 加载所有控制器对应程序集的文档 + var dir = new DirectoryInfo(AppContext.BaseDirectory); + var files = dir.GetFiles().Where(x => x.Name.EndsWith(".xml")).ToArray(); + foreach (var item in files) + { + // 如果 Controller 程序集的 xml 文件存在,则加载 + if (ApiAssemblies.Any(x => item.Name.Equals(x.GetName().Name + ".xml", StringComparison.CurrentCultureIgnoreCase))) + { + options.IncludeXmlComments(item.FullName); + } + } + + options.BuildGroupApis(maomiSwaggerOptions.Value); + + // 最后使用用户自定义配置代码 + if (setupSwaggerAction != null) + { + setupSwaggerAction.Invoke(options); + } + }); + } + + /// + /// swagger 页面显示配置. + /// + /// + /// 配置 swagger. + /// 配置显示规则. + /// . + public static IApplicationBuilder UseMaomiSwagger( + this IApplicationBuilder app, + Action? setupAction = null, + Action? setupUIAction = null) + { + var ioc = app.ApplicationServices; + var descriptionProvider = ioc.GetRequiredService(); + var maomiSwaggerOptions = ioc.GetRequiredService>(); + + app.UseSwagger(setupAction); + + app.UseSwaggerUI(options => + { + bool haveDefault = false; + + // 配置页面显示和使用哪些位置的 swagger.json 文件 + foreach (var description in descriptionProvider.ApiDescriptionGroups.Items) + { + if (description.GroupName == null) + { + haveDefault = true; + continue; + } + + options.SwaggerEndpoint($"{description.GroupName}/swagger.json", description.GroupName); + } + + // 有默认不带分组的 + if (haveDefault) + { + options.SwaggerEndpoint($"{maomiSwaggerOptions.Value.DefaultGroupName}/swagger.json", maomiSwaggerOptions.Value.DefaultGroupName); + } + + // 执行用户自定义配置 + if (setupUIAction != null) + { + setupUIAction.Invoke(options); + } + }); + + return app; + } + + // 配置每个分组中有哪些 Action + private static void BuildGroupApis(this SwaggerGenOptions swaggerGenOptions, MaomiSwaggerOptions maomiSwaggerOptions) + { + // docname == GroupName + swaggerGenOptions.DocInclusionPredicate((string docname, ApiDescription apiDescription) => + { + if (!apiDescription.TryGetMethodInfo(out MethodInfo methodInfo)) + { + return false; + } + + // 属于默认分组 + if (docname == maomiSwaggerOptions.DefaultGroupName && apiDescription.GroupName == null) + { + return true; + } + + return apiDescription.GroupName == docname; + }); + } +} \ No newline at end of file diff --git a/framework/Maomi.Swagger/package.png b/framework/Maomi.Swagger/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/framework/Maomi.Swagger/package.png differ diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/Maomi.Web.Core.Test.csproj b/framework/Maomi.Web.Core.Test/Maomi.Web.Core.Test.csproj similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core.Test/Maomi.Web.Core.Test.csproj rename to framework/Maomi.Web.Core.Test/Maomi.Web.Core.Test.csproj diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/ModuleTest.cs b/framework/Maomi.Web.Core.Test/ModuleTest.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core.Test/ModuleTest.cs rename to framework/Maomi.Web.Core.Test/ModuleTest.cs diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/Properties/launchSettings.json b/framework/Maomi.Web.Core.Test/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core.Test/Properties/launchSettings.json rename to framework/Maomi.Web.Core.Test/Properties/launchSettings.json diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/SwaggerTest.cs b/framework/Maomi.Web.Core.Test/SwaggerTest.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core.Test/SwaggerTest.cs rename to framework/Maomi.Web.Core.Test/SwaggerTest.cs diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/Usings.cs b/framework/Maomi.Web.Core.Test/Usings.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core.Test/Usings.cs rename to framework/Maomi.Web.Core.Test/Usings.cs diff --git a/framework/Maomi.Web.Core/Maomi.Web.Core.csproj b/framework/Maomi.Web.Core/Maomi.Web.Core.csproj new file mode 100644 index 0000000..d8b48f5 --- /dev/null +++ b/framework/Maomi.Web.Core/Maomi.Web.Core.csproj @@ -0,0 +1,58 @@ + + + Library + net8.0 + enable + enable + Maomi + true + 2.2.2 + Codestin Search App + Maomi.Web.Core 框架是一个简洁的 Web 开发框架,简化定制 ASP.NET Core 的代码复杂度,提供一些基础功能。 + True + true + MIT + package.png + https://maonmi.whuanle.cn + https://github.com/whuanle/maonmi + README.md + git + + Maomi.Web.Core 框架是一个简洁的 Web 开发框架,简化定制 ASP.NET Core 的代码复杂度,提供一些基础功能。 + + True + snupkg + True + + + + + + + + + + + + + True + \ + Always + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + \ No newline at end of file diff --git a/framework/Maomi.Web.Core/MaomiWebModule.cs b/framework/Maomi.Web.Core/MaomiWebModule.cs new file mode 100644 index 0000000..d215ba0 --- /dev/null +++ b/framework/Maomi.Web.Core/MaomiWebModule.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using Maomi.I18n; + +namespace Maomi.Web.Core; + +/// +/// Web 扩展方法. +/// +public class MaomiWebModule : IModule +{ + /// + public void ConfigureServices(ServiceContext context) + { + // i18n 服务 + context.Services.AddI18nAspNetCore(); + + // 添加控制器 + context.Services.AddControllers(options => + { + }) + .AddI18nDataAnnotation(); + } +} diff --git a/framework/Maomi.Web.Core/Models/PageArrayRes{T}.cs b/framework/Maomi.Web.Core/Models/PageArrayRes{T}.cs new file mode 100644 index 0000000..c975db5 --- /dev/null +++ b/framework/Maomi.Web.Core/Models/PageArrayRes{T}.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Net; + +namespace Maomi.Web.Core; + +/// +/// 分页结果模型类. +/// +/// 类型. +public partial class PageArrayRes : Res> +{ +} diff --git a/framework/Maomi.Web.Core/Models/PageListRes{T}.cs b/framework/Maomi.Web.Core/Models/PageListRes{T}.cs new file mode 100644 index 0000000..6832e4c --- /dev/null +++ b/framework/Maomi.Web.Core/Models/PageListRes{T}.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Net; + +namespace Maomi.Web.Core; + +/// +/// 分页结果模型类. +/// +/// 类型. +public partial class PageListRes : Res>> +{ +} diff --git a/framework/Maomi.Web.Core/Models/PageRes{T}.cs b/framework/Maomi.Web.Core/Models/PageRes{T}.cs new file mode 100644 index 0000000..ad0d5ee --- /dev/null +++ b/framework/Maomi.Web.Core/Models/PageRes{T}.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Net; + +namespace Maomi.Web.Core; + +/// +/// 分页结果模型类. +/// +/// 类型. +public class PageRes +{ + /// + /// 当前页. + /// + public virtual int PageNo { get; set; } + + /// + /// 页大小. + /// + public virtual int PageSize { get; set; } + + /// + /// 列表. + /// + public virtual T? List { get; set; } +} diff --git a/framework/Maomi.Web.Core/Models/Res.cs b/framework/Maomi.Web.Core/Models/Res.cs new file mode 100644 index 0000000..9321745 --- /dev/null +++ b/framework/Maomi.Web.Core/Models/Res.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) Maomi. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Github link: https://github.com/whuanle/maomi +// + +using System.Net; + +namespace Maomi.Web.Core; + +/// +/// 响应模型类. +/// +/// 类型. +public class Res +{ + /// + /// 当前请求是否有错误. + /// + public virtual bool IsSuccess => Code == 200; + + /// + /// 业务代码. + /// + public virtual int Code { get; set; } + + /// + /// 响应消息. + /// + public virtual string Msg { get; set; } = default!; + + /// + /// 返回数据. + /// + public virtual T? Data { get; set; } +} + +/// +/// 响应模型类. +/// +public partial class Res : Res +{ + /// + /// 创建 . + /// + /// 类型. + /// 响应代码. + /// 响应信息. + /// 响应内容. + /// . + public static Res Create(int code, string message, T data) + { + return new Res + { + Code = code, + Msg = message, + Data = data + }; + } + + /// + /// 创建 . + /// + /// 类型. + /// 响应代码. + /// 响应信息. + /// . + public static Res Create(int code, string message) + { + return new Res + { + Code = code, + Msg = message + }; + } + + /// + /// 创建 . + /// + /// 响应代码. + /// 响应信息. + /// . + public static Res Create(int code, string message) + { + return new Res + { + Code = code, + Msg = message + }; + } + + /// + /// 创建 . + /// + /// 类型. + /// 响应代码. + /// 响应信息. + /// 响应内容. + /// . + public static Res Create(HttpStatusCode code, string message, T data) => Create((int)code, message, data); +} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Properties/launchSettings.json b/framework/Maomi.Web.Core/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core/Properties/launchSettings.json rename to framework/Maomi.Web.Core/Properties/launchSettings.json diff --git a/framework/Maomi.Web.Core/package.png b/framework/Maomi.Web.Core/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/framework/Maomi.Web.Core/package.png differ diff --git a/src/MaomiFramework/logo.png b/logo.png similarity index 100% rename from src/MaomiFramework/logo.png rename to logo.png diff --git a/src/MaomiFramework/logoz.png b/logoz.png similarity index 100% rename from src/MaomiFramework/logoz.png rename to logoz.png diff --git a/src/MaomiFramework/demo/1/Demo1.Api/ApiModule.cs b/src/MaomiFramework/demo/1/Demo1.Api/ApiModule.cs deleted file mode 100644 index e0bdc58..0000000 --- a/src/MaomiFramework/demo/1/Demo1.Api/ApiModule.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Demo1.Application; -using Maomi.Module; - -namespace Demo1.Api -{ - [InjectModule] - public class ApiModule : IModule - { - private readonly IConfiguration _configuration; - public ApiModule(IConfiguration configuration) - { - _configuration = configuration; - } - - public void ConfigureServices(ServiceContext context) - { - var configuration = context.Configuration; - context.Services.AddCors(); - } - } -} diff --git a/src/MaomiFramework/demo/1/Demo1.Api/Controllers/IndexController.cs b/src/MaomiFramework/demo/1/Demo1.Api/Controllers/IndexController.cs deleted file mode 100644 index 8b9ddd3..0000000 --- a/src/MaomiFramework/demo/1/Demo1.Api/Controllers/IndexController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Demo1.Application; -using Microsoft.AspNetCore.Mvc; - -namespace Demo1.Api.Controllers -{ - [ApiController] - [Route("[controller]")] - public class IndexController : ControllerBase - { - - private readonly IMyService _service; - - public IndexController(IMyService service) - { - _service = service; - } - - [HttpGet(Name = "sum")] - public int Get(int a, int b) - { - return _service.Sum(a, b); - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/demo/1/Demo1.Application/ApplicationModule.cs b/src/MaomiFramework/demo/1/Demo1.Application/ApplicationModule.cs deleted file mode 100644 index 23c11a7..0000000 --- a/src/MaomiFramework/demo/1/Demo1.Application/ApplicationModule.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Maomi.Module; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Demo1.Application -{ - public class ApplicationModule : IModule - { - // 模块类中可以使用依赖注入 - private readonly IConfiguration _configuration; - public ApplicationModule(IConfiguration configuration) - { - _configuration = configuration; - } - - public void ConfigureServices(ServiceContext services) - { - // services.Services.AddScoped(); - } - } -} diff --git a/src/MaomiFramework/demo/1/Demo1.Application/IMyService.cs b/src/MaomiFramework/demo/1/Demo1.Application/IMyService.cs deleted file mode 100644 index 89638d6..0000000 --- a/src/MaomiFramework/demo/1/Demo1.Application/IMyService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Demo1.Application -{ - public interface IMyService - { - int Sum(int a, int b); - } -} diff --git a/src/MaomiFramework/demo/1/Demo1.Application/MyService.cs b/src/MaomiFramework/demo/1/Demo1.Application/MyService.cs deleted file mode 100644 index 5a3f39b..0000000 --- a/src/MaomiFramework/demo/1/Demo1.Application/MyService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Maomi.Module; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Demo1.Application -{ - //[InjectOn(ServiceLifetime.Scoped, Own = true)] - [InjectOnScoped(Own = true)] - public class MyService : IMyService - { - public int Sum(int a, int b) - { - return a + b; - } - } -} diff --git a/src/MaomiFramework/demo/5/Demo5.Console/i18n/Demo5.Console/en-US.json b/src/MaomiFramework/demo/5/Demo5.Console/i18n/Demo5.Console/en-US.json deleted file mode 100644 index 80658c3..0000000 --- a/src/MaomiFramework/demo/5/Demo5.Console/i18n/Demo5.Console/en-US.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test": "console" -} \ No newline at end of file diff --git a/src/MaomiFramework/demo/5/Demo5.Console/i18n/Demo5.Console/zh-CN.json b/src/MaomiFramework/demo/5/Demo5.Console/i18n/Demo5.Console/zh-CN.json deleted file mode 100644 index 80658c3..0000000 --- a/src/MaomiFramework/demo/5/Demo5.Console/i18n/Demo5.Console/zh-CN.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test": "console" -} \ No newline at end of file diff --git a/src/MaomiFramework/demo/5/Demo5.Lib/Extensions.cs b/src/MaomiFramework/demo/5/Demo5.Lib/Extensions.cs deleted file mode 100644 index 9bf9d48..0000000 --- a/src/MaomiFramework/demo/5/Demo5.Lib/Extensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Maomi.I18n; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Demo5.Lib -{ - public class Test { } - public static class Extensions - { - public static void AddLib(this IServiceCollection services) - { - services.AddI18nResource(options => - { - options.AddJson("i18n"); - }); - } - } -} diff --git a/src/MaomiFramework/demo/5/Demo5.Lib/i18n/Demo5.Lib/en-US.json b/src/MaomiFramework/demo/5/Demo5.Lib/i18n/Demo5.Lib/en-US.json deleted file mode 100644 index dd31c08..0000000 --- a/src/MaomiFramework/demo/5/Demo5.Lib/i18n/Demo5.Lib/en-US.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test": "lib" -} \ No newline at end of file diff --git a/src/MaomiFramework/demo/5/Demo5.Lib/i18n/Demo5.Lib/zh-CN.json b/src/MaomiFramework/demo/5/Demo5.Lib/i18n/Demo5.Lib/zh-CN.json deleted file mode 100644 index dd31c08..0000000 --- a/src/MaomiFramework/demo/5/Demo5.Lib/i18n/Demo5.Lib/zh-CN.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test": "lib" -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/HostService.cs b/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/HostService.cs deleted file mode 100644 index 19cef56..0000000 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/HostService.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Maomi.Core.Tests.Module1 -{ - public class HostService : IHostService - { - public void Build() - { - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/IHostService.cs b/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/IHostService.cs deleted file mode 100644 index 5402b78..0000000 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/IHostService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Maomi.Core.Tests.Module1 -{ - public interface IHostService - { - void Build(); - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/Modules.cs b/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/Modules.cs deleted file mode 100644 index 2cb222d..0000000 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/Modules.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Maomi.Module; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Core.Tests.Module1 -{ - public class AModule : IModule - { - private readonly IConfiguration _configuration; - private readonly RecordInfo _recordInfo; - private readonly IHostService _hostService; - - public AModule(IConfiguration configuration, RecordInfo recordInfo, IHostService hostService) - { - _configuration = configuration; - _recordInfo = recordInfo; - _hostService = hostService; - } - public void ConfigureServices(ServiceContext context) - { - _recordInfo.Add(typeof(AModule).Name); - } - } - - [InjectModule] - public class BModule : IModule - { - private readonly IConfiguration _configuration; - private readonly RecordInfo _recordInfo; - public BModule(IConfiguration configuration, RecordInfo recordInfo) - { - _configuration = configuration; - _recordInfo = recordInfo; - } - - public void ConfigureServices(ServiceContext context) - { - _recordInfo.Add(typeof(BModule).Name); - } - } - - [InjectModule] - public class CModule : IModule - { - private readonly IConfiguration _configuration; - private readonly RecordInfo _recordInfo; - public CModule(IConfiguration configuration, RecordInfo recordInfo) - { - _configuration = configuration; - _recordInfo = recordInfo; - } - - public void ConfigureServices(ServiceContext context) - { - _recordInfo.Add(typeof(CModule).Name); - } - } - - [InjectModule] - [InjectModule] - public class DModule : IModule - { - private readonly IConfiguration _configuration; - private readonly RecordInfo _recordInfo; - public DModule(IConfiguration configuration, RecordInfo recordInfo) - { - _configuration = configuration; - _recordInfo = recordInfo; - } - public void ConfigureServices(ServiceContext context) - { - _recordInfo.Add(typeof(DModule).Name); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/RecordInfo.cs b/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/RecordInfo.cs deleted file mode 100644 index 2dd49b0..0000000 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/RecordInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Maomi.Module; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Core.Tests.Module1 -{ - - - public class RecordInfo - { - private readonly List _list = new List(); - public IReadOnlyList List => _list; - public void Add(string name) - { - _list.Add(name); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/Services.cs b/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/Services.cs deleted file mode 100644 index ced59ba..0000000 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module1/Services.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Maomi.Module; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Core.Tests.Module1 -{ - public interface IA { } - public interface IB { } - public interface IC { } - - - public class ParentService { } - [InjectOn] - public class MyService : ParentService, IA, IB, IC { } -} diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module3/Modules.cs b/src/MaomiFramework/framework/Maomi.Core.Tests/Module3/Modules.cs deleted file mode 100644 index 84f41f9..0000000 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module3/Modules.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Maomi.Module; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Core.Tests.Module3 -{ - public class ServiceModule : IModule - { - public void ConfigureServices(ServiceContext context) - { - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/ModuleTest.cs b/src/MaomiFramework/framework/Maomi.Core.Tests/ModuleTest.cs deleted file mode 100644 index 3b38615..0000000 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/ModuleTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Maomi.Core.Tests.Module1; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Maomi.Core.Tests -{ - // ѭģ - // Զע - public class ModuleTest - { - // ӵģע - [Fact] - public void Check_Module_Inject() - { - var ioc = new ServiceCollection(); - var congiguration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary()).Build(); - ioc.AddScoped(s => congiguration); - ioc.AddSingleton(); - RecordInfo recordInfo = new RecordInfo(); - ioc.AddSingleton(recordInfo); - - ioc.AddModule(); - var list = recordInfo.List; - Assert.Equal(typeof(AModule).Name, list[0]); - Assert.Equal(typeof(BModule).Name, list[1]); - Assert.Equal(typeof(CModule).Name, list[2]); - Assert.Equal(typeof(DModule).Name, list[3]); - - var services = ioc.BuildServiceProvider(); - var s1 = services.GetService(); - Assert.Null(s1); - var sa = services.GetRequiredService(); - var sb = services.GetRequiredService(); - var sc = services.GetRequiredService(); - Assert.NotNull(sa); - Assert.NotNull(sb); - Assert.NotNull(sc); - } - - // ѭģ - [Fact] - public void Check_Loop_Dependency_Module() - { - var ioc = new ServiceCollection(); - try - { - ioc.AddModule(); - Assert.True(false); - } - catch - { - Assert.True(true); - } - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Core/Maomi.Core.csproj b/src/MaomiFramework/framework/Maomi.Core/Maomi.Core.csproj deleted file mode 100644 index f00b20e..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Maomi.Core.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - Library - net8.0 - enable - enable - Maomi - true - 2.0.1 - Codestin Search App - Maomi 框架是一个简洁的 .NET 开发框架,具有模块化、自动服务注册等功能。 - True - true - MIT - packageIcon.png - https://maomi.whuanle.cn - https://github.com/whuanle/maomi - - - - - - - - - - - - diff --git a/src/MaomiFramework/framework/Maomi.Core/Module/IModule.cs b/src/MaomiFramework/framework/Maomi.Core/Module/IModule.cs deleted file mode 100644 index af98494..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Module/IModule.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Maomi.Module -{ - /// - /// 模块接口 - /// - public interface IModule - { - /// - /// 模块中的依赖注入 - /// - /// 模块服务上下文 - void ConfigureServices(ServiceContext context); - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core/Module/InjectModuleAttribute.cs b/src/MaomiFramework/framework/Maomi.Core/Module/InjectModuleAttribute.cs deleted file mode 100644 index cd1272e..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Module/InjectModuleAttribute.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Module -{ - /// - /// 模块注入 - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class InjectModuleAttribute : Attribute - { - /// - /// 依赖的模块 - /// - public Type ModuleType { get; private init; } - - /// - /// 注入需要使用的模块 - /// - /// - public InjectModuleAttribute(Type type) - { - ModuleType = type; - } - } - - /// - /// 模块注入 - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public sealed class InjectModuleAttribute : InjectModuleAttribute - where TModule : IModule - { - - public InjectModuleAttribute() : base(typeof(TModule)) - { - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core/Module/InjectOnAttribute.cs b/src/MaomiFramework/framework/Maomi.Core/Module/InjectOnAttribute.cs deleted file mode 100644 index 221e0d8..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Module/InjectOnAttribute.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Maomi.Module -{ - /// - /// 依赖注入标记 - /// - /// 注意,程序启动时先注册模块类以及实例化,请勿在模块类中使用自动依赖注入的服务类 - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class InjectOnAttribute : Attribute - { - /// - /// 要注入的服务 - /// - public Type[]? ServicesType { get; set; } - - /// - /// 生命周期 - /// - public ServiceLifetime Lifetime { get; set; } - - /// - /// 注入模式 - /// - public InjectScheme Scheme { get; set; } - - /// - /// 是否注入自己 - /// - public bool Own { get; set; } = false; - - /// - /// - /// - /// - /// - public InjectOnAttribute(ServiceLifetime lifetime = ServiceLifetime.Transient, InjectScheme scheme = InjectScheme.OnlyInterfaces) - { - Lifetime = lifetime; - Scheme = scheme; - } - } - - /// - /// 依赖注入标记 - /// - /// 注意,程序启动时先注册模块类以及实例化,请勿在模块类中使用自动依赖注入的服务类 - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class InjectOnTransientAttribute : InjectOnAttribute - { - /// - /// - /// - /// - public InjectOnTransientAttribute(InjectScheme scheme = InjectScheme.OnlyInterfaces) : base(ServiceLifetime.Transient, scheme) - { - } - } - - /// - /// 依赖注入标记 - /// - /// 注意,程序启动时先注册模块类以及实例化,请勿在模块类中使用自动依赖注入的服务类 - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class InjectOnScopedAttribute : InjectOnAttribute - { - /// - /// - /// - /// - public InjectOnScopedAttribute(InjectScheme scheme = InjectScheme.OnlyInterfaces) : base(ServiceLifetime.Scoped, scheme) - { - } - } - - /// - /// 依赖注入标记 - /// - /// 注意,程序启动时先注册模块类以及实例化,请勿在模块类中使用自动依赖注入的服务类 - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class InjectOnTSingletonAttribute : InjectOnAttribute - { - /// - /// - /// - /// - public InjectOnTSingletonAttribute(InjectScheme scheme = InjectScheme.OnlyInterfaces) : base(ServiceLifetime.Singleton, scheme) - { - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core/Module/InjectScheme.cs b/src/MaomiFramework/framework/Maomi.Core/Module/InjectScheme.cs deleted file mode 100644 index 3db2d02..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Module/InjectScheme.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Maomi.Module -{ - /// - /// 自动依赖注入模式 - /// - public enum InjectScheme - { - /// - /// 注入的父类、接口 - /// - Any, - - /// - /// 手动选择要注入的服务 - /// - Some, - - /// - /// 只注入父类 - /// - OnlyBaseClass, - - /// - /// 只注入继承的接口 - /// - OnlyInterfaces, - - /// - /// 此服务不会被注入到容器中 - /// - None - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core/Module/ModuleExtensions.cs b/src/MaomiFramework/framework/Maomi.Core/Module/ModuleExtensions.cs deleted file mode 100644 index 5bc176f..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Module/ModuleExtensions.cs +++ /dev/null @@ -1,239 +0,0 @@ -using Maomi.Module; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using System.Reflection; - -namespace Maomi -{ - /// - /// 模块处理 - /// - public static class ModuleExtensions - { - /// - /// 注册模块化服务 - /// - /// 入口模块 - /// - public static void AddModule(this IServiceCollection services) - where TModule : IModule - { - AddModule(services, typeof(TModule)); - } - - - /// - /// 注册模块化服务 - /// - /// - /// 入口模块 - public static void AddModule(this IServiceCollection services, Type startupModule) - { - if (startupModule?.GetInterface(nameof(IModule)) == null) - { - throw new TypeLoadException($"{startupModule?.Name} 不是有效的模块类"); - } - - IServiceProvider scope = BuildModule(services, startupModule); - } - - - #region 自动依赖注入 - - /// - /// 自动依赖注入 - /// - /// - /// - /// 已被注入的服务 - private static void InitInjectService(IServiceCollection services, Assembly assembly, HashSet injectTypes) - { - // 只扫描可实例化的类,不扫描静态类、接口、抽象类、嵌套类、非公开类等 - foreach (var item in assembly.GetTypes().Where(x => x.IsClass && !x.IsAbstract && !x.IsNestedPublic)) - { - var inject = item.GetCustomAttributes().OfType().FirstOrDefault(); - if (inject == null) continue; - - if (injectTypes.Contains(item)) continue; - injectTypes.Add(item); - - // 如果需要注入自身 - if (inject.Own) - { - switch (inject.Lifetime) - { - case ServiceLifetime.Transient: services.AddTransient(item); break; - case ServiceLifetime.Scoped: services.AddScoped(item); break; - case ServiceLifetime.Singleton: services.AddSingleton(item); break; - } - } - - if (inject.Scheme == InjectScheme.None) continue; - - // 注入所有接口 - if (inject.Scheme == InjectScheme.OnlyInterfaces || inject.Scheme == InjectScheme.Any) - { - var interfaces = item.GetInterfaces().Where(x => x != typeof(IDisposable)).ToList(); - if (interfaces.Count() == 0) continue; - switch (inject.Lifetime) - { - case ServiceLifetime.Transient: interfaces.ForEach(x => services.AddTransient(x, item)); break; - case ServiceLifetime.Scoped: interfaces.ForEach(x => services.AddScoped(x, item)); break; - case ServiceLifetime.Singleton: interfaces.ForEach(x => services.AddSingleton(x, item)); break; - } - } - - // 注入父类 - if (inject.Scheme == InjectScheme.OnlyBaseClass || inject.Scheme == InjectScheme.Any) - { - var baseType = item.BaseType; - if (baseType == null) throw new ArgumentException($"{item.Name} 注入模式 {nameof(inject.Scheme)} 未找到父类!"); - switch (inject.Lifetime) - { - case ServiceLifetime.Transient: services.AddTransient(baseType, item); break; - case ServiceLifetime.Scoped: services.AddScoped(baseType, item); break; - case ServiceLifetime.Singleton: services.AddSingleton(baseType, item); break; - } - } - if (inject.Scheme == InjectScheme.Some) - { - var types = inject.ServicesType; - if (types == null) throw new ArgumentException($"{item.Name} 注入模式 {nameof(inject.Scheme)} 未找到服务!"); - switch (inject.Lifetime) - { - case ServiceLifetime.Transient: types.ToList().ForEach(x => services.AddTransient(x, item)); break; - case ServiceLifetime.Scoped: types.ToList().ForEach(x => services.AddScoped(x, item)); break; - case ServiceLifetime.Singleton: types.ToList().ForEach(x => services.AddSingleton(x, item)); break; - } - } - } - } - - #endregion - - - #region 模块注册 - - /// - /// 构建模块依赖树并初始化模块 - /// - /// - /// - /// - /// - private static IServiceProvider BuildModule(IServiceCollection services, Type startupModule) - { - // 生成根模块 - ModuleNode rootTree = new ModuleNode() - { - ModuleType = startupModule, - Childs = new HashSet() - }; - - // 根模块依赖的其他模块 - // IModule => InjectModuleAttribute - var rootDependencies = startupModule.GetCustomAttributes(false) - .Where(x => x.GetType().IsSubclassOf(typeof(InjectModuleAttribute))) - .OfType(); - - // 构建模块依赖树 - BuildTree(services, rootTree, rootDependencies); - - // 构建一个 Ioc 实例,以便初始化模块类 - var scope = services.BuildServiceProvider(); - - // 初始化所有模块类 - var serviceContext = new ServiceContext(services, scope.GetService()!); - - // 记录已经处理的程序集、模块和服务,以免重复处理 - HashSet moduleAssemblies = new HashSet { startupModule.Assembly }; - HashSet moduleTypes = new HashSet(); - HashSet injectTypes = new HashSet(); - - InitModuleTree(scope, serviceContext, moduleAssemblies, moduleTypes, injectTypes, rootTree); - - return scope; - } - - /// - /// 构建模块依赖树 - /// - /// - /// - /// 其依赖的模块 - private static void BuildTree(IServiceCollection services, ModuleNode currentNode, IEnumerable injectModules) - { - services.AddTransient(currentNode.ModuleType); - if (injectModules == null || injectModules.Count() == 0) return; - foreach (var childModule in injectModules) - { - var childTree = new ModuleNode - { - ModuleType = childModule.ModuleType, - ParentModule = currentNode - }; - - // 循环依赖检测 - // 检查当前模块(parentTree)依赖的模块(childTree)是否在之前出现过,如果是,则说明是循环依赖 - var isLoop = currentNode.ContainsTree(childTree); - if (isLoop) - { - throw new OverflowException($"检测到循环依赖引用或重复引用!{currentNode.ModuleType.Name} 依赖的 {childModule.ModuleType.Name} 模块在其父模块中出现过!"); - } - - if (currentNode.Childs == null) - { - currentNode.Childs = new HashSet(); - } - - currentNode.Childs.Add(childTree); - // 子模块依赖的其他模块 - var childDependencies = childModule.ModuleType.GetCustomAttributes(inherit: false) - .Where(x => x.GetType().IsSubclassOf(typeof(InjectModuleAttribute))).OfType().ToHashSet(); - // 子模块也依赖其他模块 - BuildTree(services, childTree, childDependencies); - } - } - - - /// - /// 从模块树中遍历 - /// - /// - /// - /// 已经被注册到容器中的模块类 - /// 模块类所在的程序集' - /// 已被注册到容器的服务 - /// 模块节点 - private static void InitModuleTree(IServiceProvider serviceProvider, - ServiceContext context, - HashSet moduleAssemblies, - HashSet moduleTypes, - HashSet injectTypes, - ModuleNode moduleNode) - { - if (moduleNode.Childs != null) - { - foreach (var item in moduleNode.Childs) - { - InitModuleTree(serviceProvider, context, moduleAssemblies, moduleTypes, injectTypes, item); - } - } - - // 如果模块没有处理过 - if (!moduleTypes.Contains(moduleNode.ModuleType)) - { - moduleTypes.Add(moduleNode.ModuleType); - - // 实例化此模块 - // 扫描此模块(程序集)中需要依赖注入的服务 - var module = (IModule)serviceProvider.GetRequiredService(moduleNode.ModuleType); - module.ConfigureServices(context); - InitInjectService(context.Services, moduleNode.ModuleType.Assembly, injectTypes); - moduleAssemblies.Add(moduleNode.ModuleType.Assembly); - } - } - - #endregion - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core/Module/ModuleNode.cs b/src/MaomiFramework/framework/Maomi.Core/Module/ModuleNode.cs deleted file mode 100644 index b2ca196..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Module/ModuleNode.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Maomi.Module -{ - /// - /// 模块节点 - /// - internal class ModuleNode - { - /// - /// 当前模块类型 - /// - public Type ModuleType { get; set; } = null!; - - /// - /// 链表,指向父模块节点,用于循环引用检测 - /// - public ModuleNode? ParentModule { get; set; } - - /// - /// 依赖的其它模块 - /// - public HashSet? Childs { get; set; } - - /// - /// 一直向父节点搜索,如果存在此模块,说明是循环引用 - /// - /// - /// - public bool ContainsTree(ModuleNode childModule) - { - if (childModule.ModuleType == ModuleType) return true; - if (this.ParentModule == null) return false; - // 如果当前模块找不到记录,则向上查找 - return this.ParentModule.ContainsTree(childModule); - } - - public override int GetHashCode() - { - return ModuleType.GetHashCode(); - } - - public override bool Equals(object? obj) - { - if (obj == null) return false; - if (obj is ModuleNode module) - { - return GetHashCode() == module.GetHashCode(); - } - return false; - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core/Module/ServiceContext.cs b/src/MaomiFramework/framework/Maomi.Core/Module/ServiceContext.cs deleted file mode 100644 index a6de2e0..0000000 --- a/src/MaomiFramework/framework/Maomi.Core/Module/ServiceContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -namespace Maomi.Module -{ - /// - /// 模块上下文 - /// - public class ServiceContext - { - private readonly IServiceCollection _serviceCollection; - private readonly IConfiguration _configuration; - - - internal ServiceContext(IServiceCollection serviceCollection, IConfiguration configuration) - { - _serviceCollection = serviceCollection; - _configuration = configuration; - } - - /// - /// 依赖注入服务 - /// - public IServiceCollection Services => _serviceCollection; - - /// - /// 配置 - /// - public IConfiguration Configuration => _configuration; - } -} diff --git a/src/MaomiFramework/framework/Maomi.Core/packageIcon.png b/src/MaomiFramework/framework/Maomi.Core/packageIcon.png deleted file mode 100644 index 5632752..0000000 Binary files a/src/MaomiFramework/framework/Maomi.Core/packageIcon.png and /dev/null differ diff --git a/src/MaomiFramework/framework/Maomi.EventBus.Tests/EventBusTest.cs b/src/MaomiFramework/framework/Maomi.EventBus.Tests/EventBusTest.cs deleted file mode 100644 index aa21396..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus.Tests/EventBusTest.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using System; - -namespace Maomi.EventBus.Tests -{ - public class EventBusTest - { - private readonly IServiceCollection _ioc; - public EventBusTest() - { - var ioc = new ServiceCollection(); - ioc.AddEventBus(); - ioc.AddLogging(builder => builder.AddConsole()); - ioc.AddScoped(); - ioc.AddScoped(s => new EventStats { Names = new() }); - _ioc = ioc; - } - - [Fact] - public async Task PublishEvent() - { - var provider = _ioc.BuildServiceProvider(); - var eventBus = provider.GetRequiredService(); - var setException = provider.GetRequiredService(); - var eventStats = provider.GetRequiredService(); - setException.Node = 1; - - await eventBus.PublishAsync(new MyEvent() - { - Name = "ھ", - Book = "Hive ŵ" - }); - - Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); - Assert.Equal(nameof(UserRegisterEventHandler.CancelInsertDb), eventStats.Names[2]); - - provider = _ioc.BuildServiceProvider(); - eventBus = provider.GetRequiredService(); - setException = provider.GetRequiredService(); - eventStats = provider.GetRequiredService(); - setException.Node = 2; - - await eventBus.PublishAsync(new MyEvent() - { - Name = "ھ", - Book = "Hive ŵ" - }); - - Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); - Assert.Equal(nameof(UserRegisterEventHandler.InitUser), eventStats.Names[2]); - Assert.Equal(nameof(UserRegisterEventHandler.CancelInitUser), eventStats.Names[3]); - Assert.Equal(nameof(UserRegisterEventHandler.CancelInsertDb), eventStats.Names[4]); - - provider = _ioc.BuildServiceProvider(); - eventBus = provider.GetRequiredService(); - setException = provider.GetRequiredService(); - eventStats = provider.GetRequiredService(); - setException.Node = 3; - - await eventBus.PublishAsync(new MyEvent() - { - Name = "ھ", - Book = "Hive ŵ" - }); - - Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); - Assert.Equal(nameof(UserRegisterEventHandler.InitUser), eventStats.Names[2]); - Assert.Equal(nameof(UserRegisterEventHandler.SendEmail), eventStats.Names[3]); - Assert.Equal(nameof(UserRegisterEventHandler.CancelSendEmail), eventStats.Names[4]); - Assert.Equal(nameof(UserRegisterEventHandler.CancelInitUser), eventStats.Names[5]); - Assert.Equal(nameof(UserRegisterEventHandler.CancelInsertDb), eventStats.Names[6]); - - } - - - [Fact] - public async Task CancelPublishEvent() - { - var provider = _ioc.BuildServiceProvider(); - var eventBus = provider.GetRequiredService(); - var setException = provider.GetRequiredService(); - var eventStats = provider.GetRequiredService(); - - await eventBus.PublishAsync(new CancelMyEvent() - { - Name = "ھ", - Book = "Hive ŵ" - }); - - // ȡ֮ǰִк 4 - Assert.Equal(4, eventStats.Names.Count); - Assert.Equal(nameof(UserRegisterEventHandler.InsertDb), eventStats.Names[1]); - Assert.Equal(nameof(UserRegisterEventHandler.SendEmail), eventStats.Names[3]); - - provider = _ioc.BuildServiceProvider(); - eventBus = provider.GetRequiredService(); - setException = provider.GetRequiredService(); - eventStats = provider.GetRequiredService(); - - CancellationTokenSource source = new CancellationTokenSource(); - new Thread(() => - { - Thread.Sleep(600); - source.Cancel(); - }).Start(); - await eventBus.PublishAsync(new CancelMyEvent() - { - Name = "ھ", - Book = "Hive ŵ" - }, source.Token); - - // ȡִ֮к < 4 - Assert.NotEqual(4, eventStats.Names.Count); - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.EventBus/Event.cs b/src/MaomiFramework/framework/Maomi.EventBus/Event.cs deleted file mode 100644 index ccedb57..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus/Event.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Maomi.EventBus -{ - /// - /// 事件模型类,作为事件的参数使用 - /// - public abstract record Event : IEvent - { - private Guid _eventId; - private DateTime _creationTime; - - protected Event() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - protected Event(Guid eventId, DateTime creationTime) - { - _eventId = eventId; - _creationTime = creationTime; - } - - /// - /// 事件 id - /// - /// - public Guid GetEventId() => _eventId; - - /// - /// 设置事件 id - /// - /// - public void SetEventId(Guid eventId) => _eventId = eventId; - - /// - /// 事件创建时间 - /// - /// - public DateTime GetCreationTime() => _creationTime; - - /// - /// 设置时间创建时间 - /// - /// - public void SetCreationTime(DateTime creationTime) => _creationTime = creationTime; - } - -} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/EventBus.cs b/src/MaomiFramework/framework/Maomi.EventBus/EventBus.cs deleted file mode 100644 index 3cc44ef..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus/EventBus.cs +++ /dev/null @@ -1,173 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Reflection; - -namespace Maomi.EventBus -{ - public partial class EventBus - { - #region static - - // 拦截器 - private static Type? Middleware; - // 缓存所有事件执行器 - private static readonly Dictionary> EventCache = new(); - // 调用链缓存 - private static readonly Dictionary HandlerDelegateCache = new(); - - // 设置拦截器 - public static void SetMiddleware(Type type) - { - Middleware = type; - } - - // 给一个事件添加执行器 - public static void AddEventHandler( - Type declaringType, // 执行器方法所在的类 - int order, - Type eventType, // 绑定了哪个事件 - MethodInfo method) // 执行器方法 - { - if (!EventCache.TryGetValue(eventType, out var events)) - { - events = new HashSet(); - EventCache[eventType] = events; - } - var info = new EventInfo - { - DeclaringType = declaringType, - EventType = eventType, - MethodInfo = method, - IsCancel = false, - Order = order, - // 封装方法为统一的格式 - TaskInvoke = InvokeBuilder.Build(method, declaringType) - }; - events.Add(info); - // 绑定对应的撤销器 - var cancelInfo = events.FirstOrDefault(x => x.EventType == eventType && x.Order == order && x.IsCancel == true); - if (cancelInfo != null) info.CancelInfo = cancelInfo; - } - - // 添加撤销事件执行器 - public static void AddCancelEventHandler(Type declaringType, int order, Type eventType, MethodInfo method) - { - if (!EventCache.TryGetValue(eventType, out var events)) - { - events = new HashSet(); - EventCache[eventType] = events; - } - var cancelInfo = new EventInfo - { - DeclaringType = declaringType, - EventType = eventType, - MethodInfo = method, - IsCancel = true, - Order = order, - TaskInvoke = InvokeBuilder.Build(method, declaringType) - }; - events.Add(cancelInfo); - // 该撤销器绑定对应的执行器 - var info = events.FirstOrDefault(x => x.EventType == eventType && x.Order == order && x.IsCancel == false); - if (info != null) info.CancelInfo = cancelInfo; - } - - - // 构建事件执行链 - private static ServiceEventHandlerDelegate BuildHandler() where TEvent : IEvent - { - if (HandlerDelegateCache.TryGetValue(typeof(TEvent), out var handler)) return handler; - - ServiceEventHandlerDelegate next = async (provider, @params) => - { - var eventData = @params.OfType().FirstOrDefault(); - var cancel = @params.OfType().FirstOrDefault(); - - var logger = provider.GetRequiredService>(); - logger.LogDebug("开始执行事件: {0},{1}", typeof(TEvent).Name, @params[0]); - - if (!EventCache.TryGetValue(typeof(TEvent), out var eventInfos)) return; - var infos = eventInfos.Where(x => x.IsCancel == false).OrderBy(x => x.Order).ToArray(); - - Exception? exception = null; - // 包装调用链和撤销链 - for (int i = 0; i < infos.Length; i++) - { - var info = infos[i]; - - if (cancel.IsCancellationRequested) - { - logger.LogDebug("事件已被取消执行: {0},位置:{1}", typeof(TEvent).Name, info.MethodInfo.Name); - return; - } - - logger.LogDebug("事件: {0},=> {1}", typeof(TEvent).Name, info.MethodInfo.Name); - - // 构建执行链 - var currentService = provider.GetRequiredService(info.DeclaringType); - try - { - await info.TaskInvoke(currentService, @params); - } - // 执行失败,开始回退 - catch (Exception ex) - { - exception = ex; - - logger.LogError(ex, "执行事件失败: {0},执行器:{1},{2}", typeof(TEvent).Name, info.MethodInfo.Name, @params[0]); - for (int j = i; j >= 0; j--) - { - var backInfo = infos[j]; - if (backInfo.CancelInfo is not null) - { - await backInfo.CancelInfo.TaskInvoke(currentService, @params); - } - } - break; - } - } - - // 如果出现了异常,在执行撤销链完成后,重新抛出异常 - if (exception != null) throw exception; - }; - // 存到缓存 - HandlerDelegateCache[typeof(TEvent)] = next; - return next; - } - - #endregion - - } - - // 事件总线 - public partial class EventBus : IEventBus - { - - private readonly IServiceProvider _provider; - - public EventBus(IServiceProvider serviceProvider) - { - _provider = serviceProvider; - } - - // 发布事件 - public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) where TEvent : IEvent - { - var handler = BuildHandler(); - - if (Middleware != null) - { - var mid = _provider.GetRequiredService>(); - EventHandlerDelegate next = async () => - { - await handler(_provider, @event, cancellationToken); - }; - await mid.HandleAsync(@event, next); - } - else - { - await handler(_provider, @event, cancellationToken); - } - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/EventBusExtensions.cs b/src/MaomiFramework/framework/Maomi.EventBus/EventBusExtensions.cs deleted file mode 100644 index 5453834..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus/EventBusExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace Maomi.EventBus -{ - public static class EventBusExtensions - { - // 添加事件总线扩展 - public static void AddEventBus(this IServiceCollection services, Type? middleware = null) - { - services.AddScoped(); - if (middleware is not null) - { - EventBus.SetMiddleware(middleware); - services.TryAddEnumerable(new ServiceDescriptor(typeof(IEventMiddleware<>), middleware, lifetime: ServiceLifetime.Transient)); - } - - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in assemblies) - { - foreach (var type in assembly.GetTypes()) - { - if (type.CustomAttributes.Any(x => x.AttributeType == typeof(EventAttribute))) - { - GetEventHandler(services, type); - } - } - } - } - - // 扫描类中的执行器 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void GetEventHandler(IServiceCollection services, Type type) - { - services.AddScoped(type); - - var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); - foreach (var method in methods) - { - var attr = method.GetCustomAttribute(); - if (attr == null) return; - - var parameters = method.GetParameters(); - if (parameters.Length == 0) throw new Exception($"{method.Name} 的定义不正确,至少包含一个参数"); - var eventType = parameters[0].ParameterType; - if (!(eventType.IsSubclassOf(typeof(Event)) || eventType.GetInterface(typeof(IEvent).Name) != null)) - throw new Exception($"{method.Name} 的定义不正确,第一个参数必须为事件"); - - if (!attr.IsCancel) EventBus.AddEventHandler(type, attr.Order, eventType, method); - else EventBus.AddCancelEventHandler(type, attr.Order, eventType, method); - } - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/EventInfo.cs b/src/MaomiFramework/framework/Maomi.EventBus/EventInfo.cs deleted file mode 100644 index f3cb12d..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus/EventInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; - -namespace Maomi.EventBus -{ - // 用来记录一个 Handler - internal class EventInfo - { - // 执行器所在的类 - public Type DeclaringType { get; set; } - // 执行序号 - public int Order { get; set; } - // 事件 - public Type EventType { get; set; } - // 执行器方法 - public MethodInfo MethodInfo { get; set; } - // 委托封装的执行器方法 - public TaskInvokeDelegate TaskInvoke { get; set; } - // 撤销时执行 - public bool IsCancel { get; set; } - // 撤销执行器对应的信息 - public EventInfo? CancelInfo { get; set; } - - public override int GetHashCode() - { - return MethodInfo.GetHashCode(); - } - public override bool Equals(object? obj) - { - if (obj is not EventInfo info) return false; - return this.GetHashCode() == info.GetHashCode(); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/IEventBus.cs b/src/MaomiFramework/framework/Maomi.EventBus/IEventBus.cs deleted file mode 100644 index 197d9d2..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus/IEventBus.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.EventBus -{ - // 事件总线服务 - public interface IEventBus - { - Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) - where TEvent : IEvent; - } -} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/IEventMiddleware.cs b/src/MaomiFramework/framework/Maomi.EventBus/IEventMiddleware.cs deleted file mode 100644 index dd82093..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus/IEventMiddleware.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Maomi.EventBus -{ - // 定义事件委托,用于构建执行链 - public delegate Task EventHandlerDelegate(); - - // 带依赖注入的事件委托,用于构建执行链 - internal delegate Task ServiceEventHandlerDelegate(IServiceProvider provider, params object?[] parameters); - - // 事件执行中间件,即执行事件时的拦截器 - public interface IEventMiddleware - where TEvent : IEvent - { - // @event: 事件 - // next: 下一个要执行的函数 - Task HandleAsync(TEvent @event, EventHandlerDelegate next); - } -} diff --git a/src/MaomiFramework/framework/Maomi.EventBus/Maomi.EventBus.csproj b/src/MaomiFramework/framework/Maomi.EventBus/Maomi.EventBus.csproj deleted file mode 100644 index 2c7c414..0000000 --- a/src/MaomiFramework/framework/Maomi.EventBus/Maomi.EventBus.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - Library - net8.0 - enable - enable - Maomi - true - 2.0.0 - Codestin Search App - Maomi.EventBus 框架是一个事件总线框架。 - True - true - MIT - packageIcon.png - https://maomi.whuanle.cn - https://github.com/whuanle/maomi - - - - - - - - - - - diff --git a/src/MaomiFramework/framework/Maomi.EventBus/packageIcon.png b/src/MaomiFramework/framework/Maomi.EventBus/packageIcon.png deleted file mode 100644 index 5632752..0000000 Binary files a/src/MaomiFramework/framework/Maomi.EventBus/packageIcon.png and /dev/null differ diff --git a/src/MaomiFramework/framework/Maomi.I18n.Redis/Extensions.cs b/src/MaomiFramework/framework/Maomi.I18n.Redis/Extensions.cs deleted file mode 100644 index 2f0ce92..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n.Redis/Extensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using FreeRedis; - -namespace Maomi.I18n.Redis -{ - /// - /// I18n Redis 缓存 - /// - public static class Extensions - { - /// - /// 添加 i18n redis 资源 - /// - /// - /// - /// key前缀 - /// 本地缓存有效期 - /// 缓存的 key 数量 - public static I18nResourceFactory AddRedis(this I18nResourceFactory resourceFactory, - RedisClient redis, - string pathPrefix, - TimeSpan expired, - int capacity = 10 - ) - { - var resource = new RedisI18nResource(redis, pathPrefix, expired, capacity); - - resourceFactory.Add(resource); - return resourceFactory; - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n.Redis/Maomi.I18n.Redis.csproj b/src/MaomiFramework/framework/Maomi.I18n.Redis/Maomi.I18n.Redis.csproj deleted file mode 100644 index be25ada..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n.Redis/Maomi.I18n.Redis.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - Library - net8.0 - enable - enable - Maomi - true - 2.0.0 - Codestin Search App - Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 - True - true - MIT - packageIcon.png - https://maomi.whuanle.cn - https://github.com/whuanle/maomi - - - - - - - - - - - - - - - - - diff --git a/src/MaomiFramework/framework/Maomi.I18n.Redis/RedisI18nResource.cs b/src/MaomiFramework/framework/Maomi.I18n.Redis/RedisI18nResource.cs deleted file mode 100644 index 2519086..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n.Redis/RedisI18nResource.cs +++ /dev/null @@ -1,114 +0,0 @@ -using FreeRedis; -using Microsoft.Extensions.Localization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.I18n.Redis -{ - /// - /// i18n redis 资源 - /// - public class RedisI18nResource : I18nResource - { - private readonly RedisClient _redisClient; - private readonly string _pathPrefix; - - internal RedisI18nResource(RedisClient redisClient, string pathPrefix, TimeSpan expired, int capacity = 10) - { - _redisClient = redisClient; - _pathPrefix = pathPrefix; - - // Redis client-side 模式 - redisClient.UseClientSideCaching(new ClientSideCachingOptions - { - Capacity = capacity, - KeyFilter = key => key.StartsWith(pathPrefix), - CheckExpired = (key, dt) => DateTime.Now.Subtract(dt) > expired - }); - - // FreeRedis 的 client-side 模式,使用 Hash 类型时, - // 第一次需要先 HGetAll() ,框架将缓存拉取到本地 - GetAllStrings(default); - } - - /// - /// - /// - public IReadOnlyList SupportedCultures => _redisClient - .Keys(_pathPrefix) - .Select(x => new CultureInfo(x.Remove(0, _pathPrefix.Length + 1))).ToList(); - - /// - /// - /// - public IReadOnlyList SupportedUICultures => _redisClient - .Keys(_pathPrefix) - .Select(x => new CultureInfo(x.Remove(0, _pathPrefix.Length + 1))).ToList(); - - /// - /// - /// - public LocalizedString Get(string culture, string name) - { - var key = $"{_pathPrefix}:{culture}"; - var value = _redisClient.HGet(key, name); - if (string.IsNullOrEmpty(value)) return new LocalizedString(name, name, resourceNotFound: true); - return new LocalizedString(name, value); - } - - /// - /// - /// - public LocalizedString Get(string culture, string name, params object[] arguments) - { - var key = $"{_pathPrefix}:{culture}"; - var value = _redisClient.HGet(key, name); - if (string.IsNullOrEmpty(value)) return new LocalizedString(name, name, resourceNotFound: true); - var v = string.Format(value, arguments); - return new LocalizedString(name, v); - } - - /// - /// - /// - public LocalizedString Get(string culture, string name) - { - var key = $"{_pathPrefix}:{culture}"; - var value = _redisClient.HGet(key, name); - if (string.IsNullOrEmpty(value)) return new LocalizedString(name, name, resourceNotFound: true); - return new LocalizedString(name, value); - } - - /// - /// - /// - public LocalizedString Get(string culture, string name, params object[] arguments) - { - var key = $"{_pathPrefix}:{culture}"; - var value = _redisClient.HGet(key, name); - if (string.IsNullOrEmpty(value)) return new LocalizedString(name, name, resourceNotFound: true); - var v = string.Format(value, arguments); - return new LocalizedString(name, v); - } - - /// - /// - /// - public IEnumerable GetAllStrings(bool includeParentCultures) - { - var keys = _redisClient.Keys(_pathPrefix); - foreach (var key in keys) - { - var vs = _redisClient.HGetAll(key); - foreach (var item in vs) - { - yield return new LocalizedString(item.Key, item.Value); - } - } - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n.Redis/packageIcon.png b/src/MaomiFramework/framework/Maomi.I18n.Redis/packageIcon.png deleted file mode 100644 index 5632752..0000000 Binary files a/src/MaomiFramework/framework/Maomi.I18n.Redis/packageIcon.png and /dev/null differ diff --git a/src/MaomiFramework/framework/Maomi.I18n.Tests/I18nTest.cs b/src/MaomiFramework/framework/Maomi.I18n.Tests/I18nTest.cs deleted file mode 100644 index 0f67dd1..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n.Tests/I18nTest.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Localization; -using System.Net.Http.Headers; - -namespace Maomi.I18n.Tests -{ - public class I18nTest - { - [Fact] - public async Task I18n_Request() - { - using var host = await new HostBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder - .UseTestServer() - .ConfigureServices(services => - { - services.AddControllers(); - services.AddI18n(defaultLanguage: "zh-CN"); - services.AddI18nResource(option => - { - var basePath = "i18n"; - option.AddJson(basePath); - }); - }) - .Configure(app => - { - app.UseI18n(); - app.UseRouting(); - app.Use(async (HttpContext context, RequestDelegate next) => - { - var localizer = context.RequestServices.GetRequiredService(); - await context.Response.WriteAsync(localizer["ﳵ:Ʒ"]); - return; - }); - }); - }) - .StartAsync(); - - var httpClient = host.GetTestClient(); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - var response = await httpClient.GetStringAsync("/test?culture=en-US&ui-culture=en-US"); - Assert.Equal("Product name", response); - - response = await httpClient.GetStringAsync("/test?culture=zh-CN&ui-culture=zh-CN"); - Assert.Equal("Ʒ", response); - - httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=en-US|uic=en-US"); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=zh-CN|uic=zh-CN"); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Ʒ", response); - httpClient.DefaultRequestHeaders.Remove("Cookie"); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh-CN")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Ʒ", response); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("sv")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - } - - [Fact] - public async Task I18n_Assembly_Request() - { - using var host = await new HostBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder - .UseTestServer() - .ConfigureServices(services => - { - services.AddControllers(); - services.AddI18n(defaultLanguage: "zh-CN"); - services.AddI18nResource(option => - { - var basePath = "i18n"; - option.AddJson(basePath); - }); - }) - .Configure(app => - { - app.UseI18n(); - app.UseRouting(); - app.Use(async (HttpContext context, RequestDelegate next) => - { - var localizer = context.RequestServices.GetRequiredService>(); - await context.Response.WriteAsync(localizer["ﳵ:Ʒ"]); - return; - }); - }); - }) - .StartAsync(); - - var httpClient = host.GetTestClient(); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - var response = await httpClient.GetStringAsync("/test?culture=en-US&ui-culture=en-US"); - Assert.Equal("Product name", response); - - response = await httpClient.GetStringAsync("/test?culture=zh-CN&ui-culture=zh-CN"); - Assert.Equal("Ʒ", response); - - httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=en-US|uic=en-US"); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=zh-CN|uic=zh-CN"); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Ʒ", response); - httpClient.DefaultRequestHeaders.Remove("Cookie"); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh-CN")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Ʒ", response); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("sv")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.I18n/CultureInfoScope.cs b/src/MaomiFramework/framework/Maomi.I18n/CultureInfoScope.cs deleted file mode 100644 index c6acbdc..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/CultureInfoScope.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Globalization; -using System.Runtime.InteropServices; - -namespace Maomi.I18n -{ - /// - /// 创建作用域 - /// - public class CultureInfoScope : IDisposable - { - private readonly CultureInfo _defaultCultureInfo; - - /// - public CultureInfoScope(string language) - { - _defaultCultureInfo = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language); - } - - /// - public void Dispose() - { - CultureInfo.CurrentCulture = _defaultCultureInfo; - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/DataAnnotationsExtensions.cs b/src/MaomiFramework/framework/Maomi.I18n/DataAnnotationsExtensions.cs deleted file mode 100644 index c34c381..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/DataAnnotationsExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Mvc.DataAnnotations; -using Microsoft.Extensions.Localization; - -namespace Maomi.I18n -{ - public static partial class DataAnnotationsExtensions - { - /// - /// 为 API 模型验证注入 i18n 服务 - /// - /// - public static IMvcBuilder AddI18nDataAnnotation(this IMvcBuilder builder) - { - builder.Services.AddTransient(); - builder - .AddDataAnnotationsLocalization(options => - { - options.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) => - stringLocalizerFactory.Create(modelType); - }); - return builder; - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nContext.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nContext.cs deleted file mode 100644 index 786954f..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Globalization; - -namespace Maomi.I18n -{ - /// - /// 记录当前请求的 i18n 信息 - /// - public class I18nContext - { - /// - /// 当前用户请求的语言 - /// - public CultureInfo Culture { get; internal set; } = CultureInfo.CurrentCulture; - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nExtensions.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nExtensions.cs deleted file mode 100644 index 1cae699..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.AspNetCore.Localization; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Localization; -using System.Globalization; - -namespace Maomi.I18n -{ - public static class I18nExtensions - { - /// - /// 添加 i18n 资源 - /// - /// - /// - public static void AddI18nResource(this IServiceCollection services, Action resourceFactory) - { - var service = services.BuildServiceProvider().GetRequiredService(); - resourceFactory.Invoke(service); - } - - /// - /// 添加 i18n 支持服务 - /// - /// - /// - public static void AddI18n(this IServiceCollection services, - string defaultLanguage = "zh-CN") - { - InternalI18nResourceFactory resourceFactory = new InternalI18nResourceFactory(); - - // ASP.NET Core 自带的 - services.AddLocalization(); - - // 配置 ASP.NET Core 的本地化服务 - services.Configure(options => - { - options.ApplyCurrentCultureToResponseHeaders = true; - options.DefaultRequestCulture = new RequestCulture(culture: defaultLanguage, uiCulture: defaultLanguage); - options.SupportedCultures = resourceFactory.SupportedCultures; - options.SupportedUICultures = resourceFactory.SupportedUICultures; - - // 默认自带了三个请求语言提供器,会先从这些提供器识别要使用的语言。 - // QueryStringRequestCultureProvider - // CookieRequestCultureProvider - // AcceptLanguageHeaderRequestCultureProvider - // 自定义请求请求语言提供器 - options.RequestCultureProviders.Add(new I18nRequestCultureProvider(defaultLanguage)); - }); - - // i18n 中间件 - services.AddScoped(); - services.AddScoped(s => new I18nMiddleware(new CultureInfo(defaultLanguage))); - - // 注入 i18n 服务 - services.AddSingleton(s => resourceFactory); - services.AddSingleton(); - services.AddScoped(); - services.TryAddEnumerable(new ServiceDescriptor(typeof(IStringLocalizer<>), typeof(I18nStringLocalizer<>), ServiceLifetime.Scoped)); - } - - /// - /// i18n 中间件 - /// - /// - public static void UseI18n(this IApplicationBuilder app) - { - app.UseRequestLocalization(); - app.UseMiddleware(); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nMiddleware.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nMiddleware.cs deleted file mode 100644 index 05b461c..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nMiddleware.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Localization; -using System.Globalization; - -namespace Maomi.I18n -{ - /// - /// i18n 中间件,从请求中提取用户指定的语言 - /// - public class I18nMiddleware : IMiddleware - { - // 默认多语言配置 - private readonly CultureInfo _defaultCulture; - - /// - public I18nMiddleware(CultureInfo defaultCulture) - { - _defaultCulture = defaultCulture; - } - - /// - public async Task InvokeAsync(HttpContext context, RequestDelegate next) - { - CultureInfo culture; - var requestCultureFeature = context.Features.Get(); - var requestCulture = requestCultureFeature?.RequestCulture; - if (requestCulture != null) - culture = requestCulture.Culture; - else culture = _defaultCulture; - - var option = context.RequestServices.GetRequiredService(); - option.Culture = culture; - await next(context); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nRequestCultureProvider.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nRequestCultureProvider.cs deleted file mode 100644 index 434945b..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nRequestCultureProvider.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Localization; - -namespace Maomi.I18n -{ - /// - /// 自定义如何从请求中解析请求语言 - /// - public class I18nRequestCultureProvider : RequestCultureProvider - { - private readonly string _defaultLanguage; - public I18nRequestCultureProvider(string defaultLanguage) - { - _defaultLanguage = defaultLanguage; - } - - private const string RouteValueKey = "c"; - private const string UIRouteValueKey = "uic"; - public override Task DetermineProviderCultureResult(HttpContext httpContext) - { - var request = httpContext.Request; - if (!request.RouteValues.Any()) - { - return NullProviderCultureResult; - } - - string? queryCulture = null; - string? queryUICulture = null; - - // 从路由中解析 - if (!string.IsNullOrWhiteSpace(RouteValueKey)) - { - queryCulture = request.RouteValues[RouteValueKey]?.ToString(); - } - - if (!string.IsNullOrWhiteSpace(UIRouteValueKey)) - { - queryUICulture = request.RouteValues[UIRouteValueKey]?.ToString() ?? queryCulture; - } - - if (queryCulture == null && queryUICulture == null) - { - return NullProviderCultureResult; - } - - var providerResultCulture = new ProviderCultureResult(queryCulture, queryUICulture); - - return Task.FromResult(providerResultCulture); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nResource.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nResource.cs deleted file mode 100644 index f4f277d..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nResource.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Globalization; - -namespace Maomi.I18n -{ - /// - /// i18n 资源管理 - /// - public interface I18nResource - { - /// - /// 该资源提供的语言 - /// - IReadOnlyList SupportedCultures { get; } - - /// - /// 该资源提供的 UI 语言 - /// - IReadOnlyList SupportedUICultures { get; } - - /// - /// 获取值 - /// - /// - /// - /// - LocalizedString Get(string culture, string name); - - /// - /// 获取值,支持字符串插值 - /// - /// - /// - /// - /// - LocalizedString Get(string culture, string name, params object[] arguments); - - /// - /// 获取值 - /// - /// - /// - /// - /// - LocalizedString Get(string culture, string name); - - /// - /// 支持字符串插值 - /// - /// - /// - /// - /// - /// - LocalizedString Get(string culture, string name, params object[] arguments); - - /// - /// 获取全部字符串 - /// - /// - /// - IEnumerable GetAllStrings(bool includeParentCultures); - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nResourceFactory.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nResourceFactory.cs deleted file mode 100644 index 5036073..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nResourceFactory.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Globalization; - -namespace Maomi.I18n -{ - /// - /// I18n 资源工厂 - /// - public interface I18nResourceFactory - { - /// - /// 支持的语言 - /// - IList SupportedCultures { get; } - - /// - /// UI 支持的语言 - /// - IList SupportedUICultures { get; } - - /// - /// 所有资源提供器 - /// - IReadOnlyList Resources { get; } - - /// - /// 添加资源提供器 - /// - /// - /// - I18nResourceFactory Add(I18nResource resource); - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizer.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizer.cs deleted file mode 100644 index 81b63bb..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizer.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Microsoft.Extensions.Localization; - -namespace Maomi.I18n -{ - /// - /// i18n 字符串本地化,从 I18nResource 获取字符串 - /// - public class I18nStringLocalizer : IStringLocalizer - { - private readonly I18nContext _context; - private readonly IReadOnlyList _resources; - public I18nStringLocalizer(I18nContext context, I18nResourceFactory resourceFactory) - { - _context = context; - _resources = resourceFactory.Resources; - } - - public LocalizedString this[string name] => Find(name); - - public LocalizedString this[string name, params object[] arguments] => Find(name,arguments); - - public IEnumerable GetAllStrings(bool includeParentCultures) - { - foreach (var resource in _resources) - { - foreach (var item in resource.GetAllStrings(includeParentCultures)) - { - yield return item; - } - } - } - - private LocalizedString Find(string name) - { - foreach (var resource in _resources) - { - var result = resource.Get(_context.Culture.Name, name); - if (result == null || result.ResourceNotFound) continue; - return result; - } - // 所有的资源都查找不到时,使用默认值 - return new LocalizedString(name, name); - } - - private LocalizedString Find(string name, params object[] arguments) - { - foreach (var resource in _resources) - { - var result = resource.Get(_context.Culture.Name, name, arguments); - if (result == null || result.ResourceNotFound) continue; - return result; - } - // 所有的资源都查找不到时,使用默认值 - return new LocalizedString(name, name); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizerFactory.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizerFactory.cs deleted file mode 100644 index 2277b22..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizerFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Globalization; - -namespace Maomi.I18n -{ - public class I18nStringLocalizerFactory : IStringLocalizerFactory - { - private readonly I18nResourceFactory _i18NResourceFactory; - public I18nStringLocalizerFactory(I18nResourceFactory i18NResourceFactory) - { - _i18NResourceFactory = i18NResourceFactory; - } - - /// - /// 根据泛型类型创建 IStringLocalizer - /// - /// - /// - public IStringLocalizer Create(Type resourceSource) - { - return Activator.CreateInstance(typeof(I18nStringLocalizer<>).MakeGenericType(resourceSource), - new object[] - { - new I18nContext{ Culture = CultureInfo.CurrentCulture }, - _i18NResourceFactory - }) as IStringLocalizer; - } - - public IStringLocalizer Create(string baseName, string location) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizer`.cs b/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizer`.cs deleted file mode 100644 index c2aca9a..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/I18nStringLocalizer`.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.Extensions.Localization; - -namespace Maomi.I18n -{ - /// - /// i18n 字符串本地化,从 I18nResource 获取字符串 - /// - /// - public class I18nStringLocalizer : IStringLocalizer - { - private readonly I18nContext _context; - private readonly IReadOnlyList _resources; - - public I18nStringLocalizer(I18nContext context, I18nResourceFactory resourceFactory) - { - _context = context; - _resources = resourceFactory.Resources; - } - - public LocalizedString this[string name] => Find(name); - - public LocalizedString this[string name, params object[] arguments] => Find(name, arguments); - - public IEnumerable GetAllStrings(bool includeParentCultures) - { - foreach (var resource in _resources) - { - foreach (var item in resource.GetAllStrings(includeParentCultures)) - { - yield return item; - } - } - } - - private LocalizedString Find(string name) - { - foreach (var resource in _resources) - { - var result = resource.Get(_context.Culture.Name, name); - if (result == null || result.ResourceNotFound) continue; - return result; - } - // 所有的资源都查找不到时,使用默认值 - return new LocalizedString(name, name); - } - - private LocalizedString Find(string name, params object[] arguments) - { - foreach (var resource in _resources) - { - var result = resource.Get(_context.Culture.Name, name, arguments); - if (result == null || result.ResourceNotFound) continue; - return result; - } - // 所有的资源都查找不到时,使用默认值 - return new LocalizedString(name, name); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/InternalI18nResourceFactory.cs b/src/MaomiFramework/framework/Maomi.I18n/InternalI18nResourceFactory.cs deleted file mode 100644 index 9f9ceff..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/InternalI18nResourceFactory.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Collections.Concurrent; -using System.ComponentModel; -using System.Globalization; - -namespace Maomi.I18n -{ - internal class InternalI18nResourceFactory : I18nResourceFactory - { - private readonly List _resources; - public IReadOnlyList Resources => _resources; - - private readonly List _supportedCultures = new(); - public IList SupportedCultures => _supportedCultures; - - private readonly List _supportedUICultures = new(); - public IList SupportedUICultures => _supportedUICultures; - - internal InternalI18nResourceFactory() - { - _resources = new List(); - } - - public I18nResourceFactory Add(I18nResource resource) - { - _resources.Add(resource); - foreach (var item in resource.SupportedCultures) - { - if (_supportedCultures.Contains(item)) continue; - _supportedCultures.Add(item); - } - foreach (var item in resource.SupportedUICultures) - { - if (_supportedUICultures.Contains(item)) continue; - _supportedUICultures.Add(item); - } - return this; - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/JsonResource.cs b/src/MaomiFramework/framework/Maomi.I18n/JsonResource.cs deleted file mode 100644 index f5e6af1..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/JsonResource.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Globalization; - -namespace Maomi.I18n -{ - /// - public class JsonResource : I18nResource - { - /// - protected readonly string _defaultLanguage; - - /// - public IReadOnlyList SupportedCultures => new List() { new CultureInfo(_defaultLanguage) }; - - /// - public IReadOnlyList SupportedUICultures => new List() { new CultureInfo(_defaultLanguage) }; - - - private readonly IReadOnlyDictionary _kvs; - - /// - public JsonResource(string language, IReadOnlyDictionary kvs) - { - _defaultLanguage = language; - _kvs = kvs.ToDictionary(x => x.Key, x => new LocalizedString(x.Key, x.Value.ToString())); - } - - /// - public IEnumerable GetAllStrings(bool includeParentCultures) => _kvs.Values; - - /// - public LocalizedString Get(string culture, string name) - { - if (culture != _defaultLanguage) return new LocalizedString(name, name, resourceNotFound: true); - - var value = _kvs.GetValueOrDefault(name); - if (value == null) return new LocalizedString(name, name, resourceNotFound: true); - return value; - } - - /// - public LocalizedString Get(string culture, string name, params object[] arguments) - { - if (culture != _defaultLanguage) return new LocalizedString(name, name, resourceNotFound: true); - - var value = _kvs.GetValueOrDefault(name); - if (value == null) return new LocalizedString(name, name, resourceNotFound: true); - - return new LocalizedString(name, string.Format(value, arguments)); - } - - /// - public LocalizedString Get(string culture, string name) - { - // 不是同一个程序集的资源,不处理 - if (typeof(TResource).Assembly != typeof(T).Assembly) return new LocalizedString(name, name, resourceNotFound: true); - return Get(culture, name); - } - - /// - public LocalizedString Get(string culture, string name, params object[] arguments) - { - if (typeof(TResource).Assembly != typeof(T).Assembly) return new LocalizedString(name, name, resourceNotFound: true); - return Get(culture, name, arguments); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/JsonResourceExtensions.cs b/src/MaomiFramework/framework/Maomi.I18n/JsonResourceExtensions.cs deleted file mode 100644 index 69ba9f6..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/JsonResourceExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Buffers; -using System.Text.Json; -using System.Text; - -namespace Maomi.I18n -{ - public static class JsonResourceExtensions - { - /// - /// 添加 json 文件资源 - /// - /// - /// - /// - /// - public static I18nResourceFactory AddJson(this I18nResourceFactory resourceFactory, - string basePath) - where T : class - { - var dirName = typeof(T).Assembly.GetName().Name; - - // 非递归法遍历所有目录,读取 json 文件,生成语言支持 - - var rootDir = new DirectoryInfo(Path.Combine(Directory.GetParent(typeof(T).Assembly.Location).FullName, basePath)); - var lanDir = rootDir.GetDirectories().FirstOrDefault(x => x.Name == dirName); - - ArgumentNullException.ThrowIfNull(lanDir); - - var files = lanDir.GetFiles().Where(x => x.Name.EndsWith(".json")); - foreach (var file in files) - { - var language = Path.GetFileNameWithoutExtension(file.Name); - var text = File.ReadAllText(file.FullName); - var dic = ReadJsonHelper.Read(new ReadOnlySequence(Encoding.UTF8.GetBytes(text)), new JsonReaderOptions { AllowTrailingCommas = true }); - - JsonResource jsonResource = new JsonResource(language, dic); - resourceFactory.Add(jsonResource); - } - - return resourceFactory; - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.I18n/Maomi.I18n.csproj b/src/MaomiFramework/framework/Maomi.I18n/Maomi.I18n.csproj deleted file mode 100644 index 5e5658d..0000000 --- a/src/MaomiFramework/framework/Maomi.I18n/Maomi.I18n.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - Library - net8.0 - enable - enable - Maomi - true - 2.0.0 - Codestin Search App - Maomi.I18n 是一个便于使用的、简洁的 i18n 多语言框架。 - True - true - MIT - packageIcon.png - https://maomi.whuanle.cn - https://github.com/whuanle/maomi - - - - - - - diff --git a/src/MaomiFramework/framework/Maomi.I18n/packageIcon.png b/src/MaomiFramework/framework/Maomi.I18n/packageIcon.png deleted file mode 100644 index 5632752..0000000 Binary files a/src/MaomiFramework/framework/Maomi.I18n/packageIcon.png and /dev/null differ diff --git a/src/MaomiFramework/framework/Maomi.Swagger/Maomi.Swagger.csproj b/src/MaomiFramework/framework/Maomi.Swagger/Maomi.Swagger.csproj deleted file mode 100644 index 5b1b5eb..0000000 --- a/src/MaomiFramework/framework/Maomi.Swagger/Maomi.Swagger.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - Library - net8.0 - enable - enable - Maomi - true - 2.0.0 - Codestin Search App - Maomi.Web.Core 框架是一个简洁的 Web 开发框架,简化定制 ASP.NET Core 的代码复杂度,提供一些基础功能。 - True - true - MIT - packageIcon.png - https://maomi.whuanle.cn - https://github.com/whuanle/maomi - false - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Swagger/packageIcon.png b/src/MaomiFramework/framework/Maomi.Swagger/packageIcon.png deleted file mode 100644 index 5632752..0000000 Binary files a/src/MaomiFramework/framework/Maomi.Swagger/packageIcon.png and /dev/null differ diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/ActionFilterTest.cs b/src/MaomiFramework/framework/Maomi.Web.Core.Test/ActionFilterTest.cs deleted file mode 100644 index 41aafa6..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core.Test/ActionFilterTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Maomi.I18n; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - - -namespace Maomi.Web.Core.Test -{ - public class ActionFilterTest - { - // 测试在中英文情况下,模型验证失败时返回统一格式 - [Fact] - public async Task Request_I18n() - { - using var host = await new HostBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder - .UseTestServer() - .ConfigureServices(services => - { - services.AddControllers(); - services.AddI18n(defaultLanguage: "zh-CN"); - services.AddI18nResource(option => - { - var basePath = "i18n"; - option.AddJson(basePath); - }); - }) - .Configure(app => - { - app.UseI18n(); - app.UseRouting(); - app.UseEndpoints(configure => - { - configure.MapGet("/test",() => - { - return "test"; - }); - }); - }); - }) - .StartAsync(); - - - - var httpClient = host.GetTestClient(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - var response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - response = await httpClient.GetStringAsync("/test?culture=en-US&ui-culture=en-US"); - Assert.Equal("Product name", response); - - response = await httpClient.GetStringAsync("/test?culture=zh-CN&ui-culture=zh-CN"); - Assert.Equal("商品名称", response); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/ExceptionFilterTest.cs b/src/MaomiFramework/framework/Maomi.Web.Core.Test/ExceptionFilterTest.cs deleted file mode 100644 index eafc85f..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core.Test/ExceptionFilterTest.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Web.Core.Test -{ - internal class ExceptionFilterTest - { - } -} diff --git a/src/MaomiFramework/framework/Maomi.Web.Core.Test/I18nTest.cs b/src/MaomiFramework/framework/Maomi.Web.Core.Test/I18nTest.cs deleted file mode 100644 index 95ae02e..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core.Test/I18nTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Maomi.I18n; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Localization; -using System.Net.Http.Headers; - -namespace Maomi.Web.Core.Test -{ - public class I18nTest - { - [Fact] - public async Task Request_I18n() - { - using var host = await new HostBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder - .UseTestServer() - .ConfigureServices(services => - { - services.AddControllers(); - services.AddI18n(defaultLanguage: "zh-CN"); - services.AddI18nResource(option => - { - var basePath = "i18n"; - option.AddJson(basePath); - }); - }) - .Configure(app => - { - app.UseI18n(); - app.UseRouting(); - app.Use(async (HttpContext context, RequestDelegate next) => - { - var localizer = context.RequestServices.GetRequiredService(); - await context.Response.WriteAsync(localizer["ﳵ:Ʒ"]); - return; - }); - }); - }) - .StartAsync(); - - var httpClient = host.GetTestClient(); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - var response = await httpClient.GetStringAsync("/test?culture=en-US&ui-culture=en-US"); - Assert.Equal("Product name", response); - - response = await httpClient.GetStringAsync("/test?culture=zh-CN&ui-culture=zh-CN"); - Assert.Equal("Ʒ", response); - - httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=en-US|uic=en-US"); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - httpClient.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=zh-CN|uic=zh-CN"); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Ʒ", response); - httpClient.DefaultRequestHeaders.Remove("Cookie"); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh-CN")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("zh", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Ʒ", response); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - httpClient.DefaultRequestHeaders.AcceptLanguage.Clear(); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("sv")); - httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US", 0.9)); - response = await httpClient.GetStringAsync("/test"); - Assert.Equal("Product name", response); - - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Filters/MaomiActionFilter.cs b/src/MaomiFramework/framework/Maomi.Web.Core/Filters/MaomiActionFilter.cs deleted file mode 100644 index 8f07269..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/Filters/MaomiActionFilter.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Maomi.Module; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Localization; - -namespace Maomi.Web.Core.Filters -{ - /// - /// Action 过滤器 - /// - [InjectOn(Scheme = InjectScheme.None, Own = true)] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class MaomiActionFilter : ActionFilterAttribute - { - private readonly IStringLocalizer _localizer; - public MaomiActionFilter(IStringLocalizer stringLocalizer) - { - _localizer = stringLocalizer; - } - - - public override void OnResultExecuting(ResultExecutingContext context) - { - if (!context.ModelState.IsValid) - { - Dictionary> errors = new(); - foreach (var item in context.ModelState) - { - List list = new(); - foreach (var error in item.Value.Errors) - { - list.Add(error.ErrorMessage); - } - errors.Add(item.Key, list); - } - context.Result = new BadRequestObjectResult(Res.Create(400, _localizer["400"], errors)); - } - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Filters/MaomiExceptionFilter.cs b/src/MaomiFramework/framework/Maomi.Web.Core/Filters/MaomiExceptionFilter.cs deleted file mode 100644 index cca4bb4..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/Filters/MaomiExceptionFilter.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Maomi.Module; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Localization; - -namespace Maomi.Web.Core.Filters -{ - /// - /// 统一异常处理 - /// - [InjectOn(Scheme = InjectScheme.None, Own = true)] - public class MaomiExceptionFilter : IAsyncExceptionFilter - { - protected readonly ILogger _logger; - private readonly IStringLocalizer _stringLocalizer; - - /// - /// 统一异常处理 - /// - /// - /// - public MaomiExceptionFilter(ILogger logger, IStringLocalizer stringLocalizer) - { - _logger = logger; - _stringLocalizer = stringLocalizer; - } - - /// - /// 异常处理 - /// - /// - /// - public virtual async Task OnExceptionAsync(ExceptionContext context) - { - // 未经处理的异常 - if (!context.ExceptionHandled) - { - var action = context.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor; - - _logger.LogError(context.Exception, - """ - RequestId: {0} - ControllerName: {1} - ActionName: {2} - """, - context.HttpContext.TraceIdentifier, - action?.ControllerName, - action?.ActionName - ); - var response = new Res() - { - Code = 500, - Msg = _stringLocalizer["500", context.HttpContext.TraceIdentifier], - }; - - context.Result = new ObjectResult(response) - { - StatusCode = 500, - }; - - context.ExceptionHandled = true; - } - - await Task.CompletedTask; - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Maomi.Web.Core.csproj b/src/MaomiFramework/framework/Maomi.Web.Core/Maomi.Web.Core.csproj deleted file mode 100644 index 70f4543..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/Maomi.Web.Core.csproj +++ /dev/null @@ -1,51 +0,0 @@ - - - Library - net8.0 - enable - enable - Maomi - true - 2.0.1.3 - Codestin Search App - Maomi.Web.Core 框架是一个简洁的 Web 开发框架,简化定制 ASP.NET Core 的代码复杂度,提供一些基础功能。 - True - true - MIT - packageIcon.png - https://maomi.whuanle.cn - https://github.com/whuanle/maomi - false - - - - - - - - - - - - - - - - - - - - - true - Always - contentFiles\any\any\i18n\Maomi.Web.Core\en-US.json - - - - true - Always - contentFiles\any\any\i18n\Maomi.Web.Core\zh-CN.json - - - - \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/MaomiWebModule.cs b/src/MaomiFramework/framework/Maomi.Web.Core/MaomiWebModule.cs deleted file mode 100644 index c2278a9..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/MaomiWebModule.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Maomi.I18n; -using Maomi.Module; -using Maomi.Web.Core.Filters; - -namespace Maomi.Web.Core -{ - /// - /// - /// - public class MaomiWebModule : IModule - { - /// - /// - /// - /// - public void ConfigureServices(ServiceContext context) - { - // i18n 服务 - context.Services.AddI18n("zh-CN"); - context.Services.AddI18nResource(options => - { - options.AddJson("i18n"); - }); - - // 添加控制器 - context.Services.AddControllers(options => - { - options.Filters.Add(); - options.Filters.AddService(); - options.Filters.AddService(); - }) - .AddI18nDataAnnotation(); - } - } -} diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Res.cs b/src/MaomiFramework/framework/Maomi.Web.Core/Res.cs deleted file mode 100644 index 05a3e45..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/Res.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Net; - -namespace Maomi.Web.Core -{ - /// - /// 响应模型类 - /// - /// - public class Res - { - /// - /// 当前请求是否有错误 - /// - public virtual bool IsSuccess => Code == 200; - - /// - /// 业务代码 - /// - public virtual int Code { get; set; } - - /// - /// 响应消息 - /// - public virtual string Msg { get; set; } - - /// - /// 返回数据 - /// - public virtual T? Data { get; set; } - } - - /// - /// 响应模型类 - /// - public partial class Res : Res - { - /// - /// 创建 - /// - /// - /// - /// - /// - /// - public static Res Create(int code, string message, T data) - { - return new Res - { - Code = code, - Msg = message, - Data = data - }; - } - - /// - /// 创建 - /// - /// - /// - /// - /// - public static Res Create(int code, string message) - { - return new Res - { - Code = code, - Msg = message - }; - } - - /// - /// 创建 - /// - /// - /// - /// - public static Res Create(int code, string message) - { - return new Res - { - Code = code, - Msg = message - }; - } - - - /// - /// 创建 - /// - /// - /// - /// - /// - /// - public static Res Create(HttpStatusCode code, string message, T data) => Create((int)code, message, data); - } - - /// - /// 分页结果模型类 - /// - /// - public class PageRes - { - /// - /// 当前页 - /// - public virtual int PageNo { get; set; } - - /// - /// 页大小 - /// - public virtual int PageSize { get; set; } - - /// - /// - /// - public virtual T? List { get; set; } - } - - /// - /// 分页结果模型类 - /// - /// - public partial class PageListRes : Res>> - { - } - - /// - /// 分页结果模型类 - /// - /// - public partial class PageArrayRes : Res> - { - } -} diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/MaomiSwaggerOptions.cs b/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/MaomiSwaggerOptions.cs deleted file mode 100644 index 050c088..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/MaomiSwaggerOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Maomi.Web.Core -{ - /// - /// swagger 配置 - /// - public class MaomiSwaggerOptions - { - /// - /// 默认分组名称 - /// - /// - public string DefaultGroupName { get; set; } = "default"; - - /// - /// 默认标题 - /// - /// - public string DefaultGroupTitle { get; set; } = "default"; - } -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/MaomiSwaggerSchemaFilter.cs b/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/MaomiSwaggerSchemaFilter.cs deleted file mode 100644 index fe68151..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/MaomiSwaggerSchemaFilter.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using System.Reflection; -using System.Text.Json.Serialization; - -namespace Maomi.Web.Core -{ - /// - /// Swagger 模型类过滤器 - /// - public class MaomiSwaggerSchemaFilter : ISchemaFilter - { - /// - /// - /// - /// Swagger 中的属性 - /// 模型类上下文 - public void Apply(OpenApiSchema schema, SchemaFilterContext context) - { - // 模型类的类型 - var type = context.Type; - - // 如果 API 参数不是对象 - if (type.IsPrimitive || TypeInfo.GetTypeCode(type) != TypeCode.Object) - { - return; - } - - // 如果 API 参数是对象类型 - - // 获取类型的所有属性 - PropertyInfo[] ps = context.Type.GetProperties(); - - // 获取 swagger 文件显示的所有属性 - // 注意文档属性是已经已经生成的,这里进行后期转换,替换为需要显示的类型 - foreach (var property in schema.Properties) - { - var p = ps.FirstOrDefault(x => x.Name.ToLower() == property.Key.ToLower()); - if (p == null) continue; - var t = property.Value.Type; - var converter = p.GetCustomAttribute(); - if (converter == null || converter.ConverterType == null) continue; - - var targetType = TypeInfo.GetTypeCode(converter.ConverterType); - - // 如果是基元类型或 Decimal、DateTime - if (targetType != TypeCode.Empty && - targetType != TypeCode.DBNull && - targetType != TypeCode.Object) - { - if (GetValueType(targetType, out var valueType)) - { - property.Value.Type = valueType; - } - } - } - - static bool GetValueType(TypeCode targetType, out string? valueType) - { - valueType = null; - switch (targetType) - { - case TypeCode.Boolean: valueType = "boolean"; break; - case TypeCode.Char: valueType = "string"; break; - case TypeCode.SByte: valueType = "integer"; break; - case TypeCode.Byte: valueType = "integer"; break; - case TypeCode.Int16: valueType = "integer"; break; - case TypeCode.UInt16: valueType = "integer"; break; - case TypeCode.Int32: valueType = "integer"; break; - case TypeCode.UInt32: valueType = "integer"; break; - case TypeCode.Int64: valueType = "integer"; break; - case TypeCode.UInt64: valueType = "integer"; break; - case TypeCode.Single: valueType = "number"; break; - case TypeCode.Double: valueType = "number"; break; - case TypeCode.Decimal: valueType = "number"; break; - case TypeCode.DateTime: valueType = "string"; break; - case TypeCode.String: valueType = "string"; break; - // 一般不需要处理对象 - // case TypeCode.Object: valueType = p.PropertyType.Name; break; - default: return false; - } - return true; - } - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/SwaggerExtensions.cs b/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/SwaggerExtensions.cs deleted file mode 100644 index 6213b59..0000000 --- a/src/MaomiFramework/framework/Maomi.Web.Core/Swagger/SwaggerExtensions.cs +++ /dev/null @@ -1,198 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Versioning; -using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.Swagger; -using Swashbuckle.AspNetCore.SwaggerGen; -using Swashbuckle.AspNetCore.SwaggerUI; -using System.Reflection; - -namespace Maomi.Web.Core -{ - /// - /// Swagger 扩展 - /// - public static class SwaggerExtensions - { - private static readonly HashSet ApiAssemblies = new(); - - /// - /// Swagger 配置,用于生成 swwagger.json 文件 - /// - /// swagger 配置 - /// - /// 自定义配置 - /// 设置 API 版本号 - /// 设置界面如何显示 API 版本号 - public static void AddMaomiSwaggerGen(this IServiceCollection services, - Action? setupMaomiSwaggerAction = null, - Action? setupSwaggerAction = null, - Action? setupApiVersionAction = null, - Action? setupApiExplorerAction = null) - { - if (setupMaomiSwaggerAction == null) setupMaomiSwaggerAction = option => { }; - services.Configure(setupMaomiSwaggerAction); - - // 配置 Api 版本信息 - setupApiVersionAction = setupApiVersionAction ?? (setup => - { - // 全局默认 api 版本号 - setup.DefaultApiVersion = new ApiVersion(1, 0); - // 用户请求未指定版本号时,使用默认版本号 - setup.AssumeDefaultVersionWhenUnspecified = true; - // 响应时,在 header 中返回版本号 - setup.ReportApiVersions = true; - // 从哪里读取版本号信息 - setup.ApiVersionReader = - ApiVersionReader.Combine( - new HeaderApiVersionReader("X-Api-Version"), - new QueryStringApiVersionReader("version")); - }); - - services.AddApiVersioning(setupApiVersionAction); - var defaultVersion = services.BuildServiceProvider() - .GetRequiredService>() - .Value.DefaultApiVersion; - - // 在 swagger 中显示版本信息, - // 进一步使用版本号进行隔分 - - setupApiExplorerAction = setupApiExplorerAction ?? (setup => - { - }); - services.AddVersionedApiExplorer(setupApiExplorerAction); - - services.AddSwaggerGen(options => - { - // 模型类过滤器 - options.SchemaFilter(); - - var ioc = services.BuildServiceProvider(); - // 提供对程序中所有 ApiDescriptionGroup 对象的访问, - // ApiDescriptionGroup 记录 Controller 的分组描述信息 - var descriptionProvider = ioc.GetRequiredService(); - var apiVersionDescriptionProvider = ioc.GetRequiredService(); - var apiVersionoptions = ioc.GetRequiredService>(); - var maomiSwaggerOptions = ioc.GetRequiredService>(); - - - // 配置分组信息 - // Items 是根据 ApiExplorerSettings.GroupName 进行分组的 - foreach (var description in descriptionProvider.ApiDescriptionGroups.Items) - { - // 如果 Controller 没有配置分组,则放到默认分组中 - if (description.GroupName == null) - { - options.SwaggerDoc(maomiSwaggerOptions.Value.DefaultGroupName, new OpenApiInfo - { - // 分组默认的 Api 版本号 - Version = apiVersionoptions.Value.DefaultApiVersion.ToString(), - Title = maomiSwaggerOptions.Value.DefaultGroupTitle - }); - - // 保存每个 Action 反射的 MethodInfo - foreach (var item in description.Items) - { - if (item.TryGetMethodInfo(out var methodInfo)) - { - var assembly = methodInfo.DeclaringType?.Assembly; - if (assembly != null) ApiAssemblies.Add(assembly); - } - } - } - else - { - options.SwaggerDoc(description.GroupName, new OpenApiInfo - { - Version = apiVersionoptions.Value.DefaultApiVersion.ToString(), - Title = description.GroupName, - }); - } - } - - // 加载所有控制器对应程序集的文档 - var dir = new DirectoryInfo(AppContext.BaseDirectory); - var files = dir.GetFiles().Where(x => x.Name.EndsWith(".xml")).ToArray(); - foreach (var item in files) - { - // 如果 Controller 程序集的 xml 文件存在,则加载 - if (ApiAssemblies.Any(x => item.Name.Equals(x.GetName().Name + ".xml", StringComparison.CurrentCultureIgnoreCase))) - options.IncludeXmlComments(item.FullName); - } - - options.BuildGroupApis(maomiSwaggerOptions.Value); - - // 最后使用用户自定义配置代码 - if (setupSwaggerAction != null) - { - setupSwaggerAction.Invoke(options); - } - }); - } - - // 配置每个分组中有哪些 Action - private static void BuildGroupApis(this SwaggerGenOptions swaggerGenOptions, MaomiSwaggerOptions maomiSwaggerOptions) - { - // docname == GroupName - swaggerGenOptions.DocInclusionPredicate((string docname, ApiDescription apiDescription) => - { - if (!apiDescription.TryGetMethodInfo(out MethodInfo methodInfo)) return false; - // 属于默认分组 - if (docname == maomiSwaggerOptions.DefaultGroupName && apiDescription.GroupName == null) - { - return true; - } - - return apiDescription.GroupName == docname; - }); - } - - - /// - /// swagger 页面显示配置 - /// - /// - /// - /// - /// - public static IApplicationBuilder UseMaomiSwagger(this IApplicationBuilder app, - Action? setupAction = null, - Action? setupUIAction = null) - { - var ioc = app.ApplicationServices; - var descriptionProvider = ioc.GetRequiredService(); - var maomiSwaggerOptions = ioc.GetRequiredService>(); - - app.UseSwagger(setupAction); - - app.UseSwaggerUI(options => - { - bool haveDefault = false; - - // 配置页面显示和使用哪些位置的 swagger.json 文件 - foreach (var description in descriptionProvider.ApiDescriptionGroups.Items) - { - if (description.GroupName == null) - { - haveDefault = true; - continue; - } - options.SwaggerEndpoint($"{description.GroupName}/swagger.json", description.GroupName); - } - - // 有默认不带分组的 - if (haveDefault) - { - options.SwaggerEndpoint($"{maomiSwaggerOptions.Value.DefaultGroupName}/swagger.json", maomiSwaggerOptions.Value.DefaultGroupName); - } - - // 执行用户自定义配置 - if (setupUIAction != null) setupUIAction.Invoke(options); - }); - - return app; - } - } - -} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/packageIcon.png b/src/MaomiFramework/framework/Maomi.Web.Core/packageIcon.png deleted file mode 100644 index 5632752..0000000 Binary files a/src/MaomiFramework/framework/Maomi.Web.Core/packageIcon.png and /dev/null differ diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/ApiModule.cs b/src/MaomiFramework/templates/MaomiDemo.Api/ApiModule.cs deleted file mode 100644 index 7015955..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/ApiModule.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Maomi.Module; -using Maomi.Web.Core; - -namespace MaomiDemo.Api -{ - [InjectModule] - public class ApiModule : IModule - { - private readonly IConfiguration _configuration; - public ApiModule(IConfiguration configuration) - { - _configuration = configuration; - } - - public void ConfigureServices(ServiceContext context) - { - var configuration = context.Configuration; - context.Services.AddCors(); - } - } -} diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/Controllers/TestController.cs b/src/MaomiFramework/templates/MaomiDemo.Api/Controllers/TestController.cs deleted file mode 100644 index 499ee8f..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/Controllers/TestController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MaomiDemo.Api.Services; -using Microsoft.AspNetCore.Mvc; - -namespace MaomiDemo.Api.Controllers -{ - /// - /// B - /// - [ApiController] - [Route("[controller]")] - [ApiVersion("1.0")] - public class TestController : ControllerBase - { - /// - /// - /// - /// - [HttpGet("test1")] - public string Get1() => "true"; - - /// - /// - /// - /// - [HttpGet("test2")] - public int Get2([FromServices] IMyService myService, int a, int b) - { - return myService.Sum(a, b); - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/MaomiDemo.Api.csproj b/src/MaomiFramework/templates/MaomiDemo.Api/MaomiDemo.Api.csproj deleted file mode 100644 index f58ded7..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/MaomiDemo.Api.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net8.0 - enable - enable - - - - - Always - - - Always - - - - - - - - - - - - - diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/Program.cs b/src/MaomiFramework/templates/MaomiDemo.Api/Program.cs deleted file mode 100644 index b156c62..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/Program.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Maomi; -using Maomi.Web.Core; - -namespace MaomiDemo.Api -{ - public class Program - { - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - builder.Services.AddControllers(); - - // עģ黯񣬲 ApiModule Ϊ - builder.Services.AddModule(); - // swagger - builder.Services.AddMaomiSwaggerGen(); - - var app = builder.Build(); - - if (app.Environment.IsDevelopment()) - { - // swagger м - app.UseMaomiSwagger(); - } - - app.UseAuthorization(); - - - app.MapControllers(); - - app.Run(); - } - } -} \ No newline at end of file diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/Services/IMyService.cs b/src/MaomiFramework/templates/MaomiDemo.Api/Services/IMyService.cs deleted file mode 100644 index 8f3b529..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/Services/IMyService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MaomiDemo.Api.Services -{ - public interface IMyService - { - int Sum(int a, int b); - } -} diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/Services/MyService.cs b/src/MaomiFramework/templates/MaomiDemo.Api/Services/MyService.cs deleted file mode 100644 index 090cefe..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/Services/MyService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Maomi.Module; - -namespace MaomiDemo.Api.Services -{ - [InjectOn(ServiceLifetime.Scoped, Own = true)] - public class MyService : IMyService - { - public int Sum(int a, int b) - { - return a + b; - } - } -} diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/en-US.json b/src/MaomiFramework/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/en-US.json deleted file mode 100644 index 47e61b8..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/en-US.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "200": "Success", - "400": "One or more validation errors occurred.", - "500": "Service error,Request Id:{0}" -} \ No newline at end of file diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/zh-CN.json b/src/MaomiFramework/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/zh-CN.json deleted file mode 100644 index b015437..0000000 --- a/src/MaomiFramework/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/zh-CN.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "200": "操作成功", - "400": "发生了一个或多个验证错误", - "500": "服务出现故障,请求 Id:{0}" -} \ No newline at end of file diff --git a/stylecop.json b/stylecop.json new file mode 100644 index 0000000..01c6f26 --- /dev/null +++ b/stylecop.json @@ -0,0 +1,19 @@ +{ + // ACTION REQUIRED: This file was automatically added to your project, but it + // will not take effect until additional steps are taken to enable it. See the + // following page for additional information: + // + // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md + + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Maomi", + "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information.\nGithub link: https://github.com/whuanle/maomi", + "variables": { + "licenseName": "MIT", + "licenseFile": "LICENSE" + } + } + } +} diff --git a/src/MaomiFramework/templates/.template.config/template.json b/templates/.template.config/template.json similarity index 100% rename from src/MaomiFramework/templates/.template.config/template.json rename to templates/.template.config/template.json diff --git a/templates/MaomiDemo.Api/ApiModule.cs b/templates/MaomiDemo.Api/ApiModule.cs new file mode 100644 index 0000000..d2b163d --- /dev/null +++ b/templates/MaomiDemo.Api/ApiModule.cs @@ -0,0 +1,20 @@ +using Maomi; +using Maomi.Web.Core; + +namespace MaomiDemo.Api; + +[InjectModule] +public class ApiModule : IModule +{ + private readonly IConfiguration _configuration; + public ApiModule(IConfiguration configuration) + { + _configuration = configuration; + } + + public void ConfigureServices(ServiceContext context) + { + var configuration = context.Configuration; + context.Services.AddCors(); + } +} diff --git a/templates/MaomiDemo.Api/Controllers/TestController.cs b/templates/MaomiDemo.Api/Controllers/TestController.cs new file mode 100644 index 0000000..2f4f556 --- /dev/null +++ b/templates/MaomiDemo.Api/Controllers/TestController.cs @@ -0,0 +1,30 @@ +using MaomiDemo.Api.Services; +using Microsoft.AspNetCore.Mvc; + +namespace MaomiDemo.Api.Controllers; + +/// +/// 控制器B +/// +[ApiController] +[Route("[controller]")] +[ApiVersion("1.0")] +public class TestController : ControllerBase +{ + /// + /// 测试 + /// + /// + [HttpGet("test1")] + public string Get1() => "true"; + + /// + /// 测试 + /// + /// + [HttpGet("test2")] + public int Get2([FromServices] IMyService myService, int a, int b) + { + return myService.Sum(a, b); + } +} \ No newline at end of file diff --git a/templates/MaomiDemo.Api/MaomiDemo.Api.csproj b/templates/MaomiDemo.Api/MaomiDemo.Api.csproj new file mode 100644 index 0000000..49bf529 --- /dev/null +++ b/templates/MaomiDemo.Api/MaomiDemo.Api.csproj @@ -0,0 +1,45 @@ + + + + net8.0 + enable + enable + 2.1.0 + package.png + https://maomi.whuanle.cn + https://github.com/whuanle/maomi + README.md + git + true + + + + + + + + + + + + + True + \ + Always + + + + + + + + + + Always + + + Always + + + + diff --git a/templates/MaomiDemo.Api/Program.cs b/templates/MaomiDemo.Api/Program.cs new file mode 100644 index 0000000..9d5cb1e --- /dev/null +++ b/templates/MaomiDemo.Api/Program.cs @@ -0,0 +1,37 @@ +using Maomi; +using Maomi.I18n; +using Maomi.Web.Core; + +namespace MaomiDemo.Api; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddControllers(); + + builder.Services.AddI18nAspNetCore("i18n"); + + // 注册模块化服务,并设置 ApiModule 为入口 + builder.Services.AddModule(); + // swagger 服务 + builder.Services.AddMaomiSwaggerGen(); + + var app = builder.Build(); + + if (app.Environment.IsDevelopment()) + { + // swagger 中间件 + app.UseMaomiSwagger(); + } + + app.UseI18n(); + app.UseAuthorization(); + + + app.MapControllers(); + + app.Run(); + } +} \ No newline at end of file diff --git a/src/MaomiFramework/templates/MaomiDemo.Api/Properties/launchSettings.json b/templates/MaomiDemo.Api/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/templates/MaomiDemo.Api/Properties/launchSettings.json rename to templates/MaomiDemo.Api/Properties/launchSettings.json diff --git a/templates/MaomiDemo.Api/Services/IMyService.cs b/templates/MaomiDemo.Api/Services/IMyService.cs new file mode 100644 index 0000000..24e5fab --- /dev/null +++ b/templates/MaomiDemo.Api/Services/IMyService.cs @@ -0,0 +1,6 @@ +namespace MaomiDemo.Api.Services; + +public interface IMyService +{ + int Sum(int a, int b); +} diff --git a/templates/MaomiDemo.Api/Services/MyService.cs b/templates/MaomiDemo.Api/Services/MyService.cs new file mode 100644 index 0000000..de84ea3 --- /dev/null +++ b/templates/MaomiDemo.Api/Services/MyService.cs @@ -0,0 +1,12 @@ +using Maomi; + +namespace MaomiDemo.Api.Services; + +[InjectOn(ServiceLifetime.Scoped, Own = true)] +public class MyService : IMyService +{ + public int Sum(int a, int b) + { + return a + b; + } +} diff --git a/templates/MaomiDemo.Api/appsettings.Development.json b/templates/MaomiDemo.Api/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/templates/MaomiDemo.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/templates/MaomiDemo.Api/appsettings.json b/templates/MaomiDemo.Api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/templates/MaomiDemo.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/i18n/Maomi.Web.Core/en-US.json b/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/en-US.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core/i18n/Maomi.Web.Core/en-US.json rename to templates/MaomiDemo.Api/i18n/MaomiDemo.Api/en-US.json diff --git a/src/MaomiFramework/framework/Maomi.Web.Core/i18n/Maomi.Web.Core/zh-CN.json b/templates/MaomiDemo.Api/i18n/MaomiDemo.Api/zh-CN.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.Web.Core/i18n/Maomi.Web.Core/zh-CN.json rename to templates/MaomiDemo.Api/i18n/MaomiDemo.Api/zh-CN.json diff --git a/templates/MaomiDemo.Api/package.png b/templates/MaomiDemo.Api/package.png new file mode 100644 index 0000000..5ead24a Binary files /dev/null and b/templates/MaomiDemo.Api/package.png differ diff --git a/src/MaomiFramework/templates/MaomiDemo.sln b/templates/MaomiDemo.sln similarity index 100% rename from src/MaomiFramework/templates/MaomiDemo.sln rename to templates/MaomiDemo.sln diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/DependencyInjectionTest.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/DependencyInjectionTest.cs similarity index 95% rename from src/MaomiFramework/framework/Maomi.Core.Tests/DependencyInjectionTest.cs rename to tests/MaomiCoreTests/Maomi.Core.Tests/DependencyInjectionTest.cs index 2374df6..0f6e97e 100644 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/DependencyInjectionTest.cs +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/DependencyInjectionTest.cs @@ -1,14 +1,9 @@ using Maomi.Core.Tests.Module3; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Core.Tests -{ - public class DependencyInjectionTest + +namespace Maomi.Core.Tests; + +public class DependencyInjectionTest { [Fact] public void Default_Inject() @@ -139,4 +134,3 @@ public void Inject_Own() Assert.Equal(typeof(Service_Own), s5.GetType()); } } -} diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/InjectTests.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/InjectTests.cs new file mode 100644 index 0000000..d9e3d11 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/InjectTests.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi.Core.Tests; + +public class TestModule : IModule +{ + public void ConfigureServices(ServiceContext context) + { + } +} + +[InjectOnTransient] +public class IT +{ +} + +[InjectOnScoped] +public class ISc +{ +} + +[InjectOnSingleton] +public class ISi +{ +} + +[InjectOn] +public class IO +{ +} +public class InjectTests +{ + [Fact] + public void Inject_lifetime() + { + var services = new ServiceCollection(); + services.AddModule(); + var service = services.BuildServiceProvider(); + + } +} diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Maomi.Core.Tests.csproj b/tests/MaomiCoreTests/Maomi.Core.Tests/Maomi.Core.Tests.csproj similarity index 92% rename from src/MaomiFramework/framework/Maomi.Core.Tests/Maomi.Core.Tests.csproj rename to tests/MaomiCoreTests/Maomi.Core.Tests/Maomi.Core.Tests.csproj index a31d8e8..cdcc2d7 100644 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Maomi.Core.Tests.csproj +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Maomi.Core.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/HostService.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/HostService.cs new file mode 100644 index 0000000..795c499 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/HostService.cs @@ -0,0 +1,8 @@ +namespace Maomi.Core.Tests.Module1; + +public class HostService : IHostService +{ + public void Build() + { + } +} diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/IHostService.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/IHostService.cs new file mode 100644 index 0000000..b3cd1f4 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/IHostService.cs @@ -0,0 +1,6 @@ +namespace Maomi.Core.Tests.Module1; + +public interface IHostService +{ + void Build(); +} diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/Modules.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/Modules.cs new file mode 100644 index 0000000..3819b81 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/Modules.cs @@ -0,0 +1,72 @@ +using Microsoft.Extensions.Configuration; + +namespace Maomi.Core.Tests.Module1; + +public class AModule : IModule +{ + private readonly IConfiguration _configuration; + private readonly RecordInfo _recordInfo; + private readonly IHostService _hostService; + + public AModule(IConfiguration configuration, RecordInfo recordInfo, IHostService hostService) + { + _configuration = configuration; + _recordInfo = recordInfo; + _hostService = hostService; + } + public void ConfigureServices(ServiceContext context) + { + _recordInfo.Add(typeof(AModule).Name); + } +} + +[InjectModule] +public class BModule : IModule +{ + private readonly IConfiguration _configuration; + private readonly RecordInfo _recordInfo; + public BModule(IConfiguration configuration, RecordInfo recordInfo) + { + _configuration = configuration; + _recordInfo = recordInfo; + } + + public void ConfigureServices(ServiceContext context) + { + _recordInfo.Add(typeof(BModule).Name); + } +} + +[InjectModule] +public class CModule : IModule +{ + private readonly IConfiguration _configuration; + private readonly RecordInfo _recordInfo; + public CModule(IConfiguration configuration, RecordInfo recordInfo) + { + _configuration = configuration; + _recordInfo = recordInfo; + } + + public void ConfigureServices(ServiceContext context) + { + _recordInfo.Add(typeof(CModule).Name); + } +} + +[InjectModule] +[InjectModule] +public class DModule : IModule +{ + private readonly IConfiguration _configuration; + private readonly RecordInfo _recordInfo; + public DModule(IConfiguration configuration, RecordInfo recordInfo) + { + _configuration = configuration; + _recordInfo = recordInfo; + } + public void ConfigureServices(ServiceContext context) + { + _recordInfo.Add(typeof(DModule).Name); + } +} diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/RecordInfo.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/RecordInfo.cs new file mode 100644 index 0000000..6329fe1 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/RecordInfo.cs @@ -0,0 +1,11 @@ +namespace Maomi.Core.Tests.Module1; + +public class RecordInfo +{ + private readonly List _list = new List(); + public IReadOnlyList List => _list; + public void Add(string name) + { + _list.Add(name); + } +} diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/Services.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/Services.cs new file mode 100644 index 0000000..5b6b2d0 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module1/Services.cs @@ -0,0 +1,10 @@ +namespace Maomi.Core.Tests.Module1; + +public interface IA { } +public interface IB { } +public interface IC { } + + +public class ParentService { } +[InjectOn] +public class MyService : ParentService, IA, IB, IC { } diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module2/LoopModules.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module2/LoopModules.cs similarity index 63% rename from src/MaomiFramework/framework/Maomi.Core.Tests/Module2/LoopModules.cs rename to tests/MaomiCoreTests/Maomi.Core.Tests/Module2/LoopModules.cs index f4cc67c..2e965e6 100644 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module2/LoopModules.cs +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module2/LoopModules.cs @@ -1,14 +1,7 @@ -using Maomi.Module; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace Maomi.Core.Tests.Module2; -namespace Maomi.Core.Tests.Module2 -{ - // C=>B=>A=>C - [InjectModule] +// C=>B=>A=>C +[InjectModule] public class ALoopModule : IModule { public void ConfigureServices(ServiceContext context) @@ -30,4 +23,3 @@ public void ConfigureServices(ServiceContext context) { } } -} diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/Module3/Modules.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module3/Modules.cs new file mode 100644 index 0000000..a45e4a5 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module3/Modules.cs @@ -0,0 +1,8 @@ +namespace Maomi.Core.Tests.Module3; + +public class ServiceModule : IModule + { + public void ConfigureServices(ServiceContext context) + { + } + } diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Module3/Services.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Module3/Services.cs similarity index 76% rename from src/MaomiFramework/framework/Maomi.Core.Tests/Module3/Services.cs rename to tests/MaomiCoreTests/Maomi.Core.Tests/Module3/Services.cs index c270326..28841d9 100644 --- a/src/MaomiFramework/framework/Maomi.Core.Tests/Module3/Services.cs +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/Module3/Services.cs @@ -1,14 +1,8 @@ -using Maomi.Module; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Maomi.Core.Tests.Module3 -{ - public interface IA { } +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi.Core.Tests.Module3; + +public interface IA { } public interface IB { } public interface IC { } @@ -45,10 +39,9 @@ public interface SomeA { } public interface SomeB { } public interface SomeC { } - [InjectOn(scheme: InjectScheme.Some, ServicesType = new Type[] { typeof(SomeB) })] + [InjectOn(scheme: InjectScheme.Some, ServiceTypes = new Type[] { typeof(SomeB) })] public class Service_Some : Some, SomeA, SomeB, SomeC { } [InjectOn(scheme: InjectScheme.None, Own = true)] public class Service_Own : ParentService, IA, IB, IC { } -} diff --git a/tests/MaomiCoreTests/Maomi.Core.Tests/ModuleTest.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/ModuleTest.cs new file mode 100644 index 0000000..40b84c9 --- /dev/null +++ b/tests/MaomiCoreTests/Maomi.Core.Tests/ModuleTest.cs @@ -0,0 +1,56 @@ +using Maomi.Core.Tests.Module1; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Maomi.Core.Tests; + +// 循环依赖模块 +// 自动依赖注入 +public class ModuleTest +{ + // 正常复杂的模块和依赖注入 + [Fact] + public void Check_Module_Inject() + { + var ioc = new ServiceCollection(); + var congiguration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary()).Build(); + ioc.AddScoped(s => congiguration); + ioc.AddSingleton(); + RecordInfo recordInfo = new RecordInfo(); + ioc.AddSingleton(recordInfo); + + ioc.AddModule(); + var list = recordInfo.List; + Assert.Equal(typeof(AModule).Name, list[0]); + Assert.Equal(typeof(BModule).Name, list[1]); + Assert.Equal(typeof(CModule).Name, list[2]); + Assert.Equal(typeof(DModule).Name, list[3]); + + var services = ioc.BuildServiceProvider(); + var s1 = services.GetService(); + Assert.Null(s1); + var sa = services.GetRequiredService(); + var sb = services.GetRequiredService(); + var sc = services.GetRequiredService(); + Assert.NotNull(sa); + Assert.NotNull(sb); + Assert.NotNull(sc); + } + + // 循环依赖模块检测 + [Fact] + public void Check_Loop_Dependency_Module() + { + var ioc = new ServiceCollection(); + try + { + ioc.AddModule(); + Assert.True(false); + } + catch + { + Assert.True(true); + } + } +} \ No newline at end of file diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Properties/launchSettings.json b/tests/MaomiCoreTests/Maomi.Core.Tests/Properties/launchSettings.json similarity index 100% rename from src/MaomiFramework/framework/Maomi.Core.Tests/Properties/launchSettings.json rename to tests/MaomiCoreTests/Maomi.Core.Tests/Properties/launchSettings.json diff --git a/src/MaomiFramework/framework/Maomi.Core.Tests/Using.cs b/tests/MaomiCoreTests/Maomi.Core.Tests/Using.cs similarity index 100% rename from src/MaomiFramework/framework/Maomi.Core.Tests/Using.cs rename to tests/MaomiCoreTests/Maomi.Core.Tests/Using.cs diff --git a/tests/MaomiCoreTests/ModuleAssembly/ModuleAssembly.csproj b/tests/MaomiCoreTests/ModuleAssembly/ModuleAssembly.csproj new file mode 100644 index 0000000..eb7d372 --- /dev/null +++ b/tests/MaomiCoreTests/ModuleAssembly/ModuleAssembly.csproj @@ -0,0 +1,14 @@ + + + + Library + net8.0 + enable + enable + + + + + + + diff --git a/tests/MaomiCoreTests/ModuleAssembly/ModuleDll.cs b/tests/MaomiCoreTests/ModuleAssembly/ModuleDll.cs new file mode 100644 index 0000000..c9d7fb6 --- /dev/null +++ b/tests/MaomiCoreTests/ModuleAssembly/ModuleDll.cs @@ -0,0 +1,10 @@ +using Maomi; + +namespace ModuleAssembly; + +public class ModuleDll : IModule +{ + public void ConfigureServices(ServiceContext context) + { + } +} diff --git a/tests/MaomiCoreTests/MultipleModule/ModuleAssembly.dll b/tests/MaomiCoreTests/MultipleModule/ModuleAssembly.dll new file mode 100644 index 0000000..38556e6 Binary files /dev/null and b/tests/MaomiCoreTests/MultipleModule/ModuleAssembly.dll differ diff --git a/tests/MaomiCoreTests/MultipleModule/MultipleModule.csproj b/tests/MaomiCoreTests/MultipleModule/MultipleModule.csproj new file mode 100644 index 0000000..da97ff6 --- /dev/null +++ b/tests/MaomiCoreTests/MultipleModule/MultipleModule.csproj @@ -0,0 +1,42 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + ModuleAssembly.dll + + + + + + + + + + Always + + + + diff --git "a/tests/MaomiCoreTests/MultipleModule/\345\220\214\344\270\200\344\270\252\347\250\213\345\272\217\351\233\206\345\244\232\344\270\252\346\250\241\345\235\227.cs" "b/tests/MaomiCoreTests/MultipleModule/\345\220\214\344\270\200\344\270\252\347\250\213\345\272\217\351\233\206\345\244\232\344\270\252\346\250\241\345\235\227.cs" new file mode 100644 index 0000000..8fdd461 --- /dev/null +++ "b/tests/MaomiCoreTests/MultipleModule/\345\220\214\344\270\200\344\270\252\347\250\213\345\272\217\351\233\206\345\244\232\344\270\252\346\250\241\345\235\227.cs" @@ -0,0 +1,179 @@ +using Maomi; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace MultipleModule; + +public class ͬһ򼯶ģ +{ + /* + ͬһжģ࣬ÿģ඼ʵ + óֻɨһ + */ + + [Fact] + public void ֻɨһ() + { + var services = new ServiceCollection(); + var congiguration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary()).Build(); + services.AddScoped(s => congiguration); + + var moduleBuilder = new ModuleBuilder_TotalCount(services, new ModuleBuilderParamters(new ModuleOptions { }, typeof(ModuleA))); + moduleBuilder.Build(); + + var ioc = moduleBuilder.ServiceProvider; + + Assert.Single(moduleBuilder.AssemblyScanTotalCount); + Assert.Equal(1, moduleBuilder.AssemblyScanTotalCount[typeof(ModuleA).Assembly]); + Assert.Equal(2, moduleBuilder.InitializedModules.Count); + Assert.Single(moduleBuilder.InitializedAssemblys); + Assert.Equal(2, moduleBuilder.ModuleInstances.Count); + Assert.Empty(moduleBuilder.ModuleCoreInstances); + + var moduleA = ioc.GetRequiredService(); + var moduleB = ioc.GetRequiredService(); + + Assert.Equal(1, moduleA.InitCount); + Assert.Equal(1, moduleB.InitCount); + } + + [Fact] + public void Filter_ModuleCore() + { + var services = new ServiceCollection(); + var congiguration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary()).Build(); + services.AddScoped(s => congiguration); + + var moduleBuilder = new ModuleBuilder_TotalCount(services, new ModuleBuilderParamters(new ModuleOptions { }, typeof(ModuleC))); + moduleBuilder.Build(); + + var ioc = moduleBuilder.ServiceProvider; + + Assert.Single(moduleBuilder.AssemblyScanTotalCount); + Assert.Equal(1, moduleBuilder.AssemblyScanTotalCount[typeof(ModuleA).Assembly]); + Assert.Equal(3, moduleBuilder.InitializedModules.Count); + Assert.Single(moduleBuilder.InitializedAssemblys); + Assert.Equal(3, moduleBuilder.ModuleInstances.Count); + Assert.Single(moduleBuilder.ModuleCoreInstances); + + var moduleA = ioc.GetRequiredService(); + var moduleB = ioc.GetRequiredService(); + var moduleC = ioc.GetRequiredService(); + + Assert.Equal(1, moduleA.InitCount); + Assert.Equal(1, moduleB.InitCount); + Assert.Equal(1, moduleC.InitCount); + Assert.True(moduleC.FilterCount > 2); + } + + [Fact] + public void Import_Dll() + { + var services = new ServiceCollection(); + var congiguration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary()).Build(); + services.AddScoped(s => congiguration); + + var moduleOptions = new ModuleOptions(); + moduleOptions.CustomAssembies.Add(typeof(ModuleAssembly.ModuleDll).Assembly); + + var moduleBuilder = new ModuleBuilder_TotalCount(services, new ModuleBuilderParamters(moduleOptions, typeof(ModuleC))); + moduleBuilder.Build(); + + var ioc = moduleBuilder.ServiceProvider; + + Assert.Equal(2, moduleBuilder.AssemblyScanTotalCount.Count); + foreach (var item in moduleBuilder.AssemblyScanTotalCount) + { + Assert.Equal(1, item.Value); + } + + Assert.Equal(4, moduleBuilder.InitializedModules.Count); + Assert.Equal(2, moduleBuilder.InitializedAssemblys.Count); + Assert.Equal(4, moduleBuilder.ModuleInstances.Count); + Assert.Single(moduleBuilder.ModuleCoreInstances); + + var moduleA = ioc.GetRequiredService(); + var moduleB = ioc.GetRequiredService(); + var moduleC = ioc.GetRequiredService(); + var moduleDll = ioc.GetRequiredService(); + + Assert.Equal(1, moduleA.InitCount); + Assert.Equal(1, moduleB.InitCount); + Assert.Equal(1, moduleC.InitCount); + Assert.True(moduleC.FilterCount > 2); + } +} + +public class ModuleBuilder_TotalCount : ModuleBuilder +{ + public Dictionary AssemblyScanTotalCount { get; private set; } = new(); + + public IServiceProvider ServiceProvider => _serviceProvider; + + public HashSet InitializedModules => _initializedModules; + + public HashSet InitializedAssemblys => _initializedAssemblys; + + public HashSet ModuleInstances => _moduleInstances; + + public HashSet ModuleCoreInstances => _moduleCoreInstances; + + public ModuleBuilder_TotalCount(IServiceCollection services, ModuleBuilderParamters modulerParamters) : base(services, modulerParamters) + { + } + + protected override void ScanServiceFromAssembly(Assembly assembly) + { + if (AssemblyScanTotalCount.TryGetValue(assembly, out var count)) + { + AssemblyScanTotalCount[assembly] = count + 1; + } + else + { + AssemblyScanTotalCount[assembly] = 1; + } + + base.ScanServiceFromAssembly(assembly); + } +} + +[InjectModule] +public class ModuleA : IModule +{ + public int InitCount { get; private set; } + public void ConfigureServices(ServiceContext context) + { + InitCount++; + } +} + +public class ModuleB : IModule +{ + public int InitCount { get; private set; } + public void ConfigureServices(ServiceContext context) + { + InitCount++; + } +} + +[InjectModule] +public class ModuleC : ModuleCore +{ + public int InitCount { get; private set; } + + public int FilterCount { get; private set; } + + public override void ConfigureServices(ServiceContext context) + { + InitCount++; + } + + public override void TypeFilter(Type type) + { + FilterCount++; + } +} \ No newline at end of file