diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index ba6ffa7ca..d32a303cf 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/aspnet-core/LINGYUN.MicroService.Common.sln @@ -192,7 +192,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.DataProtection. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EntityFrameworkCore.Tests", "tests\LINGYUN.Abp.EntityFrameworkCore.Tests\LINGYUN.Abp.EntityFrameworkCore.Tests.csproj", "{2F556889-006C-4A9C-8CA3-E31200C06FC9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Dapr", "modules\dapr\LINGYUN.Abp.Dapr\LINGYUN.Abp.Dapr.csproj", "{73C9A7E7-846D-49E2-B223-E705D6C48BE7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr", "modules\dapr\LINGYUN.Abp.Dapr\LINGYUN.Abp.Dapr.csproj", "{73C9A7E7-846D-49E2-B223-E705D6C48BE7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Wrapper", "modules\common\LINGYUN.Abp.Wrapper\LINGYUN.Abp.Wrapper.csproj", "{328B0863-23BE-43FD-98DD-FF0C92D5BEF0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mvc", "mvc", "{F55B987D-1DFF-4EB0-9949-8A7136A7B689}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc.Wrapper", "modules\mvc\LINGYUN.Abp.AspNetCore.Mvc.Wrapper\LINGYUN.Abp.AspNetCore.Mvc.Wrapper.csproj", "{D72748AF-2CC8-4B5B-9710-ECDE5D812D7F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Tests", "tests\LINGYUN.Abp.AspNetCore.Tests\LINGYUN.Abp.AspNetCore.Tests.csproj", "{BD4165DB-F8A4-4715-A05A-CC08F6A18D67}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc.Tests", "tests\LINGYUN.Abp.AspNetCore.Mvc.Tests\LINGYUN.Abp.AspNetCore.Mvc.Tests.csproj", "{AE5E6DE8-FC02-4633-BA49-C4B8ABADB502}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -504,6 +514,22 @@ Global {73C9A7E7-846D-49E2-B223-E705D6C48BE7}.Debug|Any CPU.Build.0 = Debug|Any CPU {73C9A7E7-846D-49E2-B223-E705D6C48BE7}.Release|Any CPU.ActiveCfg = Release|Any CPU {73C9A7E7-846D-49E2-B223-E705D6C48BE7}.Release|Any CPU.Build.0 = Release|Any CPU + {328B0863-23BE-43FD-98DD-FF0C92D5BEF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {328B0863-23BE-43FD-98DD-FF0C92D5BEF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {328B0863-23BE-43FD-98DD-FF0C92D5BEF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {328B0863-23BE-43FD-98DD-FF0C92D5BEF0}.Release|Any CPU.Build.0 = Release|Any CPU + {D72748AF-2CC8-4B5B-9710-ECDE5D812D7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D72748AF-2CC8-4B5B-9710-ECDE5D812D7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D72748AF-2CC8-4B5B-9710-ECDE5D812D7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D72748AF-2CC8-4B5B-9710-ECDE5D812D7F}.Release|Any CPU.Build.0 = Release|Any CPU + {BD4165DB-F8A4-4715-A05A-CC08F6A18D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD4165DB-F8A4-4715-A05A-CC08F6A18D67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD4165DB-F8A4-4715-A05A-CC08F6A18D67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD4165DB-F8A4-4715-A05A-CC08F6A18D67}.Release|Any CPU.Build.0 = Release|Any CPU + {AE5E6DE8-FC02-4633-BA49-C4B8ABADB502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE5E6DE8-FC02-4633-BA49-C4B8ABADB502}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE5E6DE8-FC02-4633-BA49-C4B8ABADB502}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE5E6DE8-FC02-4633-BA49-C4B8ABADB502}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -601,6 +627,11 @@ Global {FBE7D8CB-1D99-4342-A953-B9AB46E0B14D} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} {2F556889-006C-4A9C-8CA3-E31200C06FC9} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} {73C9A7E7-846D-49E2-B223-E705D6C48BE7} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} + {328B0863-23BE-43FD-98DD-FF0C92D5BEF0} = {086BE5BE-8594-4DA7-8819-935FEF76DABD} + {F55B987D-1DFF-4EB0-9949-8A7136A7B689} = {02EA4E78-5891-43BC-944F-3E52FEE032E4} + {D72748AF-2CC8-4B5B-9710-ECDE5D812D7F} = {F55B987D-1DFF-4EB0-9949-8A7136A7B689} + {BD4165DB-F8A4-4715-A05A-CC08F6A18D67} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} + {AE5E6DE8-FC02-4633-BA49-C4B8ABADB502} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN.Abp.Wrapper.csproj b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN.Abp.Wrapper.csproj new file mode 100644 index 000000000..fadbb6606 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN.Abp.Wrapper.csproj @@ -0,0 +1,14 @@ + + + + + + netstandard2.0 + + + + + + + + diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/AbpWrapperModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/AbpWrapperModule.cs new file mode 100644 index 000000000..883c9acde --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/AbpWrapperModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Wrapper +{ + public class AbpWrapperModule: AbpModule + { + + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/IgnoreWrapResultAttribute.cs b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/IgnoreWrapResultAttribute.cs new file mode 100644 index 000000000..59ac3b980 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/IgnoreWrapResultAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace LINGYUN.Abp.Wrapper +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public class IgnoreWrapResultAttribute : Attribute + { + public IgnoreWrapResultAttribute() + { + + } + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/WrapResult.cs b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/WrapResult.cs new file mode 100644 index 000000000..1a10b2815 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/WrapResult.cs @@ -0,0 +1,25 @@ +using System; + +namespace LINGYUN.Abp.Wrapper +{ + [Serializable] + public class WrapResult: WrapResult + { + public WrapResult() { } + public WrapResult( + string code, + string message, + string details = null) + : base(code, message, details) + { + } + + public WrapResult( + string code, + object result, + string message = "OK") + : base(code, result, message) + { + } + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/WrapResult`T.cs b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/WrapResult`T.cs new file mode 100644 index 000000000..9a5171f80 --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Wrapper/LINGYUN/Abp/Wrapper/WrapResult`T.cs @@ -0,0 +1,49 @@ +using System; + +namespace LINGYUN.Abp.Wrapper +{ + /// + /// 返回值包装结构 + /// + /// + [Serializable] + public class WrapResult + { + /// + /// 错误代码 + /// + public string Code { get; set; } + /// + /// 错误提示消息 + /// + public string Message { get; set; } + /// + /// 补充消息 + /// + public string Details { get; set; } + /// + /// 返回值 + /// + public TResult Result { get; set; } + public WrapResult() { } + public WrapResult( + string code, + string message, + string details = null) + { + Code = code; + Message = message; + Details = details; + } + + public WrapResult( + string code, + TResult result, + string message = "OK") + { + Code = code; + Result = result; + Message = message; + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN.Abp.AspNetCore.Mvc.Wrapper.csproj b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN.Abp.AspNetCore.Mvc.Wrapper.csproj new file mode 100644 index 000000000..e512fb2e7 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN.Abp.AspNetCore.Mvc.Wrapper.csproj @@ -0,0 +1,28 @@ + + + + + + net5.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs new file mode 100644 index 000000000..7dad941c5 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperModule.cs @@ -0,0 +1,65 @@ +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Filters; +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Localization; +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.ApiExploring; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.AspNetCore.Mvc.ProxyScripting; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper +{ + [DependsOn( + typeof(AbpWrapperModule), + typeof(AbpAspNetCoreMvcModule))] + public class AbpAspNetCoreMvcWrapperModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources"); + }); + + Configure(mvcOptions => + { + // Wrap Result Filter + mvcOptions.Filters.AddService(typeof(AbpWrapResultFilter)); + }); + + Configure(options => + { + // 即使重写端点也不包装返回结果 + // api/abp/api-definition + options.IgnoreReturnTypes.Add(); + // api/abp/application-configuration + options.IgnoreReturnTypes.Add(); + // Abp/ServiceProxyScript + options.IgnoreControllers.Add(); + + // 官方模块不包装结果 + options.IgnoreNamespaces.Add("Volo.Abp"); + + // 返回本地化的 404 错误消息 + options.MessageWithEmptyResult = (serviceProvider) => + { + var localizer = serviceProvider.GetRequiredService>(); + return localizer["Wrapper:NotFound"]; + }; + }); + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperOptions.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperOptions.cs new file mode 100644 index 000000000..2e974fbe7 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpAspNetCoreMvcWrapperOptions.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Volo.Abp.Collections; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper +{ + public class AbpAspNetCoreMvcWrapperOptions + { + /// + /// 是否启用包装器 + /// + public bool IsEnabled { get; set; } + /// + /// 资源有效时返回代码 + /// 默认:0 + /// + public string CodeWithFound { get; set; } + /// + /// 资源为空时返回代码 + /// 默认:404 + /// + public Func CodeWithEmptyResult { get; set; } + /// + /// 资源为空时返回错误消息 + /// + public Func MessageWithEmptyResult { get; set; } + /// + /// 包装后的返回状态码 + /// 默认:200 HttpStatusCode.OK + /// + public HttpStatusCode HttpStatusCode { get; set; } + /// + /// 忽略Url开头类型 + /// + public IList IgnorePrefixUrls { get; } + /// + /// 忽略指定命名空间 + /// + public IList IgnoreNamespaces { get; } + /// + /// 忽略控制器 + /// + public ITypeList IgnoreControllers { get; } + /// + /// 忽略返回值 + /// + public ITypeList IgnoreReturnTypes { get; } + /// + /// 忽略异常 + /// + public ITypeList IgnoreExceptions { get; } + + public AbpAspNetCoreMvcWrapperOptions() + { + CodeWithFound = "0"; + HttpStatusCode = HttpStatusCode.OK; + + IgnorePrefixUrls = new List(); + IgnoreNamespaces = new List(); + + IgnoreControllers = new TypeList(); + IgnoreReturnTypes = new TypeList(); + IgnoreExceptions = new TypeList(); + + CodeWithEmptyResult = (_) => "404"; + MessageWithEmptyResult = (_) => "Not Found"; + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpHttpWrapConsts.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpHttpWrapConsts.cs new file mode 100644 index 000000000..c3b8e6955 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/AbpHttpWrapConsts.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper +{ + public static class AbpHttpWrapConsts + { + public const string AbpWrapResult = "_AbpWrapResult"; + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs new file mode 100644 index 000000000..952612e1b --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs @@ -0,0 +1,81 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +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.DependencyInjection; +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Http; +using Volo.Abp.Json; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.ExceptionHandling +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(AbpExceptionPageFilter))] + public class AbpExceptionPageWrapResultFilter: AbpExceptionPageFilter, ITransientDependency + { + protected override bool ShouldHandleException(PageHandlerExecutingContext context) + { + if (!context.ActionDescriptor.CanWarpRsult()) + { + return false; + } + return base.ShouldHandleException(context); + } + + protected override async Task HandleAndWrapException(PageHandlerExecutedContext context) + { + var wrapResultChecker = context.GetRequiredService(); + if (!wrapResultChecker.WrapOnException(context)) + { + await base.HandleAndWrapException(context); + return; + } + + var wrapResultOptions = context.GetRequiredService>().Value; + var exceptionHandlingOptions = context.GetRequiredService>().Value; + var exceptionToErrorInfoConverter = context.GetRequiredService(); + var remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, exceptionHandlingOptions.SendExceptionsDetailsToClients); + + var logLevel = context.Exception.GetLogLevel(); + + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService().Serialize(remoteServiceErrorInfo, indented: true)); + + context.HttpContext.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); + + var logger = context.GetService>(NullLogger.Instance); + logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + + logger.LogException(context.Exception, logLevel); + + await context.GetRequiredService().NotifyAsync(new ExceptionNotificationContext(context.Exception)); + + // Warp Error Response + string errorCode = remoteServiceErrorInfo.Code; + if (context.Exception is IHasErrorCode exceptionWithErrorCode) + { + if (!exceptionWithErrorCode.Code.IsNullOrWhiteSpace() && + exceptionWithErrorCode.Code.Contains(":")) + { + errorCode = exceptionWithErrorCode.Code.Split(':')[1]; + } + else + { + errorCode = exceptionWithErrorCode.Code; + } + } + context.Result = new ObjectResult(new WrapResult(errorCode, remoteServiceErrorInfo.Message, remoteServiceErrorInfo.Details)); + + context.Exception = null; //Handled! + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs new file mode 100644 index 000000000..fc590d414 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs @@ -0,0 +1,82 @@ +using LINGYUN.Abp.Wrapper; +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 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.DependencyInjection; +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Http; +using Volo.Abp.Json; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.ExceptionHandling +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(AbpExceptionFilter))] + public class AbpExceptionWrapResultFilter : AbpExceptionFilter, ITransientDependency + { + protected override bool ShouldHandleException(ExceptionContext context) + { + var wrapResultChecker = context.GetRequiredService(); + + if (!wrapResultChecker.WrapOnException(context)) + { + return false; + } + return base.ShouldHandleException(context); + } + + protected override async Task HandleAndWrapException(ExceptionContext context) + { + //TODO: Trigger an AbpExceptionHandled event or something like that. + var wrapResultOptions = context.GetRequiredService>().Value; + var exceptionHandlingOptions = context.GetRequiredService>().Value; + var exceptionToErrorInfoConverter = context.GetRequiredService(); + var remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, exceptionHandlingOptions.SendExceptionsDetailsToClients); + + var logLevel = context.Exception.GetLogLevel(); + + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService().Serialize(remoteServiceErrorInfo, indented: true)); + + var logger = context.GetService>(NullLogger.Instance); + + logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + + logger.LogException(context.Exception, logLevel); + + await context.GetRequiredService().NotifyAsync(new ExceptionNotificationContext(context.Exception)); + + context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); + context.HttpContext.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); + context.HttpContext.Response.StatusCode = (int)wrapResultOptions.HttpStatusCode; + + // Warp Error Response + string errorCode = remoteServiceErrorInfo.Code; + if (context.Exception is IHasErrorCode exceptionWithErrorCode) + { + if (!exceptionWithErrorCode.Code.IsNullOrWhiteSpace() && + exceptionWithErrorCode.Code.Contains(":")) + { + errorCode = exceptionWithErrorCode.Code.Split(':')[1]; + } + else + { + errorCode = exceptionWithErrorCode.Code; + } + } + context.Result = new ObjectResult(new WrapResult(errorCode, remoteServiceErrorInfo.Message, remoteServiceErrorInfo.Details)); + + context.Exception = null; //Handled! + } + + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs new file mode 100644 index 000000000..42a5b4bc2 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs @@ -0,0 +1,37 @@ +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Wraping; +using Microsoft.AspNetCore.Mvc.Filters; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Filters +{ + public class AbpWrapResultFilter : IAsyncResultFilter, ITransientDependency + { + public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) + { + if (ShouldWrapResult(context)) + { + await HandleAndWrapResult(context); + } + + await next(); + } + + protected virtual bool ShouldWrapResult(ResultExecutingContext context) + { + var wrapResultChecker = context.GetRequiredService(); + + return wrapResultChecker.WrapOnExecution(context); + } + + protected virtual Task HandleAndWrapResult(ResultExecutingContext context) + { + var actionResultWrapperFactory = context.GetRequiredService(); + actionResultWrapperFactory.CreateFor(context).Wrap(context); + context.HttpContext.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); + + return Task.CompletedTask; + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs new file mode 100644 index 000000000..1a535fdd6 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/IWrapResultChecker.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper +{ + public interface IWrapResultChecker + { + bool WrapOnExecution(FilterContext context); + + bool WrapOnException(ExceptionContext context); + + bool WrapOnException(PageHandlerExecutedContext context); + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/AbpMvcWrapperResource.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/AbpMvcWrapperResource.cs new file mode 100644 index 000000000..b7fa623dd --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/AbpMvcWrapperResource.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Localization +{ + [LocalizationResourceName("AbpMvcWrapper")] + public class AbpMvcWrapperResource + { + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/en.json b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/en.json new file mode 100644 index 000000000..c91179d48 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Wrapper:NotFound": "The requested resource was not found on the server." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/zh-Hans.json b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..7eb2a1927 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Localization/Resources/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Wrapper:NotFound": "在服务器中没有找到请求的资源." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs new file mode 100644 index 000000000..239a28657 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/WrapResultChecker.cs @@ -0,0 +1,157 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using System; +using System.Linq; +using System.Reflection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper +{ + public class WrapResultChecker : IWrapResultChecker, ISingletonDependency + { + protected AbpAspNetCoreMvcWrapperOptions Options { get; } + + public WrapResultChecker(IOptionsMonitor optionsMonitor) + { + Options = optionsMonitor.CurrentValue; + } + + public bool WrapOnException(ExceptionContext context) + { + if (!CheckForBase(context)) + { + return false; + } + + return CheckForException(context.Exception); + } + + public bool WrapOnException(PageHandlerExecutedContext context) + { + return CheckForException(context.Exception); + } + + public bool WrapOnExecution(FilterContext context) + { + return CheckForBase(context); + } + + + protected virtual bool CheckForBase(FilterContext context) + { + if (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 (!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 CheckForReturnType(ControllerActionDescriptor controllerActionDescriptor) + { + if (controllerActionDescriptor.MethodInfo.ReturnType.IsDefined(typeof(IgnoreWrapResultAttribute), true)) + { + return false; + } + + return !Options.IgnoreReturnTypes.Any(type => + controllerActionDescriptor.MethodInfo.ReturnType.IsAssignableFrom(type)); + } + + protected virtual bool CheckForException(Exception exception) + { + return !Options.IgnoreExceptions.Any(ex => ex.IsAssignableFrom(exception.GetType())); + } + + protected virtual string BuildUrl(HttpContext httpContext) + { + //TODO: Add options to include/exclude query, schema and host + + var uriBuilder = new UriBuilder(); + + uriBuilder.Scheme = httpContext.Request.Scheme; + uriBuilder.Host = httpContext.Request.Host.Host; + uriBuilder.Path = httpContext.Request.Path.ToString(); + uriBuilder.Query = httpContext.Request.QueryString.ToString(); + + return uriBuilder.Uri.AbsolutePath; + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/ActionResultWrapperFactory.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/ActionResultWrapperFactory.cs new file mode 100644 index 000000000..8db57a15f --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/ActionResultWrapperFactory.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Volo.Abp; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public class ActionResultWrapperFactory : IActionResultWrapperFactory + { + public IActionResultWrapper CreateFor(FilterContext context) + { + Check.NotNull(context, nameof(context)); + + switch (context) + { + case ResultExecutingContext resultExecutingContext when resultExecutingContext.Result is ObjectResult: + return new ObjectActionResultWrapper(); + + case ResultExecutingContext resultExecutingContext when resultExecutingContext.Result is JsonResult: + return new JsonActionResultWrapper(); + + case ResultExecutingContext resultExecutingContext when resultExecutingContext.Result is EmptyResult: + return new EmptyActionResultWrapper(); + + case PageHandlerExecutedContext pageHandlerExecutedContext when pageHandlerExecutedContext.Result is ObjectResult: + return new ObjectActionResultWrapper(); + + case PageHandlerExecutedContext pageHandlerExecutedContext when pageHandlerExecutedContext.Result is JsonResult: + return new JsonActionResultWrapper(); + + case PageHandlerExecutedContext pageHandlerExecutedContext when pageHandlerExecutedContext.Result is EmptyResult: + return new EmptyActionResultWrapper(); + + default: + return new NullActionResultWrapper(); + } + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/EmptyActionResultWrapper.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/EmptyActionResultWrapper.cs new file mode 100644 index 000000000..6904ae8cc --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/EmptyActionResultWrapper.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public class EmptyActionResultWrapper : IActionResultWrapper + { + public void Wrap(FilterContext context) + { + var options = context.GetRequiredService>().Value; + var code = options.CodeWithEmptyResult(context.HttpContext.RequestServices); + var message = options.MessageWithEmptyResult(context.HttpContext.RequestServices); + switch (context) + { + case ResultExecutingContext resultExecutingContext: + resultExecutingContext.Result = new ObjectResult(new WrapResult(code, message)); + return; + + case PageHandlerExecutedContext pageHandlerExecutedContext: + pageHandlerExecutedContext.Result = new ObjectResult(new WrapResult(code, message)); + return; + } + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapper.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapper.cs new file mode 100644 index 000000000..fac8489ca --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapper.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public interface IActionResultWrapper + { + void Wrap(FilterContext context); + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapperFactory.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapperFactory.cs new file mode 100644 index 000000000..03caf71e8 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/IActionResultWrapperFactory.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public interface IActionResultWrapperFactory : ITransientDependency + { + IActionResultWrapper CreateFor(FilterContext context); + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/JsonActionResultWrapper.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/JsonActionResultWrapper.cs new file mode 100644 index 000000000..7ce4a6110 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/JsonActionResultWrapper.cs @@ -0,0 +1,40 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using System; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.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 WrapResult)) + { + var options = context.GetRequiredService>().Value; + + jsonResult.Value = new WrapResult(options.CodeWithFound, jsonResult.Value); + } + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/NullActionResultWrapper.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/NullActionResultWrapper.cs new file mode 100644 index 000000000..f3538fde9 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/NullActionResultWrapper.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Wrapper.Wraping +{ + public class NullActionResultWrapper : IActionResultWrapper + { + public void Wrap(FilterContext context) + { + + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/ObjectActionResultWrapper.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/ObjectActionResultWrapper.cs new file mode 100644 index 000000000..b1cd7dbc9 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/LINGYUN/Abp/AspNetCore/Mvc/Wrapper/Wraping/ObjectActionResultWrapper.cs @@ -0,0 +1,51 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using System; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.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 WrapResult)) + { + var options = context.GetRequiredService>().Value; + + if (objectResult.Value != null) + { + objectResult.Value = new WrapResult(options.CodeWithFound, objectResult.Value); + } + else + { + var code = options.CodeWithEmptyResult(context.HttpContext.RequestServices); + var message = options.MessageWithEmptyResult(context.HttpContext.RequestServices); + objectResult.Value = new WrapResult(code, message); + } + + objectResult.DeclaredType = typeof(WrapResult); + } + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/Microsoft/AspNetCore/Mvc/ActionContextExtensions.cs b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/Microsoft/AspNetCore/Mvc/ActionContextExtensions.cs new file mode 100644 index 000000000..8605cfff5 --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/Microsoft/AspNetCore/Mvc/ActionContextExtensions.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace Microsoft.AspNetCore.Mvc +{ + public static class ActionContextExtensions + { + public static bool CanWarpRsult(this ActionDescriptor actionDescriptor) + { + if (actionDescriptor is ControllerActionDescriptor descriptor) + { + if (descriptor.MethodInfo.IsDefined(typeof(IgnoreWrapResultAttribute), true)) + { + return false; + } + + if (descriptor.ControllerTypeInfo.IsDefined(typeof(IgnoreWrapResultAttribute), true)) + { + return false; + } + + return true; + } + return false; + } + } +} diff --git a/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/README.md b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/README.md new file mode 100644 index 000000000..8f4ed889c --- /dev/null +++ b/aspnet-core/modules/mvc/LINGYUN.Abp.AspNetCore.Mvc.Wrapper/README.md @@ -0,0 +1,37 @@ +# LINGYUN.Abp.AspNetCore.Mvc.Wrapper + +返回值包装器 + +## 配置使用 + +```csharp +[DependsOn(typeof(AbpAspNetCoreMvcWrapperModule))] +public class YouProjectModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + // 启用包装器 + options.IsEnabled = true; + }); + } +} +``` +## 配置项说明 + +* AbpAspNetCoreMvcWrapperOptions.IsEnabled 是否包装返回结果,默认: false +* AbpAspNetCoreMvcWrapperOptions.CodeWithFound 响应成功代码,默认: 0 +* AbpAspNetCoreMvcWrapperOptions.HttpStatusCode 包装后的Http响应代码, 默认: 200 +* AbpAspNetCoreMvcWrapperOptions.CodeWithEmptyResult 当返回空对象时返回错误代码,默认: 404 +* AbpAspNetCoreMvcWrapperOptions.MessageWithEmptyResult 当返回空对象时返回错误消息, 默认: 本地化之后的 NotFound + +* AbpAspNetCoreMvcWrapperOptions.IgnorePrefixUrls 指定哪些Url开头的地址不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreNamespaces 指定哪些命名空间开头不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreControllers 指定哪些控制器不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreReturnTypes 指定哪些返回结果类型不需要处理 +* AbpAspNetCoreMvcWrapperOptions.IgnoreExceptions 指定哪些异常类型不需要处理 + + +## 其他 + diff --git a/aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/Properties/launchSettings.json b/aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/Properties/launchSettings.json index f04e44238..7a6916353 100644 --- a/aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/Properties/launchSettings.json +++ b/aspnet-core/services/admin/LINGYUN.Abp.BackendAdmin.HttpApi.Host/Properties/launchSettings.json @@ -13,7 +13,7 @@ "launchBrowser": false, "applicationUrl": "http://0.0.0.0:30010", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Production" + "ASPNETCORE_ENVIRONMENT": "Development" } } } diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN.Abp.AspNetCore.Mvc.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN.Abp.AspNetCore.Mvc.Tests.csproj new file mode 100644 index 000000000..92665ff5d --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN.Abp.AspNetCore.Mvc.Tests.csproj @@ -0,0 +1,39 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestBase.cs new file mode 100644 index 000000000..1c5c70217 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestBase.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Hosting; +using System.IO; +using System.Linq; + +namespace LINGYUN.Abp.AspNetCore.Mvc +{ + public abstract class AbpAspNetCoreMvcTestBase : AbpAspNetCoreTestBase + { + protected override IHostBuilder CreateHostBuilder() + { + return base.CreateHostBuilder(); + } + + private static string CalculateContentRootPath(string projectFileName, string contentPath) + { + var currentDirectory = Directory.GetCurrentDirectory(); + while (!ContainsFile(currentDirectory, projectFileName)) + { + currentDirectory = new DirectoryInfo(currentDirectory).Parent.FullName; + } + + return Path.Combine(currentDirectory, contentPath); + } + + private static bool ContainsFile(string currentDirectory, string projectFileName) + { + return Directory + .GetFiles(currentDirectory, "*.*", SearchOption.TopDirectoryOnly) + .Any(f => Path.GetFileName(f) == projectFileName); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs new file mode 100644 index 000000000..5653cc27f --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -0,0 +1,144 @@ +using LINGYUN.Abp.AspNetCore.Mvc.GlobalFeatures; +using LINGYUN.Abp.AspNetCore.Mvc.Localization; +using LINGYUN.Abp.AspNetCore.Mvc.Results; +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.ApiExploring; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Security.Claims; +using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Autofac; +using Volo.Abp.GlobalFeatures; +using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; +using Volo.Abp.Validation.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.AspNetCore.Mvc +{ + [DependsOn( + typeof(AbpAspNetCoreMvcWrapperModule), + typeof(AbpAspNetCoreTestBaseModule), + typeof(AbpAspNetCoreMvcModule), + typeof(AbpAutofacModule) + )] + public class AbpAspNetCoreMvcTestModule : AbpModule + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.PreConfigure(options => + { + options.AddAssemblyResource( + typeof(MvcTestResource), + typeof(AbpAspNetCoreMvcTestModule).Assembly + ); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + GlobalFeatureManager.Instance.Modules.GetOrAdd(AbpAspNetCoreMvcTestFeatures.ModuleName, + () => new AbpAspNetCoreMvcTestFeatures(GlobalFeatureManager.Instance)) + .EnableAll(); + }); + + context.Services.AddAuthentication(options => + { + options.DefaultChallengeScheme = "Bearer"; + options.DefaultForbidScheme = "Cookie"; + }).AddCookie("Cookie").AddJwtBearer("Bearer", _ => { }); + + context.Services.AddAuthorization(options => + { + }); + + Configure(options => + { + }); + + Configure(options => + { + options.IsEnabled = true; + + // 测试先清空 + options.IgnoreControllers.Clear(); + options.IgnoreExceptions.Clear(); + options.IgnoreNamespaces.Clear(); + options.IgnorePrefixUrls.Clear(); + options.IgnoreReturnTypes.Clear(); + + // api/abp/api-definition + options.IgnoreControllers.Add(); + // api/abp/application-configuration + options.IgnoreReturnTypes.Add(); + + options.IgnorePrefixUrls.Add("/api/dont/wrap-result-test"); + + options.IgnoreExceptions.Add(); + }); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddBaseTypes( + typeof(AbpUiResource), + typeof(AbpValidationResource) + ).AddVirtualJson("/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources"); + + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + }); + + Configure(options => + { + options.RootDirectory = "/LINGYUN/Abp/AspNetCore/Mvc"; + }); + + Configure(options => + { + + }); + + Configure(options => + { + options.ErrorCodeNamespaceMappings.Add("Test", typeof(MvcTestResource)); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + + app.UseCorrelationId(); + app.UseStaticFiles(); + app.UseAbpRequestLocalization(); + app.UseAbpSecurityHeaders(); + app.UseRouting(); + app.UseAbpClaimsMap(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseAuditing(); + app.UseUnitOfWork(); + app.UseConfiguredEndpoints(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/GlobalFeatures/AbpAspNetCoreMvcTestFeatures.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/GlobalFeatures/AbpAspNetCoreMvcTestFeatures.cs new file mode 100644 index 000000000..0d6cb077e --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/GlobalFeatures/AbpAspNetCoreMvcTestFeatures.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Volo.Abp.GlobalFeatures; + +namespace LINGYUN.Abp.AspNetCore.Mvc.GlobalFeatures +{ + public class AbpAspNetCoreMvcTestFeatures : GlobalModuleFeatures + { + public const string ModuleName = "AbpAspNetCoreMvcTest"; + + public AbpAspNetCoreMvcTestFeatures([NotNull] GlobalFeatureManager featureManager) + : base(featureManager) + { + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/MvcTestResource.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/MvcTestResource.cs new file mode 100644 index 000000000..70198669f --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/MvcTestResource.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.AspNetCore.Mvc.Localization +{ + public class MvcTestResource + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources/en.json b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources/en.json new file mode 100644 index 000000000..26494eab9 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Test:1001": "Test the wrapped exception message." + } +} \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources/zh-Hans.json b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..3d32e1e5f --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Test:1001": "测试包装后的异常消息." + } +} \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/DontWrapResultController.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/DontWrapResultController.cs new file mode 100644 index 000000000..7169af056 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/DontWrapResultController.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Results +{ + [Route("api/dont/wrap-result-test")] + public class DontWrapResultController: AbpController + { + [HttpGet] + public TestResultObject Wrap() + { + return new TestResultObject + { + Id = Guid.NewGuid(), + DateTime = Clock.Now, + Double = 3.141592653d, + Integer = 100, + Name = "Not Wrap", + Properties = new Dictionary + { + { "TestKey", "TestValue" } + } + }; + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/HasDbException.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/HasDbException.cs new file mode 100644 index 000000000..dde9ff3fb --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/HasDbException.cs @@ -0,0 +1,8 @@ +using System.Data.Common; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Results +{ + public class HasDbException: DbException + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/TestResultObject.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/TestResultObject.cs new file mode 100644 index 000000000..92f22da33 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/TestResultObject.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Results +{ + public class TestResultObject + { + public Guid Id { get; set; } + public string Name { get; set; } + public DateTime DateTime { get; set; } + public int Integer { get; set; } + public double Double { get; set; } + public Dictionary Properties { get; set; } + public TestResultObject() { } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/WrapResultController.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/WrapResultController.cs new file mode 100644 index 000000000..62eb03c99 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/WrapResultController.cs @@ -0,0 +1,78 @@ +using LINGYUN.Abp.AspNetCore.Mvc.Localization; +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Data.Common; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Results +{ + [Route("api/wrap-result-test")] + public class WrapResultController: AbpController + { + public WrapResultController() + { + LocalizationResource = typeof(MvcTestResource); + } + + [HttpGet] + [Route("exception")] + public TestResultObject WrapBusinessException() + { + throw new BusinessException("Test:1001"); + } + + [HttpGet] + [Route("wrap")] + public TestResultObject Wrap() + { + return new TestResultObject + { + Id = Guid.NewGuid(), + DateTime = Clock.Now, + Double = 3.141592653d, + Integer = 100, + Name = "Wrap", + Properties = new Dictionary + { + { "TestKey", "TestValue" } + } + }; + } + + [HttpGet] + [Route("wrap-empty")] + public TestResultObject WrapEmpty() + { + return null; + } + + [HttpGet] + [Route("not-wrap")] + [IgnoreWrapResult] + public TestResultObject NotWrap() + { + return new TestResultObject + { + Id = Guid.NewGuid(), + DateTime = Clock.Now, + Double = 3.141592653d, + Integer = 100, + Name = "Not Wrap", + Properties = new Dictionary + { + { "TestKey", "TestValue" } + } + }; + } + + [HttpGet] + [Route("not-wrap-exception")] + public TestResultObject NotWrapHasDbException() + { + throw new HasDbException(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/WrapResultController_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/WrapResultController_Tests.cs new file mode 100644 index 000000000..b33924693 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Results/WrapResultController_Tests.cs @@ -0,0 +1,93 @@ +using LINGYUN.Abp.Wrapper; +using Shouldly; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.Http; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Localization; +using Xunit; + +namespace LINGYUN.Abp.AspNetCore.Mvc.Results +{ + public class WrapResultController_Tests : AbpAspNetCoreMvcTestBase + { + [Fact] + public async Task Should_Return_Not_Wrap_Result_For_Abp_Api_Definition() + { + var result = await GetResponseAsObjectAsync("/api/abp/api-definition"); + result.ShouldNotBeNull(); + } + + [Fact] + public async Task Should_Return_Not_Wrap_Result_For_Return_Application_Configuration_Dto() + { + var result = await GetResponseAsObjectAsync("/api/abp/application-configuration"); + + result.ShouldNotBeNull(); + } + + [Fact] + public async Task Should_Return_Not_Wrap_Result_For_Url_Prefix_With_Dont_Wrapper() + { + var result = await GetResponseAsObjectAsync("/api/dont/wrap-result-test"); + result.ShouldNotBeNull(); + result.Name.ShouldBe("Not Wrap"); + } + + [Fact] + public async Task Should_Return_Wrap_Result_For_Bussiness_Exception() + { + using (CultureHelper.Use("zh-Hans")) + { + var result = await GetResponseAsObjectAsync>("/api/wrap-result-test/exception"); + result.ShouldNotBeNull(); + result.Code.ShouldBe("1001"); + result.Message.ShouldBe("测试包装后的异常消息."); + } + + using (CultureHelper.Use("en")) + { + var result = await GetResponseAsObjectAsync>("/api/wrap-result-test/exception"); + result.ShouldNotBeNull(); + result.Code.ShouldBe("1001"); + result.Message.ShouldBe("Test the wrapped exception message."); + } + } + + [Fact] + public async Task Should_Return_Not_Wrap_Result_For_Has_Db_Exception() + { + using (CultureHelper.Use("zh-Hans")) + { + var result = await GetResponseAsObjectAsync("/api/wrap-result-test/not-wrap-exception", System.Net.HttpStatusCode.InternalServerError); + result.ShouldNotBeNull(); + result.Error.ShouldNotBeNull(); + result.Error.Message.ShouldBe("对不起,在处理你的请求期间,产生了一个服务器内部错误!"); + } + } + + [Fact] + public async Task Should_Return_Wrap_Result_For_Object_Return_Value() + { + var result = await GetResponseAsObjectAsync>("/api/wrap-result-test/wrap"); + result.ShouldNotBeNull(); + result.Result.Name.ShouldBe("Wrap"); + } + + [Fact] + public async Task Should_Return_Wrap_Result_For_Empty_Object_Return_Value() + { + var result = await GetResponseAsObjectAsync>("/api/wrap-result-test/wrap-empty"); + result.Code.ShouldBe("404"); + result.Result.ShouldBeNull(); + } + + [Fact] + public async Task Should_Return_Not_Wrap_Result_For_Object_Return_Value() + { + var result = await GetResponseAsObjectAsync("/api/wrap-result-test/not-wrap"); + result.ShouldNotBeNull(); + result.Name.ShouldBe("Not Wrap"); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Startup.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Startup.cs new file mode 100644 index 000000000..3dd4f6f55 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/LINGYUN/Abp/AspNetCore/Mvc/Startup.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace LINGYUN.Abp.AspNetCore.Mvc +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/Properties/launchSettings.json b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..858625d8c --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Mvc.Tests/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53117/", + "sslPort": 44307 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "LINGYUN.Abp.AspNetCore.Mvc.Tests": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN.Abp.AspNetCore.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN.Abp.AspNetCore.Tests.csproj new file mode 100644 index 000000000..3470faba6 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN.Abp.AspNetCore.Tests.csproj @@ -0,0 +1,25 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/AbpAspNetCoreTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/AbpAspNetCoreTestBase.cs new file mode 100644 index 000000000..90217b80c --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/AbpAspNetCoreTestBase.cs @@ -0,0 +1,49 @@ +using Microsoft.Net.Http.Headers; +using Shouldly; +using System.Globalization; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.TestBase; + +namespace LINGYUN.Abp.AspNetCore +{ + public class AbpAspNetCoreTestBase : AbpAspNetCoreTestBase + { + + } + + public abstract class AbpAspNetCoreTestBase : AbpAspNetCoreIntegratedTestBase + where TStartup : class + { + protected virtual async Task GetResponseAsObjectAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + { + var strResponse = await GetResponseAsStringAsync(url, expectedStatusCode); + return JsonSerializer.Deserialize(strResponse, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + } + + protected virtual async Task GetResponseAsStringAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + { + using (var response = await GetResponseAsync(url, expectedStatusCode)) + { + return await response.Content.ReadAsStringAsync(); + } + } + + protected virtual async Task GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK, bool xmlHttpRequest = false) + { + using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, url)) + { + requestMessage.Headers.Add("Accept-Language", CultureInfo.CurrentUICulture.Name); + if (xmlHttpRequest) + { + requestMessage.Headers.Add(HeaderNames.XRequestedWith, "XMLHttpRequest"); + } + var response = await Client.SendAsync(requestMessage); + response.StatusCode.ShouldBe(expectedStatusCode); + return response; + } + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/AbpAspNetCoreTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/AbpAspNetCoreTestModule.cs new file mode 100644 index 000000000..52e997920 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/AbpAspNetCoreTestModule.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.AspNetCore; +using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.AspNetCore +{ + [DependsOn( + typeof(AbpAspNetCoreTestBaseModule), + typeof(AbpAspNetCoreModule), + typeof(AbpAutofacModule) + )] + public class AbpAspNetCoreTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + Configure(options => + { + options.FileSets.AddEmbedded(); + //options.FileSets.ReplaceEmbeddedByPhysical(FindProjectPath(hostingEnvironment)); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + + app.UseCorrelationId(); + app.UseStaticFiles(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/Startup.cs b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/Startup.cs new file mode 100644 index 000000000..b19e55b0b --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.AspNetCore.Tests/LINGYUN/Abp/AspNetCore/Startup.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace LINGYUN.Abp.AspNetCore +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } + } +}