Best approach for a middleware in an application running jobs

162 Views Asked by At

I have a service that calls endpoints from a external API. I developed a custom middleware for handling possible exceptions from these API calls and logging them. However, although the middleware includes a try and catch block in the Invoke method, my application's flow is being interrupted after an Exception is caught by the catch block. The flow simply ends, although the application keeps running. As this application is executing jobs that sync data, I need that the flow continues (and possible exceptions be logged, as I mentioned).

Anyone has already experienced this or have any clue how to make the flow go on? Is there a better approach for doing this? I'm totally open for suggestions, please.

This is my code:

public class ApiException
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<ApiException> _logger;
        private readonly ApiExceptionOptions _options;

        public ApiException(ApiExceptionOptions options, RequestDelegate next,
            ILogger<ApiException> logger)
        {
            _next = next;
            _logger = logger;
            _options = options;
        }

        public async Task Invoke(HttpContext context /* other dependencies */)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }

        private Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            ApiError error = _options.AddResponseDetails?.Invoke(exception)
                ?? ApiErrorFactory.New(exception);

            LogApiException(exception, error);

            return CreateResponse(context, error);
        }

        private static Task CreateResponse(HttpContext context, ApiError error)
        {
            var result = JsonSerializer.Serialize(error,
                new JsonSerializerOptions
                {
                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                });
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)error.Status;
            return context.Response.WriteAsync(result);
        }

        private void LogApiException(Exception exception, ApiError error)
        {
            var innerExMessage = GetInnermostExceptionMessage(exception);
            var level = _options.DetermineLogLevel?.Invoke(exception) ?? GetLogLevel(exception);
            _logger.Log(level, exception, "ERROR: {message} -- {ErrorId}.", innerExMessage, error.Id);
        }

        private static LogLevel GetLogLevel(Exception exception) =>
            exception switch
            {
                InvalidInputException _ => LogLevel.Warning,
                _ => LogLevel.Error
            };

        private string GetInnermostExceptionMessage(Exception exception)
        {
            if (exception.InnerException != null)
                return GetInnermostExceptionMessage(exception.InnerException);

            return exception.Message;
        }
    }

public class ApiExceptionOptions
    {        
        public Func<Exception, ApiError> AddResponseDetails { get; set; }
        
        public Func<Exception, LogLevel> DetermineLogLevel { get; set; }
    }
public static class ApiErrorFactory
    {        
        public static ApiError New(Exception exception) =>
            exception switch
            {
                InvalidInputException e => new ApiError
                {
                    Details = e.Details,
                    Message = e.Message,
                    Status = BadRequest
                },
                _ => new ApiError()
            };
    }
public class ApiError
    {
        public static string DefaultErrorMessage = "Erro no processamento da requisição.";

        public string Id { get; } = Guid.NewGuid().ToString();
        public HttpStatusCode Status { get; set; } = HttpStatusCode.InternalServerError;
        public string Title { get; set; } = "API Error";
        public string Message { get; set; } = DefaultErrorMessage;
        public IDictionary<string, object> Details { get; set; } = new Dictionary<string, object>();
    }
public static class ApiExceptionExtensions
    {        
        public static IApplicationBuilder UseApiExceptionHandler(this IApplicationBuilder builder)
        {
            var options = new ApiExceptionOptions();
            return builder.UseMiddleware<ApiException>(options);
        }
        
        public static IApplicationBuilder UseApiExceptionHandler(this IApplicationBuilder builder,
            Action<ApiExceptionOptions> configureOptions)
        {
            var options = new ApiExceptionOptions();
            configureOptions(options);

            return builder.UseMiddleware<ApiException>(options);
        }
    }

0

There are 0 best solutions below