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