using Diligent.WebAPI.Contracts.Exceptions; using Diligent.WebAPI.Contracts.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Diagnostics; using System.Net; using System.Runtime.Serialization; namespace Diligent.WebAPI.Host.Middlewares { [ExcludeFromCodeCoverage] public class DiligBadRequestException : Exception { public const string ExceptionMessage = "Bad request from custom middleware"; public const string ExceptionCode = "DiligBadRequestException"; public const HttpStatusCode HttpResponseCode = HttpStatusCode.BadRequest; public DiligBadRequestException() : base(ExceptionMessage) { } public DiligBadRequestException(string message) : base(message) { } public DiligBadRequestException(string message, Exception innerException) : base(message, innerException) { } public DiligBadRequestException(SerializationInfo info, StreamingContext context) : base(info, context) { } } [ExcludeFromCodeCoverage] public class DiligExceptionMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IWebHostEnvironment _env; private readonly ApiBehaviorOptions _options; public DiligExceptionMiddleware(RequestDelegate next, ILogger logger, IWebHostEnvironment env, IOptions options) => (_next, _logger, _env, _options) = (next, logger, env, options.Value); public async Task InvokeAsync(HttpContext httpContext) { try { await _next(httpContext); } // Some custom Exceptions catch (EntityNotFoundException ex) { _logger.LogError(ex, DiligBadRequestException.ExceptionMessage); await HandleExceptionAsync(httpContext, ex, DiligBadRequestException.ExceptionMessage, HttpStatusCode.NotFound); } catch (Exception ex) { // Better solution is to use switch statement if(HasInternalExceptionOfType(ex)) { _logger.LogError(ex, DiligBadRequestException.ExceptionMessage); await HandleExceptionAsync(httpContext, ex, DiligBadRequestException.ExceptionMessage, DiligBadRequestException.HttpResponseCode); } else { await HandleSomethingWentWrongAsync(httpContext, ex); } } } private async Task HandleSomethingWentWrongAsync(HttpContext httpContext, Exception ex) { _logger.LogError(ex, "Something went wrong"); await HandleExceptionAsync(httpContext, ex); } private bool HasInternalExceptionOfType(Exception e) { if(e.InnerException != null) { return e is T || HasInternalExceptionOfType(e.InnerException); } return e is T; } private Task HandleExceptionAsync(HttpContext context, Exception exception, string? message = null, HttpStatusCode? responseCode = null) { context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)(responseCode ?? HttpStatusCode.InternalServerError); string type = null; if (_options.ClientErrorMapping.TryGetValue(context.Response.StatusCode, out var clientErrorData)) { type = clientErrorData.Link; } var errorTitle = message ?? (_env.IsDevelopment() ? exception.Message : "Internal server error"); var errorMessage = _env.IsDevelopment() ? exception.StackTrace : "Something went wrong" ; var serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; var resultError = new ResultError(errorTitle); var httpResultError = new HttpResultError() { ErrorMessage = errorMessage, ErrorCode = responseCode ?? HttpStatusCode.InternalServerError }; resultError.AddCustomError(httpResultError); var responseString = JsonConvert.SerializeObject( new BaseResult { // Type = type, // TraceId = Activity.Current?.Id ?? context?.TraceIdentifier, IsSuccess = false, Errors = { resultError } }, serializerSettings); return context.Response.WriteAsync(responseString); } } [ExcludeFromCodeCoverage] public static class DiligExceptionMiddlewareExtensions { public static IApplicationBuilder UseDiligExceptionHandler(this IApplicationBuilder app) { return app.UseMiddleware(); } } }