Browse Source

Exception handler.

pull/492/head
Sebastian 6 years ago
parent
commit
7ff5517d97
  1. 9
      backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs
  2. 30
      backend/src/Squidex.Web/Pipeline/DefaultExceptionHandler.cs
  3. 16
      backend/src/Squidex.Web/Pipeline/IExceptionHandler.cs
  4. 42
      backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs
  5. 7
      backend/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs
  6. 3
      backend/src/Squidex/Config/Web/WebServices.cs
  7. 23
      backend/tests/Squidex.Web.Tests/ApiExceptionFilterAttributeTests.cs

9
backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs

@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure.Log; using Squidex.Web.Pipeline;
namespace Squidex.Web namespace Squidex.Web
{ {
@ -33,12 +33,9 @@ namespace Squidex.Web
if (!wellKnown) if (!wellKnown)
{ {
var log = context.HttpContext.RequestServices.GetService<ISemanticLog>(); var exceptionHandler = context.HttpContext.RequestServices.GetService<IExceptionHandler>();
if (log != null) exceptionHandler?.Handle(context.Exception);
{
log.LogError(context.Exception, w => w.WriteProperty("status", "UnhandledException"));
}
} }
context.Result = GetResult(error); context.Result = GetResult(error);

30
backend/src/Squidex.Web/Pipeline/DefaultExceptionHandler.cs

@ -0,0 +1,30 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
namespace Squidex.Web.Pipeline
{
public class DefaultExceptionHandler : IExceptionHandler
{
private readonly ISemanticLog log;
public DefaultExceptionHandler(ISemanticLog log)
{
Guard.NotNull(log);
this.log = log;
}
public void Handle(Exception ex)
{
log.LogError(ex, w => w.WriteProperty("status", "UnhandledException"));
}
}
}

16
backend/src/Squidex.Web/Pipeline/IExceptionHandler.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Web.Pipeline
{
public interface IExceptionHandler
{
void Handle(Exception exception);
}
}

42
backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Security;
namespace Squidex.Web.Pipeline
{
public sealed class RequestExceptionMiddleware : IMiddleware
{
private readonly IExceptionHandler exceptionHandler;
public RequestExceptionMiddleware(IExceptionHandler exceptionHandler)
{
Guard.NotNull(exceptionHandler);
this.exceptionHandler = exceptionHandler;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
exceptionHandler.Handle(ex);
context.Response.StatusCode = 500;
}
}
}
}

7
backend/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -40,12 +39,6 @@ namespace Squidex.Web.Pipeline
{ {
await next(context); await next(context);
} }
catch (Exception ex)
{
log.LogError(ex, w => w.WriteProperty("status", "UnhandledException"));
context.Response.StatusCode = 500;
}
finally finally
{ {
var elapsedMs = watch.Stop(); var elapsedMs = watch.Stop();

3
backend/src/Squidex/Config/Web/WebServices.cs

@ -61,6 +61,9 @@ namespace Squidex.Config.Web
services.AddSingletonAs<ActionContextAccessor>() services.AddSingletonAs<ActionContextAccessor>()
.As<IActionContextAccessor>(); .As<IActionContextAccessor>();
services.AddSingletonAs<DefaultExceptionHandler>()
.AsOptional<IExceptionHandler>();
services.Configure<ApiBehaviorOptions>(options => services.Configure<ApiBehaviorOptions>(options =>
{ {
options.SuppressModelStateInvalidFilter = true; options.SuppressModelStateInvalidFilter = true;

23
backend/tests/Squidex.Web.Tests/ApiExceptionFilterAttributeTests.cs

@ -19,13 +19,14 @@ using Microsoft.AspNetCore.Routing;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Squidex.Web.Pipeline;
using Xunit; using Xunit;
namespace Squidex.Web namespace Squidex.Web
{ {
public class ApiExceptionFilterAttributeTests public class ApiExceptionFilterAttributeTests
{ {
private readonly ISemanticLog log = A.Fake<ISemanticLog>(); private readonly IExceptionHandler exceptionHandler = A.Fake<IExceptionHandler>();
private readonly ApiExceptionFilterAttribute sut = new ApiExceptionFilterAttribute(); private readonly ApiExceptionFilterAttribute sut = new ApiExceptionFilterAttribute();
[Fact] [Fact]
@ -49,7 +50,7 @@ namespace Squidex.Web
Assert.Equal(new[] { "Error1", "P: Error2", "P1, P2: Error3" }, ((ErrorDto)result.Value).Details); Assert.Equal(new[] { "Error1", "P: Error2", "P1, P2: Error3" }, ((ErrorDto)result.Value).Details);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -62,7 +63,7 @@ namespace Squidex.Web
Assert.IsType<NotFoundResult>(context.Result); Assert.IsType<NotFoundResult>(context.Result);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -75,7 +76,7 @@ namespace Squidex.Web
Validate(500, context.Result, null); Validate(500, context.Result, null);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -88,7 +89,7 @@ namespace Squidex.Web
Validate(400, context.Result, context.Exception); Validate(400, context.Result, context.Exception);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -101,7 +102,7 @@ namespace Squidex.Web
Validate(400, context.Result, context.Exception); Validate(400, context.Result, context.Exception);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -114,7 +115,7 @@ namespace Squidex.Web
Validate(412, context.Result, context.Exception); Validate(412, context.Result, context.Exception);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -127,7 +128,7 @@ namespace Squidex.Web
Validate(403, context.Result, context.Exception); Validate(403, context.Result, context.Exception);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -140,7 +141,7 @@ namespace Squidex.Web
Validate(403, context.Result, null); Validate(403, context.Result, null);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -153,7 +154,7 @@ namespace Squidex.Web
Validate(403, context.Result, null); Validate(403, context.Result, null);
A.CallTo(() => log.Log(SemanticLogLevel.Error, None.Value, A<Action<None, IObjectWriter>>._)) A.CallTo(() => exceptionHandler.Handle(A<Exception>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -190,7 +191,7 @@ namespace Squidex.Web
var services = A.Fake<IServiceProvider>(); var services = A.Fake<IServiceProvider>();
A.CallTo(() => services.GetService(typeof(ISemanticLog))) A.CallTo(() => services.GetService(typeof(ISemanticLog)))
.Returns(log); .Returns(exceptionHandler);
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {

Loading…
Cancel
Save