Merge pull request #3334 from abpframework/exception-notification

Implemented: Allow to subscribe to exceptions handled by the abp framework
pull/3336/head
Halil İbrahim Kalkan 5 years ago committed by GitHub
commit 5799914c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -291,6 +291,26 @@ services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
});
````
## Subscribing to the Exceptions
It is possible to be informed when the ABP Framework **handles an exception**. It automatically **logs** all the exceptions to the standard [logger](Logging.md), but you may want to do more.
In this case, create a class derived from the `ExceptionSubscriber` class in your application:
````csharp
public class MyExceptionSubscriber : ExceptionSubscriber
{
public override async Task HandleAsync(ExceptionNotificationContext context)
{
//TODO...
}
}
````
The `context` object contains necessary information about the exception occurred.
> You can have multiple subscribers, each gets a copy of the exception. Exceptions thrown by your subscriber is ignored (but still logged).
## Built-In Exceptions
Some exception types are automatically thrown by the framework:

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
@ -7,6 +8,7 @@ using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Views.Error;
using Volo.Abp.ExceptionHandling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
{
@ -16,20 +18,23 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder;
private readonly IStringLocalizer<AbpUiResource> _localizer;
private readonly AbpErrorPageOptions _abpErrorPageOptions;
private readonly IExceptionNotifier _exceptionNotifier;
public ErrorController(
IExceptionToErrorInfoConverter exceptionToErrorInfoConverter,
IHttpExceptionStatusCodeFinder httpExceptionStatusCodeFinder,
IOptions<AbpErrorPageOptions> abpErrorPageOptions,
IStringLocalizer<AbpUiResource> localizer)
IStringLocalizer<AbpUiResource> localizer,
IExceptionNotifier exceptionNotifier)
{
_errorInfoConverter = exceptionToErrorInfoConverter;
_statusCodeFinder = httpExceptionStatusCodeFinder;
_localizer = localizer;
_exceptionNotifier = exceptionNotifier;
_abpErrorPageOptions = abpErrorPageOptions.Value;
}
public IActionResult Index(int httpStatusCode)
public async Task<IActionResult> Index(int httpStatusCode)
{
var exHandlerFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();
@ -37,6 +42,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
? exHandlerFeature.Error
: new Exception(_localizer["UnhandledException"]);
await _exceptionNotifier.NotifyAsync(new ExceptionNotificationContext(exception));
var errorInfo = _errorInfoConverter.Convert(exception);
if (httpStatusCode == 0)

@ -1,18 +1,21 @@
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 AbpExceptionFilter : IExceptionFilter, ITransientDependency
public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency
{
public ILogger<AbpExceptionFilter> Logger { get; set; }
@ -32,14 +35,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
Logger = NullLogger<AbpExceptionFilter>.Instance;
}
public virtual void OnException(ExceptionContext context)
public async Task OnExceptionAsync(ExceptionContext context)
{
if (!ShouldHandleException(context))
{
return;
}
HandleAndWrapException(context);
await HandleAndWrapException(context);
}
protected virtual bool ShouldHandleException(ExceptionContext context)
@ -65,7 +68,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
return false;
}
protected virtual void HandleAndWrapException(ExceptionContext context)
protected virtual async Task HandleAndWrapException(ExceptionContext context)
{
//TODO: Trigger an AbpExceptionHandled event or something like that.
@ -82,6 +85,13 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
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!
}
}

@ -7,6 +7,7 @@ using Microsoft.Net.Http.Headers;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http;
using Volo.Abp.Json;
@ -73,6 +74,13 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
)
)
);
await httpContext
.RequestServices
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(
new ExceptionNotificationContext(exception)
);
}
private Task ClearCacheHeaders(object state)

@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
namespace Volo.Abp.BackgroundJobs
{
@ -51,6 +53,10 @@ namespace Volo.Abp.BackgroundJobs
{
Logger.LogException(ex);
await context.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex));
throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex)
{
JobType = context.JobType.AssemblyQualifiedName,

@ -9,6 +9,7 @@ using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.RabbitMQ;
using Volo.Abp.Threading;
@ -31,6 +32,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
protected IRabbitMqSerializer Serializer { get; }
protected IBackgroundJobExecuter JobExecuter { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected IExceptionNotifier ExceptionNotifier { get; }
protected SemaphoreSlim SyncObj = new SemaphoreSlim(1, 1);
protected bool IsDiposed { get; private set; }
@ -41,13 +43,15 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
IChannelPool channelPool,
IRabbitMqSerializer serializer,
IBackgroundJobExecuter jobExecuter,
IServiceScopeFactory serviceScopeFactory)
IServiceScopeFactory serviceScopeFactory,
IExceptionNotifier exceptionNotifier)
{
AbpBackgroundJobOptions = backgroundJobOptions.Value;
AbpRabbitMqBackgroundJobOptions = rabbitMqAbpBackgroundJobOptions.Value;
Serializer = serializer;
JobExecuter = jobExecuter;
ServiceScopeFactory = serviceScopeFactory;
ExceptionNotifier = exceptionNotifier;
ChannelPool = channelPool;
JobConfiguration = AbpBackgroundJobOptions.GetJob(typeof(TArgs));

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundWorkers
@ -35,18 +36,24 @@ namespace Volo.Abp.BackgroundWorkers
private void Timer_Elapsed(object sender, System.EventArgs e)
{
try
using (var scope = ServiceScopeFactory.CreateScope())
{
using (var scope = ServiceScopeFactory.CreateScope())
try
{
AsyncHelper.RunSync(
() => DoWorkAsync(new PeriodicBackgroundWorkerContext(scope.ServiceProvider))
);
}
}
catch (Exception ex)
{
Logger.LogException(ex);
catch (Exception ex)
{
AsyncHelper.RunSync(
() => scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex))
);
Logger.LogException(ex);
}
}
}

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundWorkers
@ -38,16 +39,21 @@ namespace Volo.Abp.BackgroundWorkers
private void Timer_Elapsed(object sender, System.EventArgs e)
{
try
using (var scope = ServiceScopeFactory.CreateScope())
{
using (var scope = ServiceScopeFactory.CreateScope())
try
{
DoWork(new PeriodicBackgroundWorkerContext(scope.ServiceProvider));
}
}
catch (Exception ex)
{
Logger.LogException(ex);
catch (Exception ex)
{
scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex));
Logger.LogException(ex);
}
}
}

@ -2,10 +2,13 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
@ -23,12 +26,14 @@ namespace Volo.Abp.Caching
IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer,
IDistributedCacheKeyNormalizer keyNormalizer) : base(
IDistributedCacheKeyNormalizer keyNormalizer,
IHybridServiceScopeFactory serviceScopeFactory) : base(
distributedCacheOption: distributedCacheOption,
cache: cache,
cancellationTokenProvider: cancellationTokenProvider,
serializer: serializer,
keyNormalizer: keyNormalizer)
keyNormalizer: keyNormalizer,
serviceScopeFactory: serviceScopeFactory)
{
}
@ -56,6 +61,8 @@ namespace Volo.Abp.Caching
protected IDistributedCacheKeyNormalizer KeyNormalizer { get; }
protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
protected SemaphoreSlim SyncSemaphore { get; }
protected DistributedCacheEntryOptions DefaultCacheOptions;
@ -67,7 +74,8 @@ namespace Volo.Abp.Caching
IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer,
IDistributedCacheKeyNormalizer keyNormalizer)
IDistributedCacheKeyNormalizer keyNormalizer,
IHybridServiceScopeFactory serviceScopeFactory)
{
_distributedCacheOption = distributedCacheOption.Value;
Cache = cache;
@ -75,6 +83,7 @@ namespace Volo.Abp.Caching
Logger = NullLogger<DistributedCache<TCacheItem, TCacheKey>>.Instance;
Serializer = serializer;
KeyNormalizer = keyNormalizer;
ServiceScopeFactory = serviceScopeFactory;
SyncSemaphore = new SemaphoreSlim(1, 1);
@ -139,7 +148,7 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return null;
}
@ -181,7 +190,7 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
await HandleExceptionAsync(ex);
return null;
}
@ -298,7 +307,7 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return;
}
@ -337,7 +346,7 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
await HandleExceptionAsync(ex);
return;
}
@ -364,7 +373,7 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return;
}
@ -393,7 +402,7 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
await HandleExceptionAsync(ex);
return;
}
@ -420,7 +429,8 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return;
}
throw;
@ -449,12 +459,24 @@ namespace Volo.Abp.Caching
{
if (hideErrors == true)
{
Logger.LogException(ex, LogLevel.Warning);
await HandleExceptionAsync(ex);
return;
}
throw;
}
}
protected virtual async Task HandleExceptionAsync(Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
using (var scope = ServiceScopeFactory.CreateScope())
{
await scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning));
}
}
}
}

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Volo.Abp.DependencyInjection
{
@ -17,10 +16,11 @@ namespace Volo.Abp.DependencyInjection
public static List<Type> GetExposedServices(Type type)
{
return type
.GetCustomAttributes()
.GetCustomAttributes(true)
.OfType<IExposedServiceTypesProvider>()
.DefaultIfEmpty(DefaultExposeServicesAttribute)
.SelectMany(p => p.GetExposedServiceTypes(type))
.Distinct()
.ToList();
}
}

@ -0,0 +1,32 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.ExceptionHandling
{
public class ExceptionNotificationContext
{
/// <summary>
/// The exception object.
/// </summary>
[NotNull]
public Exception Exception { get; }
public LogLevel LogLevel { get; }
/// <summary>
/// True, if it is handled.
/// </summary>
public bool Handled { get; }
public ExceptionNotificationContext(
[NotNull] Exception exception,
LogLevel? logLevel = null,
bool handled = true)
{
Exception = Check.NotNull(exception, nameof(exception));
LogLevel = logLevel ?? exception.GetLogLevel();
Handled = handled;
}
}
}

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.ExceptionHandling
{
public class ExceptionNotifier : IExceptionNotifier, ITransientDependency
{
public ILogger<ExceptionNotifier> Logger { get; set; }
protected IEnumerable<IExceptionSubscriber> ExceptionSubscribers { get; }
public ExceptionNotifier(IEnumerable<IExceptionSubscriber> exceptionSubscribers)
{
ExceptionSubscribers = exceptionSubscribers;
Logger = NullLogger<ExceptionNotifier>.Instance;
}
public virtual async Task NotifyAsync([NotNull] ExceptionNotificationContext context)
{
Check.NotNull(context, nameof(context));
foreach (var exceptionSubscriber in ExceptionSubscribers)
{
try
{
await exceptionSubscriber.HandleAsync(context);
}
catch (Exception e)
{
Logger.LogWarning($"Exception subscriber of type {exceptionSubscriber.GetType().AssemblyQualifiedName} has thrown an exception!");
Logger.LogException(e, LogLevel.Warning);
}
}
}
}
}

@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.ExceptionHandling
{
public static class ExceptionNotifierExtensions
{
public static Task NotifyAsync(
[NotNull] this IExceptionNotifier exceptionNotifier,
[NotNull] Exception exception,
LogLevel? logLevel = null,
bool handled = true)
{
Check.NotNull(exceptionNotifier, nameof(exceptionNotifier));
return exceptionNotifier.NotifyAsync(
new ExceptionNotificationContext(
exception,
logLevel,
handled
)
);
}
}
}

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.ExceptionHandling
{
[ExposeServices(typeof(IExceptionSubscriber))]
public abstract class ExceptionSubscriber : IExceptionSubscriber, ITransientDependency
{
public abstract Task HandleAsync(ExceptionNotificationContext context);
}
}

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.ExceptionHandling
{
public interface IExceptionNotifier
{
Task NotifyAsync([NotNull] ExceptionNotificationContext context);
}
}

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.ExceptionHandling
{
public interface IExceptionSubscriber
{
Task HandleAsync([NotNull] ExceptionNotificationContext context);
}
}

