38 changed files with 1365 additions and 0 deletions
			
			
		| @ -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 | ||||
| @ -0,0 +1,5 @@ | ||||
| <Project> | ||||
| 	<PropertyGroup> | ||||
| 		<VoloAbpVersion>5.1.4</VoloAbpVersion> | ||||
| 	</PropertyGroup> | ||||
| </Project> | ||||
| @ -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 | ||||
| @ -0,0 +1,9 @@ | ||||
| <Project> | ||||
|   <PropertyGroup> | ||||
|     <LangVersion>latest</LangVersion> | ||||
|     <Version>5.1.4</Version> | ||||
|     <NoWarn>$(NoWarn);CS1591;</NoWarn> | ||||
|     <RepositoryType>git</RepositoryType> | ||||
|     <GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||||
|   </PropertyGroup> | ||||
| </Project> | ||||
| @ -0,0 +1,9 @@ | ||||
| <Project> | ||||
|   <ItemGroup> | ||||
|       <PackageReference Include="ConfigureAwait.Fody" Version="3.3.1" PrivateAssets="All" /> | ||||
|       <PackageReference Include="Fody" Version="6.5.3"> | ||||
|         <PrivateAssets>All</PrivateAssets> | ||||
|         <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> | ||||
|       </PackageReference> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,15 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>netstandard2.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <PackageReference Include="Volo.Abp.ExceptionHandling" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
| @ -0,0 +1,8 @@ | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| public class AbpHttpWrapConsts | ||||
| { | ||||
|     public const string AbpWrapResult = "_AbpWrapResult"; | ||||
| 
 | ||||
|     public const string AbpDontWrapResult = "_AbpDontWrapResult"; | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| using Volo.Abp.ExceptionHandling; | ||||
| using Volo.Abp.Modularity; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| [DependsOn(typeof(AbpExceptionHandlingModule))] | ||||
| public class AbpWrapperModule : AbpModule | ||||
| { | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,111 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Net; | ||||
| using Volo.Abp.Collections; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| public class AbpWrapperOptions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 未处理异常代码 | ||||
|     /// 默认: 500 | ||||
|     /// </summary> | ||||
|     public string CodeWithUnhandled { get; set; } | ||||
|     /// <summary> | ||||
|     /// 是否启用包装器 | ||||
|     /// </summary> | ||||
|     public bool IsEnabled { get; set; } | ||||
|     /// <summary> | ||||
|     /// 成功时返回代码 | ||||
|     /// 默认:0 | ||||
|     /// </summary> | ||||
|     public string CodeWithSuccess { get; set; } | ||||
|     /// <summary> | ||||
|     /// 资源为空时是否提示错误 | ||||
|     /// 默认: false | ||||
|     /// </summary> | ||||
|     public bool ErrorWithEmptyResult { get; set; } | ||||
|     /// <summary> | ||||
|     /// 资源为空时返回代码 | ||||
|     /// 默认:404 | ||||
|     /// </summary> | ||||
|     public Func<IServiceProvider, string> CodeWithEmptyResult { get; set; } | ||||
|     /// <summary> | ||||
|     /// 资源为空时返回错误消息 | ||||
|     /// </summary> | ||||
|     public Func<IServiceProvider, string> MessageWithEmptyResult { get; set; } | ||||
|     /// <summary> | ||||
|     /// 包装后的返回状态码 | ||||
|     /// 默认:200  HttpStatusCode.OK | ||||
|     /// </summary> | ||||
|     public HttpStatusCode HttpStatusCode { get; set; } | ||||
|     /// <summary> | ||||
|     /// 忽略Url开头类型 | ||||
|     /// </summary> | ||||
|     public IList<string> IgnorePrefixUrls { get; } | ||||
|     /// <summary> | ||||
|     /// 忽略指定命名空间 | ||||
|     /// </summary> | ||||
|     public IList<string> IgnoreNamespaces { get; } | ||||
|     /// <summary> | ||||
|     /// 忽略控制器 | ||||
|     /// </summary> | ||||
|     public ITypeList IgnoreControllers { get; } | ||||
|     /// <summary> | ||||
|     /// 忽略返回值 | ||||
|     /// </summary> | ||||
|     public ITypeList IgnoreReturnTypes { get; } | ||||
|     /// <summary> | ||||
|     /// 忽略异常 | ||||
|     /// </summary> | ||||
|     public ITypeList<Exception> IgnoreExceptions { get; } | ||||
|     /// <summary> | ||||
|     /// 忽略接口类型 | ||||
|     /// </summary> | ||||
|     public ITypeList IgnoredInterfaces { get; } | ||||
| 
 | ||||
|     internal IDictionary<Type, IExceptionWrapHandler> ExceptionHandles { get; } | ||||
| 
 | ||||
|     public AbpWrapperOptions() | ||||
|     { | ||||
|         CodeWithUnhandled = "500"; | ||||
|         CodeWithSuccess = "0"; | ||||
|         HttpStatusCode = HttpStatusCode.OK; | ||||
|         ErrorWithEmptyResult = false; | ||||
| 
 | ||||
|         IgnorePrefixUrls = new List<string>(); | ||||
|         IgnoreNamespaces = new List<string>(); | ||||
| 
 | ||||
|         IgnoreControllers = new TypeList(); | ||||
|         IgnoreReturnTypes = new TypeList(); | ||||
|         IgnoredInterfaces = new TypeList() | ||||
|         { | ||||
|             typeof(IWrapDisabled) | ||||
|         }; | ||||
|         IgnoreExceptions = new TypeList<Exception>(); | ||||
| 
 | ||||
|         CodeWithEmptyResult = (_) => "404"; | ||||
|         MessageWithEmptyResult = (_) => "Not Found"; | ||||
| 
 | ||||
|         ExceptionHandles = new Dictionary<Type, IExceptionWrapHandler>(); | ||||
|     } | ||||
| 
 | ||||
|     public void AddHandler<TException>(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; | ||||
|     } | ||||
| } | ||||
| @ -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<IOptions<AbpWrapperOptions>>().Value; | ||||
|             context.WithCode(wrapperOptions.CodeWithUnhandled); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,64 @@ | ||||
| using System; | ||||
| using System.Net; | ||||
| using Volo.Abp.Http; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 异常包装上下文。 | ||||
| /// </summary> | ||||
| public class ExceptionWrapContext | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 异常 | ||||
|     /// </summary> | ||||
|     public Exception Exception { get; } | ||||
|     /// <summary> | ||||
|     /// 服务提供器 | ||||
|     /// </summary> | ||||
|     public IServiceProvider ServiceProvider { get; } | ||||
|     /// <summary> | ||||
|     /// 用于存储有关错误的信息 | ||||
|     /// </summary> | ||||
|     public RemoteServiceErrorInfo ErrorInfo { get; } | ||||
|     /// <summary> | ||||
|     /// 状态码 | ||||
|     /// </summary> | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| @ -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<AbpWrapperOptions> 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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 异常包装处理器接口。 | ||||
| /// </summary> | ||||
| public interface IExceptionWrapHandler | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 包装 | ||||
|     /// </summary> | ||||
|     /// <param name="context">异常包装上下文</param> | ||||
|     void Wrap(ExceptionWrapContext context); | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| public interface IExceptionWrapHandlerFactory | ||||
| { | ||||
|     IExceptionWrapHandler CreateFor(ExceptionWrapContext context); | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 继承此接口表示禁用包装。 | ||||
| /// </summary> | ||||
| public interface IWrapDisabled | ||||
| { | ||||
| 
 | ||||
| } | ||||
| @ -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() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 返回值包装结构。 | ||||
| /// </summary> | ||||
| [Serializable] | ||||
| public class WrapResult : WrapResult<object> | ||||
| { | ||||
|     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) | ||||
|     { | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Wrapper; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 返回值包装结构。 | ||||
| /// </summary> | ||||
| /// <typeparam name="TResult"></typeparam> | ||||
| [Serializable] | ||||
| public class WrapResult<TResult> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 错误代码 | ||||
|     /// </summary> | ||||
|     public string Code { get; set; } | ||||
|     /// <summary> | ||||
|     /// 错误提示消息 | ||||
|     /// </summary> | ||||
|     public string Message { get; set; } | ||||
|     /// <summary> | ||||
|     /// 补充消息 | ||||
|     /// </summary> | ||||
|     public string Details { get; set; } | ||||
|     /// <summary> | ||||
|     /// 返回值 | ||||
|     /// </summary> | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,37 @@ | ||||
| # Sanhe.Abp.AspNetCore.Mvc.Wrapper | ||||
| 
 | ||||
| 返回值包装器 | ||||
| 
 | ||||
| ## 配置使用 | ||||
| 
 | ||||
| ```csharp | ||||
| [DependsOn(typeof(AbpAspNetCoreMvcWrapperModule))] | ||||
| public class YouProjectModule : AbpModule | ||||
| { | ||||
| 	public override void ConfigureServices(ServiceConfigurationContext context) | ||||
| 	{ | ||||
| 		Configure<AbpAspNetCoreMvcWrapperOptions>(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					指定哪些异常类型不需要处理   | ||||
| 
 | ||||
| 
 | ||||
| ## 其他 | ||||
| 
 | ||||
| @ -0,0 +1,34 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>net6.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <None Remove="Sanhe\Abp\AspNetCore\Mvc\Wrapper\Localization\Resources\en.json" /> | ||||
| 	  <None Remove="Sanhe\Abp\AspNetCore\Mvc\Wrapper\Localization\Resources\zh-Hans.json" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <EmbeddedResource Include="Sanhe\Abp\AspNetCore\Mvc\Wrapper\Localization\Resources\en.json" /> | ||||
| 	  <EmbeddedResource Include="Sanhe\Abp\AspNetCore\Mvc\Wrapper\Localization\Resources\zh-Hans.json" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\..\common\Sanhe.Abp.Wrapper\Sanhe.Abp.Wrapper.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <Folder Include="Microsoft\AspNetCore\Cors\" /> | ||||
| 	  <Folder Include="Microsoft\AspNetCore\Mvc\" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
| @ -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<AbpVirtualFileSystemOptions>(options => | ||||
|         { | ||||
|             options.FileSets.AddEmbedded<AbpAspNetCoreMvcWrapperModule>(); | ||||
|         }); | ||||
| 
 | ||||
|         Configure<AbpLocalizationOptions>(options => | ||||
|         { | ||||
|             options.Resources | ||||
|                 .Add<AbpMvcWrapperResource>("en") | ||||
|                 .AddVirtualJson("/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources"); | ||||
|         }); | ||||
| 
 | ||||
|         Configure<MvcOptions>(mvcOptions => | ||||
|         { | ||||
|             // Wrap Result Filter | ||||
|             mvcOptions.Filters.AddService(typeof(AbpWrapResultFilter)); | ||||
|         }); | ||||
| 
 | ||||
|         Configure<AbpWrapperOptions>(options => | ||||
|         { | ||||
|             // 即使重写端点也不包装返回结果 | ||||
|             // api/abp/api-definition | ||||
|             options.IgnoreReturnTypes.Add<ApplicationApiDescriptionModel>(); | ||||
|             // api/abp/application-configuration | ||||
|             options.IgnoreReturnTypes.Add<ApplicationConfigurationDto>(); | ||||
|             // 流 | ||||
|             options.IgnoreReturnTypes.Add<IRemoteStreamContent>(); | ||||
|             // Abp/ServiceProxyScript | ||||
|             options.IgnoreControllers.Add<AbpServiceProxyScriptController>(); | ||||
| 
 | ||||
|             // 官方模块不包装结果 | ||||
|             options.IgnoreNamespaces.Add("Volo.Abp"); | ||||
| 
 | ||||
|             // 返回本地化的 404 错误消息 | ||||
|             options.MessageWithEmptyResult = (serviceProvider) => | ||||
|             { | ||||
|                 var localizer = serviceProvider.GetRequiredService<IStringLocalizer<AbpMvcWrapperResource>>(); | ||||
|                 return localizer["Wrapper:NotFound"]; | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -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<IWrapResultChecker>(); | ||||
|             if (!wrapResultChecker.WrapOnException(context)) | ||||
|             { | ||||
|                 await base.HandleAndWrapException(context); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var wrapOptions = context.GetRequiredService<IOptions<AbpWrapperOptions>>().Value; | ||||
|             var exceptionHandlingOptions = context.GetRequiredService<IOptions<AbpExceptionHandlingOptions>>().Value; | ||||
|             var exceptionToErrorInfoConverter = context.GetRequiredService<IExceptionToErrorInfoConverter>(); | ||||
|             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<IJsonSerializer>().Serialize(remoteServiceErrorInfo, indented: true)); | ||||
| 
 | ||||
|             var logger = context.GetService<ILogger<AbpExceptionPageWrapResultFilter>>(NullLogger<AbpExceptionPageWrapResultFilter>.Instance); | ||||
|             logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); | ||||
| 
 | ||||
|             logger.LogException(context.Exception, logLevel); | ||||
| 
 | ||||
|             await context.GetRequiredService<IExceptionNotifier>().NotifyAsync(new ExceptionNotificationContext(context.Exception)); | ||||
| 
 | ||||
| 
 | ||||
|             if (context.Exception is AbpAuthorizationException) | ||||
|             { | ||||
|                 await context.HttpContext.RequestServices.GetRequiredService<IAbpAuthorizationExceptionHandler>() | ||||
|                     .HandleAsync(context.Exception.As<AbpAuthorizationException>(), context.HttpContext); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var statusCodFinder = context.GetRequiredService<IHttpExceptionStatusCodeFinder>(); | ||||
|                 var exceptionWrapHandler = context.GetRequiredService<IExceptionWrapHandlerFactory>(); | ||||
|                  | ||||
|                 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! | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<IWrapResultChecker>(); | ||||
| 
 | ||||
|             if (!wrapResultChecker.WrapOnException(context)) | ||||
|             { | ||||
|                 await base.HandleAndWrapException(context); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             //TODO: Trigger an AbpExceptionHandled event or something like that. | ||||
|             var wrapOptions = context.GetRequiredService<IOptions<AbpWrapperOptions>>().Value; | ||||
|             var exceptionHandlingOptions = context.GetRequiredService<IOptions<AbpExceptionHandlingOptions>>().Value; | ||||
|             var exceptionToErrorInfoConverter = context.GetRequiredService<IExceptionToErrorInfoConverter>(); | ||||
|             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<IJsonSerializer>().Serialize(remoteServiceErrorInfo, indented: true)); | ||||
| 
 | ||||
|             var logger = context.GetService<ILogger<AbpExceptionWrapResultFilter>>(NullLogger<AbpExceptionWrapResultFilter>.Instance); | ||||
| 
 | ||||
|             logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); | ||||
| 
 | ||||
|             logger.LogException(context.Exception, logLevel); | ||||
| 
 | ||||
|             await context.GetRequiredService<IExceptionNotifier>().NotifyAsync(new ExceptionNotificationContext(context.Exception)); | ||||
| 
 | ||||
|             if (context.Exception is AbpAuthorizationException) | ||||
|             { | ||||
|                 await context.GetRequiredService<IAbpAuthorizationExceptionHandler>() | ||||
|                     .HandleAsync(context.Exception.As<AbpAuthorizationException>(), context.HttpContext); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var statusCodFinder = context.GetRequiredService<IHttpExceptionStatusCodeFinder>(); | ||||
|                 var exceptionWrapHandler = context.GetRequiredService<IExceptionWrapHandlerFactory>(); | ||||
|                  | ||||
|                 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! | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<IWrapResultChecker>(); | ||||
| 
 | ||||
|             return wrapResultChecker.WrapOnExecution(context); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual Task HandleAndWrapResult(ResultExecutingContext context) | ||||
|         { | ||||
|             var options = context.GetRequiredService<IOptions<AbpWrapperOptions>>().Value; | ||||
|             var actionResultWrapperFactory = context.GetRequiredService<IActionResultWrapperFactory>(); | ||||
|             actionResultWrapperFactory.CreateFor(context).Wrap(context); | ||||
|             context.HttpContext.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); | ||||
|             context.HttpContext.Response.StatusCode = (int)options.HttpStatusCode; | ||||
| 
 | ||||
|             return Task.CompletedTask; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,15 @@ | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
| 
 | ||||
| namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 包装结果检测器。用于检测是否需要进行包装返回结果。 | ||||
| /// </summary> | ||||
| public interface IWrapResultChecker | ||||
| { | ||||
|     bool WrapOnExecution(FilterContext context); | ||||
| 
 | ||||
|     bool WrapOnException(ExceptionContext context); | ||||
| 
 | ||||
|     bool WrapOnException(PageHandlerExecutedContext context); | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| using Volo.Abp.Localization; | ||||
| 
 | ||||
| namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Localization | ||||
| { | ||||
|     [LocalizationResourceName("AbpMvcWrapper")] | ||||
|     public class AbpMvcWrapperResource | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| { | ||||
|   "culture": "en", | ||||
|   "texts": { | ||||
|     "Wrapper:NotFound": "The requested resource was not found on the server." | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| { | ||||
|   "culture": "zh-Hans", | ||||
|   "texts": { | ||||
|     "Wrapper:NotFound": "在服务器中没有找到请求的资源." | ||||
|   } | ||||
| } | ||||
| @ -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<AbpWrapperOptions> 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; | ||||
|     } | ||||
| } | ||||
| @ -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(), | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -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<IOptions<AbpWrapperOptions>>().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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
| 
 | ||||
| namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 结果包装器。用于包装返回结果。 | ||||
| /// </summary> | ||||
| public interface IActionResultWrapper | ||||
| { | ||||
|     void Wrap(FilterContext context); | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| using Microsoft.AspNetCore.Mvc.Filters; | ||||
| 
 | ||||
| namespace Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping; | ||||
| 
 | ||||
| public interface IActionResultWrapperFactory | ||||
| { | ||||
|     IActionResultWrapper CreateFor(FilterContext context); | ||||
| } | ||||
| @ -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<IOptions<AbpWrapperOptions>>().Value; | ||||
| 
 | ||||
|                 jsonResult.Value = new WrapResult(options.CodeWithSuccess, jsonResult.Value); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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) | ||||
|         { | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<IOptions<AbpWrapperOptions>>().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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue