mirror of https://github.com/abpframework/abp.git
committed by
GitHub
38 changed files with 998 additions and 19 deletions
@ -0,0 +1,103 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Aspects; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Auditing |
|||
{ |
|||
public class AbpAuditPageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
protected AbpAuditingOptions Options { get; } |
|||
private readonly IAuditingHelper _auditingHelper; |
|||
private readonly IAuditingManager _auditingManager; |
|||
|
|||
public AbpAuditPageFilter(IOptions<AbpAuditingOptions> options, IAuditingHelper auditingHelper, IAuditingManager auditingManager) |
|||
{ |
|||
Options = options.Value; |
|||
_auditingHelper = auditingHelper; |
|||
_auditingManager = auditingManager; |
|||
} |
|||
|
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !ShouldSaveAudit(context, out var auditLog, out var auditLogAction)) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.Auditing)) |
|||
{ |
|||
var stopwatch = Stopwatch.StartNew(); |
|||
|
|||
try |
|||
{ |
|||
var result = await next(); |
|||
|
|||
if (result.Exception != null && !result.ExceptionHandled) |
|||
{ |
|||
auditLog.Exceptions.Add(result.Exception); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
auditLog.Exceptions.Add(ex); |
|||
throw; |
|||
} |
|||
finally |
|||
{ |
|||
stopwatch.Stop(); |
|||
auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); |
|||
auditLog.Actions.Add(auditLogAction); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private bool ShouldSaveAudit(PageHandlerExecutingContext context, out AuditLogInfo auditLog, out AuditLogActionInfo auditLogAction) |
|||
{ |
|||
auditLog = null; |
|||
auditLogAction = null; |
|||
|
|||
if (!Options.IsEnabled) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!context.ActionDescriptor.IsPageAction()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var auditLogScope = _auditingManager.Current; |
|||
if (auditLogScope == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!_auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, true)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
auditLog = auditLogScope.Log; |
|||
auditLogAction = _auditingHelper.CreateAuditLogAction( |
|||
auditLog, |
|||
context.HandlerMethod.GetType(), |
|||
context.HandlerMethod.MethodInfo, |
|||
context.HandlerArguments |
|||
); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.AspNetCore.ExceptionHandling; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ExceptionHandling; |
|||
using Volo.Abp.Http; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling |
|||
{ |
|||
public class AbpExceptionPageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
public ILogger<AbpExceptionPageFilter> Logger { get; set; } |
|||
|
|||
private readonly IExceptionToErrorInfoConverter _errorInfoConverter; |
|||
private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; |
|||
private readonly IJsonSerializer _jsonSerializer; |
|||
|
|||
public AbpExceptionPageFilter( |
|||
IExceptionToErrorInfoConverter errorInfoConverter, |
|||
IHttpExceptionStatusCodeFinder statusCodeFinder, |
|||
IJsonSerializer jsonSerializer) |
|||
{ |
|||
_errorInfoConverter = errorInfoConverter; |
|||
_statusCodeFinder = statusCodeFinder; |
|||
_jsonSerializer = jsonSerializer; |
|||
|
|||
Logger = NullLogger<AbpExceptionPageFilter>.Instance; |
|||
} |
|||
|
|||
|
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !ShouldHandleException(context)) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var pageHandlerExecutedContext = await next(); |
|||
if (pageHandlerExecutedContext.Exception == null) |
|||
{ |
|||
return;; |
|||
} |
|||
|
|||
await HandleAndWrapException(pageHandlerExecutedContext); |
|||
} |
|||
|
|||
protected virtual bool ShouldHandleException(PageHandlerExecutingContext context) |
|||
{ |
|||
//TODO: Create DontWrap attribute to control wrapping..?
|
|||
|
|||
if (context.ActionDescriptor.IsPageAction() && |
|||
ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (context.HttpContext.Request.IsAjax()) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
protected virtual async Task HandleAndWrapException(PageHandlerExecutedContext context) |
|||
{ |
|||
//TODO: Trigger an AbpExceptionHandled event or something like that.
|
|||
|
|||
context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); |
|||
context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception); |
|||
|
|||
var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception); |
|||
|
|||
context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo)); |
|||
|
|||
var logLevel = context.Exception.GetLogLevel(); |
|||
|
|||
Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------"); |
|||
Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); |
|||
Logger.LogException(context.Exception, logLevel); |
|||
|
|||
await context.HttpContext |
|||
.RequestServices |
|||
.GetRequiredService<IExceptionNotifier>() |
|||
.NotifyAsync( |
|||
new ExceptionNotificationContext(context.Exception) |
|||
); |
|||
|
|||
context.Exception = null; //Handled!
|
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Volo.Abp.Aspects; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class AbpFeaturePageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; |
|||
|
|||
public AbpFeaturePageFilter(IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) |
|||
{ |
|||
_methodInvocationAuthorizationService = methodInvocationAuthorizationService; |
|||
} |
|||
|
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction()) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var methodInfo = context.HandlerMethod.MethodInfo; |
|||
|
|||
using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.FeatureChecking)) |
|||
{ |
|||
await _methodInvocationAuthorizationService.CheckAsync( |
|||
new MethodInvocationFeatureCheckerContext(methodInfo) |
|||
); |
|||
|
|||
await next(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
using System; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Uow; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
public class AbpUowPageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly AbpUnitOfWorkDefaultOptions _defaultOptions; |
|||
|
|||
public AbpUowPageFilter(IUnitOfWorkManager unitOfWorkManager, IOptions<AbpUnitOfWorkDefaultOptions> options) |
|||
{ |
|||
_unitOfWorkManager = unitOfWorkManager; |
|||
_defaultOptions = options.Value; |
|||
} |
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction()) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var methodInfo = context.HandlerMethod.MethodInfo; |
|||
var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(methodInfo); |
|||
|
|||
context.HttpContext.Items["_AbpActionInfo"] = new AbpActionInfoInHttpContext |
|||
{ |
|||
IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType) |
|||
}; |
|||
|
|||
if (unitOfWorkAttr?.IsDisabled == true) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var options = CreateOptions(context, unitOfWorkAttr); |
|||
|
|||
//Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware
|
|||
if (_unitOfWorkManager.TryBeginReserved(AbpUnitOfWorkMiddleware.UnitOfWorkReservationName, options)) |
|||
{ |
|||
var result = await next(); |
|||
if (!Succeed(result)) |
|||
{ |
|||
await RollbackAsync(context); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
//Begin a new, independent unit of work
|
|||
using (var uow = _unitOfWorkManager.Begin(options)) |
|||
{ |
|||
var result = await next(); |
|||
if (Succeed(result)) |
|||
{ |
|||
await uow.CompleteAsync(context.HttpContext.RequestAborted); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private AbpUnitOfWorkOptions CreateOptions(PageHandlerExecutingContext context, UnitOfWorkAttribute unitOfWorkAttribute) |
|||
{ |
|||
var options = new AbpUnitOfWorkOptions(); |
|||
|
|||
unitOfWorkAttribute?.SetOptions(options); |
|||
|
|||
if (unitOfWorkAttribute?.IsTransactional == null) |
|||
{ |
|||
options.IsTransactional = _defaultOptions.CalculateIsTransactional( |
|||
autoValue: !string.Equals(context.HttpContext.Request.Method, HttpMethod.Get.Method, StringComparison.OrdinalIgnoreCase) |
|||
); |
|||
} |
|||
|
|||
return options; |
|||
} |
|||
|
|||
private async Task RollbackAsync(PageHandlerExecutingContext context) |
|||
{ |
|||
var currentUow = _unitOfWorkManager.Current; |
|||
if (currentUow != null) |
|||
{ |
|||
await currentUow.RollbackAsync(context.HttpContext.RequestAborted); |
|||
} |
|||
} |
|||
|
|||
private static bool Succeed(PageHandlerExecutedContext result) |
|||
{ |
|||
return result.Exception == null || result.ExceptionHandled; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.DynamicProxy |
|||
{ |
|||
/// <summary>
|
|||
/// Castle's dynamic proxy class feature will have performance issues for some components, such as the controller of Asp net core MVC.
|
|||
/// For related discussions, see: https://github.com/castleproject/Core/issues/486 https://github.com/abpframework/abp/issues/3180
|
|||
/// The Abp framework may enable interceptors for certain components (UOW, Auditing, Authorization, etc.), which requires dynamic proxy classes, but will cause application performance to decline.
|
|||
/// We need to use other methods for the controller to implement interception, such as middleware or MVC / Page filters.
|
|||
/// So we provide some ignored types to avoid enabling dynamic proxy classes.
|
|||
/// By default it is empty. When you use middleware or filters for these components in your application, you can add these types to the list.
|
|||
/// </summary>
|
|||
public static class DynamicProxyIgnoreTypes |
|||
{ |
|||
private static HashSet<Type> IgnoredTypes { get; } = new HashSet<Type>(); |
|||
|
|||
public static void Add<T>() |
|||
{ |
|||
IgnoredTypes.Locking(() => IgnoredTypes.AddIfNotContains(typeof(T))); |
|||
} |
|||
|
|||
public static bool Contains(Type type, bool includeDerivedTypes = true) |
|||
{ |
|||
return includeDerivedTypes ? IgnoredTypes.Any(t => t.IsAssignableFrom(type)) : IgnoredTypes.Contains(type); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Auditing.AuditTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,33 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
using Volo.Abp.Auditing; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Auditing |
|||
{ |
|||
public class AuditTestPage : AbpPageModel |
|||
{ |
|||
private readonly AbpAuditingOptions _options; |
|||
|
|||
public AuditTestPage(IOptions<AbpAuditingOptions> options) |
|||
{ |
|||
_options = options.Value; |
|||
} |
|||
|
|||
public IActionResult OnGetAuditSuccessForGetRequests() |
|||
{ |
|||
return new OkResult(); |
|||
} |
|||
|
|||
public IActionResult OnGetAuditFailForGetRequests() |
|||
{ |
|||
throw new UserFriendlyException("Exception occurred!"); |
|||
} |
|||
|
|||
public ObjectResult OnGetAuditFailForGetRequestsReturningObject() |
|||
{ |
|||
throw new UserFriendlyException("Exception occurred!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using NSubstitute; |
|||
using Volo.Abp.Auditing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Auditing |
|||
{ |
|||
public class AuditTestPage_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
private readonly AbpAuditingOptions _options; |
|||
private IAuditingStore _auditingStore; |
|||
|
|||
public AuditTestPage_Tests() |
|||
{ |
|||
_options = ServiceProvider.GetRequiredService<IOptions<AbpAuditingOptions>>().Value; |
|||
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>(); |
|||
} |
|||
|
|||
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) |
|||
{ |
|||
_auditingStore = Substitute.For<IAuditingStore>(); |
|||
services.Replace(ServiceDescriptor.Singleton(_auditingStore)); |
|||
base.ConfigureServices(context, services); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests() |
|||
{ |
|||
_options.IsEnabledForGetRequests = true; |
|||
_options.AlwaysLogOnException = false; |
|||
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditSuccessForGetRequests"); |
|||
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always() |
|||
{ |
|||
_options.IsEnabled = true; |
|||
_options.AlwaysLogOnException = true; |
|||
|
|||
try |
|||
{ |
|||
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequests", System.Net.HttpStatusCode.Forbidden); |
|||
} |
|||
catch { } |
|||
|
|||
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() |
|||
{ |
|||
_options.IsEnabled = true; |
|||
_options.AlwaysLogOnException = true; |
|||
|
|||
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequestsReturningObject", System.Net.HttpStatusCode.Forbidden); |
|||
|
|||
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Authorization.AuthTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Authorization |
|||
{ |
|||
[Authorize] |
|||
public class AuthTestPage : AbpPageModel |
|||
{ |
|||
public static Guid FakeUserId { get; } = Guid.NewGuid(); |
|||
|
|||
public ActionResult OnGet() |
|||
{ |
|||
return Content("OK"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.MemoryDb; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Security.Claims; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Authorization |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreTestBaseModule), |
|||
typeof(AbpMemoryDbTestModule), |
|||
typeof(AbpAspNetCoreMvcModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class AuthTestPage_Tests: AspNetCoreMvcTestBase |
|||
{ |
|||
private readonly FakeUserClaims _fakeRequiredService; |
|||
|
|||
public AuthTestPage_Tests() |
|||
{ |
|||
_fakeRequiredService = GetRequiredService<FakeUserClaims>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Call_Simple_Authorized_Method_With_Authenticated_User() |
|||
{ |
|||
_fakeRequiredService.Claims.AddRange(new[] |
|||
{ |
|||
new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString()) |
|||
}); |
|||
|
|||
var result = await GetResponseAsStringAsync("/Authorization/AuthTestPage"); |
|||
result.ShouldBe("OK"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.ExceptionHandling.ExceptionTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling |
|||
{ |
|||
public class ExceptionTestPage : AbpPageModel |
|||
{ |
|||
public void OnGetUserFriendlyException1() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public IActionResult OnGetUserFriendlyException2() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using NSubstitute; |
|||
using Shouldly; |
|||
using Volo.Abp.ExceptionHandling; |
|||
using Volo.Abp.Http; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling |
|||
{ |
|||
public class ExceptionTestPage_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
private IExceptionSubscriber _fakeExceptionSubscriber; |
|||
|
|||
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) |
|||
{ |
|||
base.ConfigureServices(context, services); |
|||
|
|||
_fakeExceptionSubscriber = Substitute.For<IExceptionSubscriber>(); |
|||
|
|||
services.AddSingleton(_fakeExceptionSubscriber); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Void_Return_Value() |
|||
{ |
|||
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException1", HttpStatusCode.Forbidden); |
|||
result.Error.ShouldNotBeNull(); |
|||
result.Error.Message.ShouldBe("This is a sample exception!"); |
|||
|
|||
#pragma warning disable 4014
|
|||
_fakeExceptionSubscriber |
|||
.Received() |
|||
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); |
|||
#pragma warning restore 4014
|
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Handle_Exceptions_For_ActionResult_Return_Values() |
|||
{ |
|||
await Assert.ThrowsAsync<UserFriendlyException>( |
|||
async () => await GetResponseAsObjectAsync<RemoteServiceErrorResponse>( |
|||
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException2" |
|||
) |
|||
); |
|||
|
|||
#pragma warning disable 4014
|
|||
_fakeExceptionSubscriber |
|||
.DidNotReceive() |
|||
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); |
|||
#pragma warning restore 4014
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Features.FeatureTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,27 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class FeatureTestPage : AbpPageModel |
|||
{ |
|||
[RequiresFeature("AllowedFeature")] |
|||
public Task OnGetAllowedFeatureAsync() |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
[RequiresFeature("NotAllowedFeature")] |
|||
public void OnGetNotAllowedFeature() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public ObjectResult OnGetNoFeature() |
|||
{ |
|||
return new ObjectResult(42); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class FeatureTestPage_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Allow_Enabled_Features() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/Features/FeatureTestPage?handler=AllowedFeature" |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Allow_Not_Enabled_Features() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/Features/FeatureTestPage?handler=NotAllowedFeature", |
|||
HttpStatusCode.Unauthorized |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Allow_Actions_With_No_Feature() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/Features/FeatureTestPage?handler=NoFeature" |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Http; |
|||
using Volo.Abp.Json; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
public class UnitOfWorkPageFilter_Exception_Rollback_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Rollback_Transaction_For_Handled_Exceptions() |
|||
{ |
|||
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/Uow/UnitOfWorkTestPage?handler=HandledException", HttpStatusCode.Forbidden); |
|||
result.Error.ShouldNotBeNull(); |
|||
result.Error.Message.ShouldBe("This is a sample exception!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Gracefully_Handle_Exceptions_On_Complete() |
|||
{ |
|||
var response = await GetResponseAsync("/Uow/UnitOfWorkTestPage?handler=ExceptionOnComplete", HttpStatusCode.Forbidden); |
|||
|
|||
response.Headers.GetValues(AbpHttpConsts.AbpErrorFormat).FirstOrDefault().ShouldBe("true"); |
|||
|
|||
var resultAsString = await response.Content.ReadAsStringAsync(); |
|||
|
|||
var result = ServiceProvider.GetRequiredService<IJsonSerializer>().Deserialize<RemoteServiceErrorResponse>(resultAsString); |
|||
|
|||
result.Error.ShouldNotBeNull(); |
|||
result.Error.Message.ShouldBe(TestUnitOfWorkConfig.ExceptionOnCompleteMessage); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
public class UnitOfWorkPageFilter_Tests: AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Get_Actions_Should_Not_Be_Transactional() |
|||
{ |
|||
await GetResponseAsStringAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Non_Get_Actions_Should_Be_Transactional() |
|||
{ |
|||
var result = await Client.PostAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow", null); |
|||
result.IsSuccessStatusCode.ShouldBeTrue(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Uow.UnitOfWorkTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,54 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
[IgnoreAntiforgeryToken] |
|||
public class UnitOfWorkTestPage : AbpPageModel |
|||
{ |
|||
private readonly TestUnitOfWorkConfig _testUnitOfWorkConfig; |
|||
|
|||
public UnitOfWorkTestPage(TestUnitOfWorkConfig testUnitOfWorkConfig) |
|||
{ |
|||
_testUnitOfWorkConfig = testUnitOfWorkConfig; |
|||
} |
|||
|
|||
public IActionResult OnGetRequiresUow() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); |
|||
|
|||
return Content("OK"); |
|||
} |
|||
|
|||
public IActionResult OnPostRequiresUow() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); |
|||
|
|||
return Content("OK"); |
|||
} |
|||
|
|||
[UnitOfWork(isTransactional: true)] |
|||
public void OnGetHandledException() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); |
|||
|
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public ObjectResult OnGetExceptionOnComplete() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); |
|||
|
|||
_testUnitOfWorkConfig.ThrowExceptionOnComplete = true; |
|||
|
|||
//Prevent rendering of pages.
|
|||
return new ObjectResult(""); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue