Home > Enterprise >  .net5 exception handler does not respect inheriting rules?
.net5 exception handler does not respect inheriting rules?

Time:07-29

In a .net5 web service, I created an ErrorHandlerMiddleware in order to process and log the exceptions, before they are sent to consumers.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;


namespace RetailWebAgent.Extensions
{
    public class ErrorHandlerMiddleware
    {
        private readonly RequestDelegate _next;

        public ErrorHandlerMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception error)
            {
                context.Response.ContentType = "text/plain";

                switch (error)
                {
                    case KeyNotFoundException e:
                        // not found error
                        context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                        break;

                    case Oracle.ManagedDataAccess.Client.OracleException e:
                        await HandleOracleException(context, e);
                        return;

                    case mom.common.Exceptions.MomWebException e:
                        await HandleMomExceptionAsync(context, e);
                        return;

                    default:
                        var sContent = error.Message;
                        sContent  = Environment.NewLine   Environment.NewLine   "Stacktrace:"   Environment.NewLine   error.StackTrace;

                        await mom.Oracle.OraLogger.AppendLogAsync("Internal error", sContent, null);

                        // unhandled error
                        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                        break;
                }

                await context.Response.WriteAsync(error.Message);
            }
        }

        private async Task HandleOracleException(HttpContext context, Oracle.ManagedDataAccess.Client.OracleException e)
        {
            // Αποθήκευση του exception
            mom.OracleCommon.OraLogActions.SaveException(e);

            if (mom.OracleCommon.OracleSrv.IsUserDefined(e))
            {
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                await context.Response.WriteAsync(mom.OracleCommon.OracleSrv.GetErrorMessage(e.Message));
            }
            else
            {
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                await context.Response.WriteAsync(e.Message);
            }
        }

        private async Task HandleMomExceptionAsync(HttpContext context, mom.common.Exceptions.MomWebException e)
        {
            // Αποθήκευση του exception
            if (e.LogMessage)
                await e.SaveExceptionAsync();

            // Διαμόρφωση του error response
            context.Response.ContentType = e.ResponseContentType;
            context.Response.StatusCode = (int)e.StatusCode;

            // Αν υπάρχει response body το προτιμάμε
            if (!string.IsNullOrEmpty(e.ResponseBody))
                await context.Response.WriteAsync(e.ResponseBody);

            // Διαφορετικά το message, το οποίο είναι συνήθως απλό text
            else
                await context.Response.WriteAsync(e.Message);
        }
    }

}

I created a special Exception class with the member function SaveExceptionAsync() and I call this function in ErrorHandlerMiddleware.

public class MomWebException : Exception
    {
        private HttpStatusCode _statusCode = HttpStatusCode.InternalServerError;
        private bool _LogMsg = true;

        public string LogTitle { get; set; }
        public string LogHandle { get; set; }

        public string ResponseBody { get; set; }

        public string ResponseContentType { get { return "text/plain"; } }

        public string LogContent { get; set; }


        public bool LogMessage
        {
            get { return _LogMsg; }
            set { _LogMsg = value; }
        }

        public HttpStatusCode StatusCode
        {
            get { return _statusCode; }
            set { _statusCode = value; }
        }

        public MomWebException(HttpStatusCode statusCode, string msg) : base(msg)
        {
            _statusCode = statusCode;
        }

        public MomWebException(string msg) : base(msg) { }


        public string GetLogContent()
        {
            if (!string.IsNullOrEmpty(LogContent))
                return LogContent;

            string sContent = this.Message   Environment.NewLine;
            
            // Web response
            if (!string.IsNullOrEmpty(ResponseBody))
            {
                sContent  = "Response:"   Environment.NewLine   ResponseBody   Environment.NewLine;
            }

            sContent  = Environment.NewLine;
            sContent  = "Trace:"   Environment.NewLine   StackTrace;

            return sContent;
        }


        public async Task SaveExceptionAsync()
        {
            try
            {
                await OraLogger.AppendLogAsync(LogTitle, GetLogContent(), LogHandle);
            }catch(OracleException e)
            {
                FileLogger.AppendText(e.Message);
                FileLogger.AppendText(this.Message);
            }
        }
    }

I created a descendant that replaces SaveExceptionAsync() but that method is never used in ErrorHandlerMiddleware. This is the new SaveExceptionAsync() that I want to be used.

  public new async Task SaveExceptionAsync()
    {
        using var c = OracleSrv.NewConnection();
        using var t = c.BeginTransaction();
        using var cmd = OracleSrv.NewCommandSP(c, "repl$invoice.log_error_remote");

        cmd.Transaction = t;

        cmd.Parameters.Add(OracleSrv.NewParameter("p_shop_id", shopId));
        cmd.Parameters.Add(OracleSrv.NewParameter("p_inv_id", invId));
        cmd.Parameters.Add(OracleSrv.NewParameter("p_title", LogTitle));
        cmd.Parameters.Add(OracleSrv.NewParameterClob("p_content", LogContent));

        try
        {
            await cmd.ExecuteNonQueryAsync();

            await t.CommitAsync();
        }
        catch (OracleException e)
        {
            // Σε περίπτωση που αποτύχει η καταγραφή στο event log, θα γίνει καταγραφή στο txt
            FileLogger.AppendText("Αδυναμία καταγραφής σφάλματος στη βάση: ");
            FileLogger.AppendText(e.Message);
            FileLogger.AppendText("Αρχικό σφάλμα:");
            FileLogger.AppendText(this.Message);
        }
    }

When I catch (try..catch) this exception, calling SaveExceptionAsync() produces the desired result. When this exception is handled by the ErrorHandlerMiddleware, it uses the ancestor SaveExceptionAsync().

Any ideas on why ErrorHandlerMiddleware does not respect the inheriting rules?

CodePudding user response:

As fvetsch suggests, all I needed was to use the combination virtual/override, instead of new.

I had to make a deeper search, before asking this question.

  • Related