From 862ebb3945e2ce1aa89dad4199f927c0fd8f933f Mon Sep 17 00:00:00 2001 From: wwwk Date: Mon, 4 Apr 2022 21:38:09 +0800 Subject: [PATCH] =?UTF-8?q?Wrapper=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 138 ++++++++++++++ Directory.Build.props | 5 + Sanhe.Abp.Startup.sln | 48 +++++ common.props | 9 + configureawait.props | 9 + .../common/Sanhe.Abp.Wrapper/FodyWeavers.xml | 3 + .../Sanhe.Abp.Wrapper.csproj | 15 ++ .../Sanhe/Abp/Wrapper/AbpHttpWrapConsts.cs | 8 + .../Sanhe/Abp/Wrapper/AbpWrapperModule.cs | 10 + .../Sanhe/Abp/Wrapper/AbpWrapperOptions.cs | 111 +++++++++++ .../Wrapper/DefaultExceptionWrapHandler.cs | 40 ++++ .../Sanhe/Abp/Wrapper/ExceptionWrapContext.cs | 64 +++++++ .../Wrapper/ExceptionWrapHandlerFactory.cs | 31 +++ .../Abp/Wrapper/IExceptionWrapHandler.cs | 13 ++ .../Wrapper/IExceptionWrapHandlerFactory.cs | 6 + .../Sanhe/Abp/Wrapper/IWrapDisabled.cs | 9 + .../Abp/Wrapper/IgnoreWrapResultAttribute.cs | 12 ++ .../Sanhe/Abp/Wrapper/WrapResult.cs | 26 +++ .../Sanhe/Abp/Wrapper/WrapResult`T.cs | 50 +++++ .../FodyWeavers.xml | 3 + .../README.md | 37 ++++ .../Sanhe.Abp.AspNetCore.Mvc.Wrapper.csproj | 34 ++++ .../Wrapper/AbpAspNetCoreMvcWrapperModule.cs | 66 +++++++ .../AbpExceptionPageWrapResultFilter.cs | 88 +++++++++ .../AbpExceptionWrapResultFilter.cs | 90 +++++++++ .../Wrapper/Filters/AbpWrapResultFilter.cs | 41 ++++ .../Mvc/Wrapper/IWrapResultChecker.cs | 15 ++ .../Localization/AbpMvcWrapperResource.cs | 10 + .../Wrapper/Localization/Resources/en.json | 6 + .../Localization/Resources/zh-Hans.json | 6 + .../Mvc/Wrapper/WrapResultChecker.cs | 176 ++++++++++++++++++ .../Wraping/ActionResultWrapperFactory.cs | 25 +++ .../Wraping/EmptyActionResultWrapper.cs | 39 ++++ .../Wrapper/Wraping/IActionResultWrapper.cs | 11 ++ .../Wraping/IActionResultWrapperFactory.cs | 8 + .../Wraping/JsonActionResultWrapper.cs | 40 ++++ .../Wraping/NullActionResultWrapper.cs | 12 ++ .../Wraping/ObjectActionResultWrapper.cs | 51 +++++ 38 files changed, 1365 insertions(+) create mode 100644 .editorconfig create mode 100644 Directory.Build.props create mode 100644 Sanhe.Abp.Startup.sln create mode 100644 common.props create mode 100644 configureawait.props create mode 100644 modules/common/Sanhe.Abp.Wrapper/FodyWeavers.xml create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe.Abp.Wrapper.csproj create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpHttpWrapConsts.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperModule.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperOptions.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/DefaultExceptionWrapHandler.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapContext.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapHandlerFactory.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandler.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandlerFactory.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IWrapDisabled.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IgnoreWrapResultAttribute.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs create mode 100644 modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/FodyWeavers.xml create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/README.md create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe.Abp.AspNetCore.Mvc.Wrapper.csproj create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/AbpMvcWrapperResource.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/en.json create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/zh-Hans.json create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ActionResultWrapperFactory.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/EmptyActionResultWrapper.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapper.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapperFactory.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/JsonActionResultWrapper.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/NullActionResultWrapper.cs create mode 100644 modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ObjectActionResultWrapper.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..51a4bdc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,138 @@ +# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\MicroService\CRM\Vue\abp-next-admin\aspnet-core codebase based on best match to current usage at 2022-01-07 +# There already existed an .editorconfig file in this directory. Copy rules from this .editorconfig.inferred file to the existing .editorconfig file as desired to have them take effect at this location. +# You can modify the rules from these initially generated values to suit your own policies +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +[*.cs] + + +#Core editorconfig formatting - indentation + +#use soft tabs (spaces) for indentation +indent_style = space + +#Formatting - indentation options + +#indent switch case contents. +csharp_indent_case_contents = true +#indent switch labels +csharp_indent_switch_labels = true + +#Formatting - new line options + +#place catch statements on a new line +csharp_new_line_before_catch = true +#place else statements on a new line +csharp_new_line_before_else = true +#require members of anonymous types to be on separate lines +csharp_new_line_before_members_in_anonymous_types = true +#require members of object initializers to be on the same line +csharp_new_line_before_members_in_object_initializers = false +#require braces to be on a new line for methods, control_blocks, types, lambdas, object_collection_array_initializers, anonymous_methods, and anonymous_types (also known as "Allman" style) +csharp_new_line_before_open_brace = methods, control_blocks, types, lambdas, object_collection_array_initializers, anonymous_methods, anonymous_types + +#Formatting - organize using options + +#do not place System.* using directives before other using directives +dotnet_sort_system_directives_first = false + +#Formatting - spacing options + +#require NO space between a cast and the value +csharp_space_after_cast = false +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_after_colon_in_inheritance_clause = true +#require a space after a keyword in a control flow statement such as a for loop +csharp_space_after_keywords_in_control_flow_statements = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_before_colon_in_inheritance_clause = true +#remove space within empty argument list parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false +#remove space between method call name and opening parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false +#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call +csharp_space_between_method_call_parameter_list_parentheses = false +#remove space within empty parameter list parentheses for a method declaration +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options + +#leave code block on single line +csharp_preserve_single_line_blocks = true +#leave statements and member declarations on the same line +csharp_preserve_single_line_statements = true + +#Style - Code block preferences + +#prefer curly braces even for one line of code +csharp_prefer_braces = true:suggestion + +#Style - expression bodied member options + +#prefer block bodies for constructors +csharp_style_expression_bodied_constructors = false:suggestion +#prefer block bodies for methods +csharp_style_expression_bodied_methods = false:suggestion +#prefer expression-bodied members for properties +csharp_style_expression_bodied_properties = true:suggestion + +#Style - expression level options + +#prefer out variables to be declared inline in the argument list of a method call when possible +csharp_style_inlined_variable_declaration = true:suggestion +#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_member_access = true:suggestion + +#Style - Expression-level preferences + +#prefer default over default(T) +csharp_prefer_simple_default_expression = true:suggestion +#prefer objects to be initialized using object initializers when possible +dotnet_style_object_initializer = true:suggestion +#prefer inferred anonymous type member names +dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion + +#Style - implicit and explicit types + +#prefer var over explicit type in all cases, unless overridden by another code style rule +csharp_style_var_elsewhere = true:suggestion +#prefer var is used to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = true:suggestion +#prefer var when the type is already mentioned on the right-hand side of a declaration expression +csharp_style_var_when_type_is_apparent = true:suggestion + +#Style - language keyword and framework type options + +#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +#Style - Miscellaneous preferences + +#prefer anonymous functions over local functions +csharp_style_pattern_local_over_anonymous_function = false:suggestion + +#Style - modifier options + +#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +#Style - Modifier preferences + +#when this rule is set to a list of modifiers, prefer the specified ordering. +csharp_preferred_modifier_order = public,private,protected,internal,async,virtual,readonly,static,override,abstract:suggestion + +#Style - Pattern matching + +#prefer pattern matching instead of is expression with type casts +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +#Style - qualification options + +#prefer fields not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_field = false:suggestion +#prefer methods not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_method = false:suggestion +#prefer properties not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_property = false:suggestion +csharp_style_namespace_declarations=file_scoped:silent diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..0cdea9d --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + 5.1.4 + + \ No newline at end of file diff --git a/Sanhe.Abp.Startup.sln b/Sanhe.Abp.Startup.sln new file mode 100644 index 0000000..f02bd98 --- /dev/null +++ b/Sanhe.Abp.Startup.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32328.378 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{F5F5D604-531B-4B57-A88E-C9C5CEEC55D7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "services", "services", "{BBE3B270-DF4F-47F5-9705-89FCFDE2779F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{2A768109-31B7-4C52-928C-3023DAB9F254}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0E7AC09-318B-4119-A09D-237961F1965F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mvc", "mvc", "{21B8099B-881D-40AE-888E-FC94E66D90C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.Wrapper", "modules\common\Sanhe.Abp.Wrapper\Sanhe.Abp.Wrapper.csproj", "{8C1439CF-4968-4A95-9E9F-CE8F1BBED0A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.AspNetCore.Mvc.Wrapper", "modules\mvc\Sanhe.Abp.AspNetCore.Mvc.Wrapper\Sanhe.Abp.AspNetCore.Mvc.Wrapper.csproj", "{06F1C7AC-3A43-4210-8126-8DA0089A39EE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8C1439CF-4968-4A95-9E9F-CE8F1BBED0A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C1439CF-4968-4A95-9E9F-CE8F1BBED0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C1439CF-4968-4A95-9E9F-CE8F1BBED0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C1439CF-4968-4A95-9E9F-CE8F1BBED0A0}.Release|Any CPU.Build.0 = Release|Any CPU + {06F1C7AC-3A43-4210-8126-8DA0089A39EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06F1C7AC-3A43-4210-8126-8DA0089A39EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06F1C7AC-3A43-4210-8126-8DA0089A39EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06F1C7AC-3A43-4210-8126-8DA0089A39EE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2A768109-31B7-4C52-928C-3023DAB9F254} = {F5F5D604-531B-4B57-A88E-C9C5CEEC55D7} + {E0E7AC09-318B-4119-A09D-237961F1965F} = {BBE3B270-DF4F-47F5-9705-89FCFDE2779F} + {21B8099B-881D-40AE-888E-FC94E66D90C0} = {F5F5D604-531B-4B57-A88E-C9C5CEEC55D7} + {8C1439CF-4968-4A95-9E9F-CE8F1BBED0A0} = {2A768109-31B7-4C52-928C-3023DAB9F254} + {06F1C7AC-3A43-4210-8126-8DA0089A39EE} = {21B8099B-881D-40AE-888E-FC94E66D90C0} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AB69BFDE-9DDB-4D16-8CB8-72472C0319CD} + EndGlobalSection +EndGlobal diff --git a/common.props b/common.props new file mode 100644 index 0000000..5402705 --- /dev/null +++ b/common.props @@ -0,0 +1,9 @@ + + + latest + 5.1.4 + $(NoWarn);CS1591; + git + true + + \ No newline at end of file diff --git a/configureawait.props b/configureawait.props new file mode 100644 index 0000000..b0cf9ef --- /dev/null +++ b/configureawait.props @@ -0,0 +1,9 @@ + + + + + All + runtime; build; native; contentfiles; analyzers + + + \ No newline at end of file diff --git a/modules/common/Sanhe.Abp.Wrapper/FodyWeavers.xml b/modules/common/Sanhe.Abp.Wrapper/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe.Abp.Wrapper.csproj b/modules/common/Sanhe.Abp.Wrapper/Sanhe.Abp.Wrapper.csproj new file mode 100644 index 0000000..7c118d8 --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe.Abp.Wrapper.csproj @@ -0,0 +1,15 @@ + + + + + + + netstandard2.0 + + + + + + + + diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpHttpWrapConsts.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpHttpWrapConsts.cs new file mode 100644 index 0000000..2f5c52c --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpHttpWrapConsts.cs @@ -0,0 +1,8 @@ +namespace Sanhe.Abp.Wrapper; + +public class AbpHttpWrapConsts +{ + public const string AbpWrapResult = "_AbpWrapResult"; + + public const string AbpDontWrapResult = "_AbpDontWrapResult"; +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperModule.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperModule.cs new file mode 100644 index 0000000..5c8767e --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Modularity; + +namespace Sanhe.Abp.Wrapper; + +[DependsOn(typeof(AbpExceptionHandlingModule))] +public class AbpWrapperModule : AbpModule +{ + +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperOptions.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperOptions.cs new file mode 100644 index 0000000..bc316b1 --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/AbpWrapperOptions.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Volo.Abp.Collections; + +namespace Sanhe.Abp.Wrapper; + +public class AbpWrapperOptions +{ + /// + /// 未处理异常代码 + /// 默认: 500 + /// + public string CodeWithUnhandled { get; set; } + /// + /// 是否启用包装器 + /// + public bool IsEnabled { get; set; } + /// + /// 成功时返回代码 + /// 默认:0 + /// + public string CodeWithSuccess { get; set; } + /// + /// 资源为空时是否提示错误 + /// 默认: false + /// + public bool ErrorWithEmptyResult { get; set; } + /// + /// 资源为空时返回代码 + /// 默认:404 + /// + public Func CodeWithEmptyResult { get; set; } + /// + /// 资源为空时返回错误消息 + /// + public Func MessageWithEmptyResult { get; set; } + /// + /// 包装后的返回状态码 + /// 默认:200 HttpStatusCode.OK + /// + public HttpStatusCode HttpStatusCode { get; set; } + /// + /// 忽略Url开头类型 + /// + public IList IgnorePrefixUrls { get; } + /// + /// 忽略指定命名空间 + /// + public IList IgnoreNamespaces { get; } + /// + /// 忽略控制器 + /// + public ITypeList IgnoreControllers { get; } + /// + /// 忽略返回值 + /// + public ITypeList IgnoreReturnTypes { get; } + /// + /// 忽略异常 + /// + public ITypeList IgnoreExceptions { get; } + /// + /// 忽略接口类型 + /// + public ITypeList IgnoredInterfaces { get; } + + internal IDictionary ExceptionHandles { get; } + + public AbpWrapperOptions() + { + CodeWithUnhandled = "500"; + CodeWithSuccess = "0"; + HttpStatusCode = HttpStatusCode.OK; + ErrorWithEmptyResult = false; + + IgnorePrefixUrls = new List(); + IgnoreNamespaces = new List(); + + IgnoreControllers = new TypeList(); + IgnoreReturnTypes = new TypeList(); + IgnoredInterfaces = new TypeList() + { + typeof(IWrapDisabled) + }; + IgnoreExceptions = new TypeList(); + + CodeWithEmptyResult = (_) => "404"; + MessageWithEmptyResult = (_) => "Not Found"; + + ExceptionHandles = new Dictionary(); + } + + public void AddHandler(IExceptionWrapHandler handler) + where TException : Exception + { + AddHandler(typeof(TException), handler); + } + + public void AddHandler(Type exceptionType, IExceptionWrapHandler handler) + { + ExceptionHandles[exceptionType] = handler; + } + + public IExceptionWrapHandler GetHandler(Type exceptionType) + { + ExceptionHandles.TryGetValue(exceptionType, out IExceptionWrapHandler handler); + + return handler; + } +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/DefaultExceptionWrapHandler.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/DefaultExceptionWrapHandler.cs new file mode 100644 index 0000000..7e67063 --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/DefaultExceptionWrapHandler.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using Volo.Abp.ExceptionHandling; + +namespace Sanhe.Abp.Wrapper; + +public class DefaultExceptionWrapHandler : IExceptionWrapHandler +{ + public void Wrap(ExceptionWrapContext context) + { + if (context.Exception is IHasErrorCode exceptionWithErrorCode) + { + string errorCode; + if (!exceptionWithErrorCode.Code.IsNullOrWhiteSpace() && + exceptionWithErrorCode.Code.Contains(":")) + { + errorCode = exceptionWithErrorCode.Code.Split(':')[1]; + } + else + { + errorCode = exceptionWithErrorCode.Code; + } + + context.WithCode(errorCode); + } + + // 没有处理的异常代码统一用配置代码处理 + if (context.ErrorInfo.Code.IsNullOrWhiteSpace()) + { + if (context.StatusCode.HasValue) + { + context.WithCode(((int)context.StatusCode).ToString()); + return; + } + var wrapperOptions = context.ServiceProvider.GetRequiredService>().Value; + context.WithCode(wrapperOptions.CodeWithUnhandled); + } + } +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapContext.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapContext.cs new file mode 100644 index 0000000..73a5026 --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapContext.cs @@ -0,0 +1,64 @@ +using System; +using System.Net; +using Volo.Abp.Http; + +namespace Sanhe.Abp.Wrapper; + +/// +/// 异常包装上下文。 +/// +public class ExceptionWrapContext +{ + /// + /// 异常 + /// + public Exception Exception { get; } + /// + /// 服务提供器 + /// + public IServiceProvider ServiceProvider { get; } + /// + /// 用于存储有关错误的信息 + /// + public RemoteServiceErrorInfo ErrorInfo { get; } + /// + /// 状态码 + /// + public HttpStatusCode? StatusCode { get; set; } + + public ExceptionWrapContext( + Exception exception, + RemoteServiceErrorInfo errorInfo, + IServiceProvider serviceProvider, + HttpStatusCode? statusCode = null) + { + Exception = exception; + ErrorInfo = errorInfo; + ServiceProvider = serviceProvider; + StatusCode = statusCode; + } + + public ExceptionWrapContext WithCode(string code) + { + ErrorInfo.Code = code; + return this; + } + + public ExceptionWrapContext WithMessage(string message) + { + ErrorInfo.Message = message; + return this; + } + + public ExceptionWrapContext WithDetails(string details) + { + ErrorInfo.Details = details; + return this; + } + + public ExceptionWrapContext WithData(string key, object value) + { + ErrorInfo.Data[key] = value; + return this; + } +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapHandlerFactory.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapHandlerFactory.cs new file mode 100644 index 0000000..eb1ea3b --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/ExceptionWrapHandlerFactory.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Text; +using Volo.Abp.DependencyInjection; + +namespace Sanhe.Abp.Wrapper; + +public class ExceptionWrapHandlerFactory : IExceptionWrapHandlerFactory, ITransientDependency +{ + private readonly AbpWrapperOptions _options; + + public ExceptionWrapHandlerFactory(IOptions options) + { + _options = options.Value; + } + + public IExceptionWrapHandler CreateFor(ExceptionWrapContext context) + { + var exceptionType = context.Exception.GetType(); + var handler = _options.GetHandler(exceptionType); + if (handler == null) + { + handler = new DefaultExceptionWrapHandler(); + _options.AddHandler(exceptionType, handler); + return handler; + } + + return handler; + } +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandler.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandler.cs new file mode 100644 index 0000000..38e3b7e --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandler.cs @@ -0,0 +1,13 @@ +namespace Sanhe.Abp.Wrapper; + +/// +/// 异常包装处理器接口。 +/// +public interface IExceptionWrapHandler +{ + /// + /// 包装 + /// + /// 异常包装上下文 + void Wrap(ExceptionWrapContext context); +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandlerFactory.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandlerFactory.cs new file mode 100644 index 0000000..1c45125 --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IExceptionWrapHandlerFactory.cs @@ -0,0 +1,6 @@ +namespace Sanhe.Abp.Wrapper; + +public interface IExceptionWrapHandlerFactory +{ + IExceptionWrapHandler CreateFor(ExceptionWrapContext context); +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IWrapDisabled.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IWrapDisabled.cs new file mode 100644 index 0000000..ce9ff7c --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IWrapDisabled.cs @@ -0,0 +1,9 @@ +namespace Sanhe.Abp.Wrapper; + +/// +/// 继承此接口表示禁用包装。 +/// +public interface IWrapDisabled +{ + +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IgnoreWrapResultAttribute.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IgnoreWrapResultAttribute.cs new file mode 100644 index 0000000..cb3f0ea --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/IgnoreWrapResultAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Sanhe.Abp.Wrapper; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +public class IgnoreWrapResultAttribute : Attribute +{ + public IgnoreWrapResultAttribute() + { + + } +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs new file mode 100644 index 0000000..64790af --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs @@ -0,0 +1,26 @@ +using System; + +namespace Sanhe.Abp.Wrapper; + +/// +/// 返回值包装结构。 +/// +[Serializable] +public class WrapResult : WrapResult +{ + public WrapResult() { } + + public WrapResult( + string code, + string message, + string details = null) : base(code, message, details) + { + } + + public WrapResult( + string code, + object result, + string message = "OK") : base(code, result, message) + { + } +} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs new file mode 100644 index 0000000..ee907a2 --- /dev/null +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs @@ -0,0 +1,50 @@ +using System; + +namespace Sanhe.Abp.Wrapper; + +/// +/// 返回值包装结构。 +/// +/// +[Serializable] +public class WrapResult +{ + /// + /// 错误代码 + /// + public string Code { get; set; } + /// + /// 错误提示消息 + /// + public string Message { get; set; } + /// + /// 补充消息 + /// + public string Details { get; set; } + /// + /// 返回值 + /// + public TResult Result { get; set; } + + public WrapResult() { } + + public WrapResult( + string code, + string message, + string details = null) + { + Code = code; + Message = message; + Details = details; + } + + public WrapResult( + string code, + TResult result, + string message = "OK") + { + Code = code; + Result = result; + Message = message; + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/FodyWeavers.xml b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/FodyWeavers.xml new file mode 100644 index 0000000..1715698 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/README.md b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/README.md new file mode 100644 index 0000000..2ef552d --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/README.md @@ -0,0 +1,37 @@ +# Sanhe.Abp.AspNetCore.Mvc.Wrapper + +返回值包装器 + +## 配置使用 + +```csharp +[DependsOn(typeof(AbpAspNetCoreMvcWrapperModule))] +public class YouProjectModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + // 启用包装器 + options.IsEnabled = true; + }); + } +} +``` +## 配置项说明 + +* AbpAspNetCoreMvcWrapperOptions.IsEnabled 是否包装返回结果,默认: false +* AbpAspNetCoreMvcWrapperOptions.CodeWithFound 响应成功代码,默认: 0 +* AbpAspNetCoreMvcWrapperOptions.HttpStatusCode 包装后的Http响应代码, 默认: 200 +* AbpAspNetCoreMvcWrapperOptions.CodeWithEmptyResult 当返回空对象时返回错误代码,默认: 404 +* AbpAspNetCoreMvcWrapperOptions.MessageWithEmptyResult 当返回空对象时返回错误消息, 默认: 本地化之后的 NotFound + +* AbpAspNetCoreMvcWrapperOptions.IgnorePrefixUrls 指定哪些Url开头的地址不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreNamespaces 指定哪些命名空间开头不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreControllers 指定哪些控制器不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreReturnTypes 指定哪些返回结果类型不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreExceptions 指定哪些异常类型不需要处理 + + +## 其他 + diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe.Abp.AspNetCore.Mvc.Wrapper.csproj b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe.Abp.AspNetCore.Mvc.Wrapper.csproj new file mode 100644 index 0000000..3270084 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe.Abp.AspNetCore.Mvc.Wrapper.csproj @@ -0,0 +1,34 @@ + + + + + + + net6.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs new file mode 100644 index 0000000..ef75f89 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Sanhe.Abp.AspNetCore.Mvc.Wrapper.Filters; +using Sanhe.Abp.AspNetCore.Mvc.Wrapper.Localization; +using Sanhe.Abp.Wrapper; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.AspNetCore.Mvc.ProxyScripting; +using Volo.Abp.Content; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper; + +[DependsOn( + typeof(AbpWrapperModule), + typeof(AbpAspNetCoreMvcModule))] +public class AbpAspNetCoreMvcWrapperModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources"); + }); + + Configure(mvcOptions => + { + // Wrap Result Filter + mvcOptions.Filters.AddService(typeof(AbpWrapResultFilter)); + }); + + Configure(options => + { + // 即使重写端点也不包装返回结果 + // api/abp/api-definition + options.IgnoreReturnTypes.Add(); + // api/abp/application-configuration + options.IgnoreReturnTypes.Add(); + // 流 + options.IgnoreReturnTypes.Add(); + // Abp/ServiceProxyScript + options.IgnoreControllers.Add(); + + // 官方模块不包装结果 + options.IgnoreNamespaces.Add("Volo.Abp"); + + // 返回本地化的 404 错误消息 + options.MessageWithEmptyResult = (serviceProvider) => + { + var localizer = serviceProvider.GetRequiredService>(); + return localizer["Wrapper:NotFound"]; + }; + }); + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs new file mode 100644 index 0000000..672de60 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs @@ -0,0 +1,88 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Sanhe.Abp.Wrapper; +using System; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.ExceptionHandling; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.Authorization; +using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Http; +using Volo.Abp.Json; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.ExceptionHandling +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(AbpExceptionPageFilter))] + public class AbpExceptionPageWrapResultFilter : AbpExceptionPageFilter, ITransientDependency + { + protected async override Task HandleAndWrapException(PageHandlerExecutedContext context) + { + var wrapResultChecker = context.GetRequiredService(); + if (!wrapResultChecker.WrapOnException(context)) + { + await base.HandleAndWrapException(context); + return; + } + + var wrapOptions = context.GetRequiredService>().Value; + var exceptionHandlingOptions = context.GetRequiredService>().Value; + var exceptionToErrorInfoConverter = context.GetRequiredService(); + var remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, options => + { + options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients; + options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients; + }); + + var logLevel = context.Exception.GetLogLevel(); + + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService().Serialize(remoteServiceErrorInfo, indented: true)); + + var logger = context.GetService>(NullLogger.Instance); + logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + + logger.LogException(context.Exception, logLevel); + + await context.GetRequiredService().NotifyAsync(new ExceptionNotificationContext(context.Exception)); + + + if (context.Exception is AbpAuthorizationException) + { + await context.HttpContext.RequestServices.GetRequiredService() + .HandleAsync(context.Exception.As(), context.HttpContext); + } + else + { + var statusCodFinder = context.GetRequiredService(); + var exceptionWrapHandler = context.GetRequiredService(); + + var exceptionWrapContext = new ExceptionWrapContext( + context.Exception, + remoteServiceErrorInfo, + context.HttpContext.RequestServices, + statusCodFinder.GetStatusCode(context.HttpContext, context.Exception)); + + exceptionWrapHandler.CreateFor(exceptionWrapContext).Wrap(exceptionWrapContext); + + context.Result = new ObjectResult(new WrapResult( + exceptionWrapContext.ErrorInfo.Code, + exceptionWrapContext.ErrorInfo.Message, + exceptionWrapContext.ErrorInfo.Details)); + + context.HttpContext.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); + context.HttpContext.Response.StatusCode = (int)wrapOptions.HttpStatusCode; + } + + context.Exception = null; //Handled! + } + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs new file mode 100644 index 0000000..7a642a3 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs @@ -0,0 +1,90 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Sanhe.Abp.Wrapper; +using System; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.ExceptionHandling; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.Authorization; +using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Http; +using Volo.Abp.Json; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.ExceptionHandling +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(AbpExceptionFilter))] + public class AbpExceptionWrapResultFilter : AbpExceptionFilter, ITransientDependency + { + protected async override Task HandleAndWrapException(ExceptionContext context) + { + var wrapResultChecker = context.GetRequiredService(); + + if (!wrapResultChecker.WrapOnException(context)) + { + await base.HandleAndWrapException(context); + return; + } + + //TODO: Trigger an AbpExceptionHandled event or something like that. + var wrapOptions = context.GetRequiredService>().Value; + var exceptionHandlingOptions = context.GetRequiredService>().Value; + var exceptionToErrorInfoConverter = context.GetRequiredService(); + var remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, options => + { + options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients; + options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients; + }); + + var logLevel = context.Exception.GetLogLevel(); + + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService().Serialize(remoteServiceErrorInfo, indented: true)); + + var logger = context.GetService>(NullLogger.Instance); + + logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + + logger.LogException(context.Exception, logLevel); + + await context.GetRequiredService().NotifyAsync(new ExceptionNotificationContext(context.Exception)); + + if (context.Exception is AbpAuthorizationException) + { + await context.GetRequiredService() + .HandleAsync(context.Exception.As(), context.HttpContext); + } + else + { + var statusCodFinder = context.GetRequiredService(); + var exceptionWrapHandler = context.GetRequiredService(); + + var exceptionWrapContext = new ExceptionWrapContext( + context.Exception, + remoteServiceErrorInfo, + context.HttpContext.RequestServices, + statusCodFinder.GetStatusCode(context.HttpContext, context.Exception)); + + exceptionWrapHandler.CreateFor(exceptionWrapContext).Wrap(exceptionWrapContext); + + context.Result = new ObjectResult(new WrapResult( + exceptionWrapContext.ErrorInfo.Code, + exceptionWrapContext.ErrorInfo.Message, + exceptionWrapContext.ErrorInfo.Details)); + + context.HttpContext.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); + context.HttpContext.Response.StatusCode = (int)wrapOptions.HttpStatusCode; + } + + context.Exception = null; //Handled! + } + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs new file mode 100644 index 0000000..b3838db --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs @@ -0,0 +1,41 @@ +using Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping; +using Sanhe.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.DependencyInjection; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Filters +{ + public class AbpWrapResultFilter : IAsyncResultFilter, ITransientDependency + { + public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) + { + if (ShouldWrapResult(context)) + { + await HandleAndWrapResult(context); + } + + await next(); + } + + protected virtual bool ShouldWrapResult(ResultExecutingContext context) + { + var wrapResultChecker = context.GetRequiredService(); + + return wrapResultChecker.WrapOnExecution(context); + } + + protected virtual Task HandleAndWrapResult(ResultExecutingContext context) + { + var options = context.GetRequiredService>().Value; + var actionResultWrapperFactory = context.GetRequiredService(); + actionResultWrapperFactory.CreateFor(context).Wrap(context); + context.HttpContext.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); + context.HttpContext.Response.StatusCode = (int)options.HttpStatusCode; + + return Task.CompletedTask; + } + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs new file mode 100644 index 0000000..85d7d35 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper; + +/// +/// 包装结果检测器。用于检测是否需要进行包装返回结果。 +/// +public interface IWrapResultChecker +{ + bool WrapOnExecution(FilterContext context); + + bool WrapOnException(ExceptionContext context); + + bool WrapOnException(PageHandlerExecutedContext context); +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/AbpMvcWrapperResource.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/AbpMvcWrapperResource.cs new file mode 100644 index 0000000..22e184c --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/AbpMvcWrapperResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Localization +{ + [LocalizationResourceName("AbpMvcWrapper")] + public class AbpMvcWrapperResource + { + + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/en.json b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/en.json new file mode 100644 index 0000000..b66b8da --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Wrapper:NotFound": "The requested resource was not found on the server." + } +} \ No newline at end of file diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/zh-Hans.json b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/zh-Hans.json new file mode 100644 index 0000000..cdaa776 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Wrapper:NotFound": "在服务器中没有找到请求的资源." + } +} \ No newline at end of file diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs new file mode 100644 index 0000000..a6a1955 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs @@ -0,0 +1,176 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Sanhe.Abp.Wrapper; +using System; +using System.Linq; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper; + +public class WrapResultChecker : IWrapResultChecker, ISingletonDependency +{ + protected AbpWrapperOptions Options { get; } + + public WrapResultChecker(IOptionsMonitor optionsMonitor) + { + Options = optionsMonitor.CurrentValue; + } + + public bool WrapOnException(ExceptionContext context) + { + if (!CheckForBase(context)) + { + return false; + } + + return CheckForException(context.Exception); + } + + public bool WrapOnException(PageHandlerExecutedContext context) + { + return CheckForException(context.Exception); + } + + public bool WrapOnExecution(FilterContext context) + { + return CheckForBase(context); + } + + protected virtual bool CheckForBase(FilterContext context) + { + if (!Options.IsEnabled) + { + return false; + } + + if (context.HttpContext.Request.Headers.ContainsKey(AbpHttpWrapConsts.AbpDontWrapResult)) + { + return false; + } + + if (context.ActionDescriptor is ControllerActionDescriptor descriptor) + { + if (!context.ActionDescriptor.HasObjectResult()) + { + return false; + } + + //if (!context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) + //{ + // return false; + //} + + if (!CheckForUrl(context)) + { + return false; + } + + if (!CheckForNamespace(descriptor)) + { + return false; + } + + if (!CheckForController(descriptor)) + { + return false; + } + + if (!CheckForInterfaces(descriptor)) + { + return false; + } + + if (!CheckForMethod(descriptor)) + { + return false; + } + + if (!CheckForReturnType(descriptor)) + { + return false; + } + + return true; + } + + return false; + } + + protected virtual bool CheckForUrl(FilterContext context) + { + if (!Options.IgnorePrefixUrls.Any()) + { + return true; + } + var url = BuildUrl(context.HttpContext); + return !Options.IgnorePrefixUrls.Any(urlPrefix => urlPrefix.StartsWith(url)); + } + + protected virtual bool CheckForController(ControllerActionDescriptor controllerActionDescriptor) + { + if (controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(IgnoreWrapResultAttribute), true)) + { + return false; + } + + return !Options.IgnoreControllers.Any(controller => + controller.IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo)); + } + + protected virtual bool CheckForMethod(ControllerActionDescriptor controllerActionDescriptor) + { + return !controllerActionDescriptor.MethodInfo.IsDefined(typeof(IgnoreWrapResultAttribute), true); + } + + protected virtual bool CheckForNamespace(ControllerActionDescriptor controllerActionDescriptor) + { + if (string.IsNullOrWhiteSpace(controllerActionDescriptor.ControllerTypeInfo.Namespace)) + { + return true; + } + + return !Options.IgnoreNamespaces.Any(nsp => + controllerActionDescriptor.ControllerTypeInfo.Namespace.StartsWith(nsp)); + } + + protected virtual bool CheckForInterfaces(ControllerActionDescriptor controllerActionDescriptor) + { + return !Options.IgnoredInterfaces.Any(type => + type.IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo)); + } + + protected virtual bool CheckForReturnType(ControllerActionDescriptor controllerActionDescriptor) + { + var returnType = AsyncHelper.UnwrapTask(controllerActionDescriptor.MethodInfo.ReturnType); + + if (returnType.IsDefined(typeof(IgnoreWrapResultAttribute), true)) + { + return false; + } + + return !Options.IgnoreReturnTypes.Any(type => returnType.IsAssignableFrom(type)); + } + + protected virtual bool CheckForException(Exception exception) + { + return !Options.IgnoreExceptions.Any(ex => ex.IsAssignableFrom(exception.GetType())); + } + + protected virtual string BuildUrl(HttpContext httpContext) + { + //TODO: Add options to include/exclude query, schema and host + + var uriBuilder = new UriBuilder(); + + uriBuilder.Scheme = httpContext.Request.Scheme; + uriBuilder.Host = httpContext.Request.Host.Host; + uriBuilder.Path = httpContext.Request.Path.ToString(); + uriBuilder.Query = httpContext.Request.QueryString.ToString(); + + return uriBuilder.Uri.AbsolutePath; + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ActionResultWrapperFactory.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ActionResultWrapperFactory.cs new file mode 100644 index 0000000..278c26c --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ActionResultWrapperFactory.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping; + +public class ActionResultWrapperFactory : IActionResultWrapperFactory, ITransientDependency +{ + public IActionResultWrapper CreateFor(FilterContext context) + { + Check.NotNull(context, nameof(context)); + + return context switch + { + ResultExecutingContext resultExecutingContext when resultExecutingContext.Result is ObjectResult => new ObjectActionResultWrapper(), + ResultExecutingContext resultExecutingContext when resultExecutingContext.Result is JsonResult => new JsonActionResultWrapper(), + ResultExecutingContext resultExecutingContext when resultExecutingContext.Result is EmptyResult => new EmptyActionResultWrapper(), + PageHandlerExecutedContext pageHandlerExecutedContext when pageHandlerExecutedContext.Result is ObjectResult => new ObjectActionResultWrapper(), + PageHandlerExecutedContext pageHandlerExecutedContext when pageHandlerExecutedContext.Result is JsonResult => new JsonActionResultWrapper(), + PageHandlerExecutedContext pageHandlerExecutedContext when pageHandlerExecutedContext.Result is EmptyResult => new EmptyActionResultWrapper(), + _ => new NullActionResultWrapper(), + }; + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/EmptyActionResultWrapper.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/EmptyActionResultWrapper.cs new file mode 100644 index 0000000..ec36872 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/EmptyActionResultWrapper.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Sanhe.Abp.Wrapper; +using Volo.Abp.AspNetCore.Mvc; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping; + +public class EmptyActionResultWrapper : IActionResultWrapper +{ + public void Wrap(FilterContext context) + { + var options = context.GetRequiredService>().Value; + switch (context) + { + case ResultExecutingContext resultExecutingContext: + if (options.ErrorWithEmptyResult) + { + var code = options.CodeWithEmptyResult(context.HttpContext.RequestServices); + var message = options.MessageWithEmptyResult(context.HttpContext.RequestServices); + resultExecutingContext.Result = new ObjectResult(new WrapResult(code, message)); + return; + } + resultExecutingContext.Result = new ObjectResult(new WrapResult(options.CodeWithSuccess, result: null)); + return; + + case PageHandlerExecutedContext pageHandlerExecutedContext: + if (options.ErrorWithEmptyResult) + { + var code = options.CodeWithEmptyResult(context.HttpContext.RequestServices); + var message = options.MessageWithEmptyResult(context.HttpContext.RequestServices); + pageHandlerExecutedContext.Result = new ObjectResult(new WrapResult(code, message)); + return; + } + pageHandlerExecutedContext.Result = new ObjectResult(new WrapResult(options.CodeWithSuccess, result: null)); + return; + } + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapper.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapper.cs new file mode 100644 index 0000000..29a8139 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapper.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping; + +/// +/// 结果包装器。用于包装返回结果。 +/// +public interface IActionResultWrapper +{ + void Wrap(FilterContext context); +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapperFactory.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapperFactory.cs new file mode 100644 index 0000000..9f50c4c --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapperFactory.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping; + +public interface IActionResultWrapperFactory +{ + IActionResultWrapper CreateFor(FilterContext context); +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/JsonActionResultWrapper.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/JsonActionResultWrapper.cs new file mode 100644 index 0000000..f6e2852 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/JsonActionResultWrapper.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Sanhe.Abp.Wrapper; +using System; +using Volo.Abp.AspNetCore.Mvc; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public class JsonActionResultWrapper : IActionResultWrapper + { + public void Wrap(FilterContext context) + { + JsonResult jsonResult = null; + + switch (context) + { + case ResultExecutingContext resultExecutingContext: + jsonResult = resultExecutingContext.Result as JsonResult; + break; + + case PageHandlerExecutedContext pageHandlerExecutedContext: + jsonResult = pageHandlerExecutedContext.Result as JsonResult; + break; + } + + if (jsonResult == null) + { + throw new ArgumentException("Action Result should be JsonResult!"); + } + + if (jsonResult.Value is not WrapResult) + { + var options = context.GetRequiredService>().Value; + + jsonResult.Value = new WrapResult(options.CodeWithSuccess, jsonResult.Value); + } + } + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/NullActionResultWrapper.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/NullActionResultWrapper.cs new file mode 100644 index 0000000..7c5cb6f --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/NullActionResultWrapper.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public class NullActionResultWrapper : IActionResultWrapper + { + public void Wrap(FilterContext context) + { + + } + } +} diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ObjectActionResultWrapper.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ObjectActionResultWrapper.cs new file mode 100644 index 0000000..1ac6002 --- /dev/null +++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Wraping/ObjectActionResultWrapper.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Sanhe.Abp.Wrapper; +using System; +using Volo.Abp.AspNetCore.Mvc; + +namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public class ObjectActionResultWrapper : IActionResultWrapper + { + public void Wrap(FilterContext context) + { + ObjectResult objectResult = null; + + switch (context) + { + case ResultExecutingContext resultExecutingContext: + objectResult = resultExecutingContext.Result as ObjectResult; + break; + + case PageHandlerExecutedContext pageHandlerExecutedContext: + objectResult = pageHandlerExecutedContext.Result as ObjectResult; + break; + } + + if (objectResult == null) + { + throw new ArgumentException("Action Result should be ObjectResult!"); + } + + if (objectResult.Value is not WrapResult) + { + var options = context.GetRequiredService>().Value; + + if (objectResult.Value == null && options.ErrorWithEmptyResult) + { + var code = options.CodeWithEmptyResult(context.HttpContext.RequestServices); + var message = options.MessageWithEmptyResult(context.HttpContext.RequestServices); + objectResult.Value = new WrapResult(code, message); + } + else + { + objectResult.Value = new WrapResult(options.CodeWithSuccess, objectResult.Value); + } + + objectResult.DeclaredType = typeof(WrapResult); + } + } + } +}