@ -3,7 +3,9 @@ using Microsoft.Extensions.Options;
using Novell.Directory.Ldap;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Ldap.Exceptions;
using Volo.Abp.Ldap.Modeling;
@ -13,6 +15,7 @@ namespace Volo.Abp.Ldap
{
private readonly string _searchBase;
private readonly AbpLdapOptions _ldapOptions;
private readonly IHybridServiceScopeFactory _hybridServiceScopeFactory;
private readonly string[] _attributes =
{
@ -21,8 +24,9 @@ namespace Volo.Abp.Ldap
"sAMAccountName", "userPrincipalName", "telephoneNumber", "mail"
};
public LdapManager(IOptions<AbpLdapOptions> ldapSettingsOptions)
public LdapManager(IOptions<AbpLdapOptions> ldapSettingsOptions, IHybridServiceScopeFactory hybridServiceScopeFactory)
{
_hybridServiceScopeFactory = hybridServiceScopeFactory;
_ldapOptions = ldapSettingsOptions.Value;
_searchBase = _ldapOptions.SearchBase;
}
@ -231,8 +235,15 @@ namespace Volo.Abp.Ldap
return true;
}
}
catch (Exception )
catch (Exception ex)
{
using (var scope = _hybridServiceScopeFactory.CreateScope())
{
scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(ex);
}
return false;
}
}

@ -7,6 +7,7 @@ using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Threading;
namespace Volo.Abp.RabbitMQ
@ -17,6 +18,8 @@ namespace Volo.Abp.RabbitMQ
protected IConnectionPool ConnectionPool { get; }
protected IExceptionNotifier ExceptionNotifier { get; }
protected AbpTimer Timer { get; }
protected ExchangeDeclareConfiguration Exchange { get; private set; }
@ -35,10 +38,12 @@ namespace Volo.Abp.RabbitMQ
public RabbitMqMessageConsumer(
IConnectionPool connectionPool,
AbpTimer timer)
AbpTimer timer,
IExceptionNotifier exceptionNotifier)
{
ConnectionPool = connectionPool;
Timer = timer;
ExceptionNotifier = exceptionNotifier;
Logger = NullLogger<RabbitMqMessageConsumer>.Instance;
QueueBindCommands = new ConcurrentQueue<QueueBindCommand>();
@ -114,6 +119,7 @@ namespace Volo.Abp.RabbitMQ
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning));
}
}
@ -180,6 +186,7 @@ namespace Volo.Abp.RabbitMQ
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning));
}
}
@ -197,6 +204,7 @@ namespace Volo.Abp.RabbitMQ
catch (Exception ex)
{
Logger.LogException(ex);
await ExceptionNotifier.NotifyAsync(ex);
}
}
@ -214,6 +222,7 @@ namespace Volo.Abp.RabbitMQ
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning));
}
}

@ -3,11 +3,12 @@ using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
namespace Volo.Abp.Threading
{
/// <summary>
/// A roboust timer implementation that ensures no overlapping occurs. It waits exactly specified <see cref="Period"/> between ticks.
/// A robust timer implementation that ensures no overlapping occurs. It waits exactly specified <see cref="Period"/> between ticks.
/// </summary>
public class AbpTimer : ITransientDependency
{
@ -29,15 +30,23 @@ namespace Volo.Abp.Threading
public ILogger<AbpTimer> Logger { get; set; }
protected IExceptionNotifier ExceptionNotifier { get; }
private readonly Timer _taskTimer;
private volatile bool _performingTasks;
private volatile bool _isRunning;
public AbpTimer()
public AbpTimer(IExceptionNotifier exceptionNotifier)
{
ExceptionNotifier = exceptionNotifier;
Logger = NullLogger<AbpTimer>.Instance;
_taskTimer = new Timer(TimerCallBack, null, Timeout.Infinite, Timeout.Infinite);
_taskTimer = new Timer(
TimerCallBack,
null,
Timeout.Infinite,
Timeout.Infinite
);
}
public void Start(CancellationToken cancellationToken = default)
@ -89,9 +98,10 @@ namespace Volo.Abp.Threading
{
Elapsed.InvokeSafely(this, new EventArgs());
}
catch
catch(Exception ex)
{
Logger.LogException(ex);
AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex));
}
finally
{

@ -1,6 +1,10 @@
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;
@ -8,12 +12,27 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{
public class ExceptionTestController_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>("/api/exception-test/UserFriendlyException1", HttpStatusCode.Forbidden);
result.Error.ShouldNotBeNull();
result.Error.Message.ShouldBe("This is a sample exception!");
_fakeExceptionSubscriber
.Received()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
}
[Fact]
@ -24,6 +43,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
"/api/exception-test/UserFriendlyException2"
)
);
_fakeExceptionSubscriber
.DidNotReceive()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
}
}
}

Loading…
Cancel
Save