skip to Main Content

I am creating a new API website in .NET8, which is the latest version of ASP.NET Core

In my program.cs I added this:

builder.Services.AddExceptionHandler<ErrorHandler>();
app.UseExceptionHandler("/Home/Error");

In case of an exception this goes indeed to my custom error handling class:

public class ErrorHandler : IExceptionHandler

I access the extra data I need like this:

private readonly ILogger<ErrorHandler> logger;
private readonly IHttpContextAccessor _httpContextAccessor;

public ErrorHandler(ILogger<ErrorHandler> logger, IHttpContextAccessor httpContextAccessor)
{
    this.logger = logger;
    this._httpContextAccessor = httpContextAccessor;
}

So far so good, but now I try to get some data for my log:

  1. Request URL
  2. Form data (RAW)
  3. Client IP Address
  4. Referrer
    private string GetErrorReport(Exception e)
    {
        StringBuilder sbErrorReport = new StringBuilder(1024);
        try
        {
    
            HttpResponse response = _httpContextAccessor.HttpContext?.Response; 
            HttpRequest request = _httpContextAccessor.HttpContext?.Request; 
            string CurUrl = "";
            string CurReferrer = "";
            string formData = "";
            string clientIP = "";
    
            if(request != null)
            {
                CurUrl = request.GetEncodedPathAndQuery();
                CurReferrer = request.Headers["Referer"].ToString();
                if(request.Body != null)            //is this the same as request.Form.ToString()?
                {
                    using (StreamReader sr = new StreamReader(request.Body))
                    {
                        formData = sr.ReadToEnd();
                    }
                }
    
                if(request.HttpContext != null && request.HttpContext.Connection != null && request.HttpContext.Connection.RemoteIpAddress != null)
                {
                    clientIP = request.HttpContext.Connection.RemoteIpAddress.ToString();
                }
            }
    
        
        }
        catch (Exception ex)
        {
            //nothing?
        }
    
        return sbErrorReport.ToString();
    }

None of them seem to work…

CurUrl gives "/Home/Error", not the original URL that had the error.
CurReferrer gives an error about headers not being available.
formData crashes completely.
clientIP actually seems to work, if ::1 is correct for localhost.

Any ideas how to best write errors with all these details to a log file?

2

Answers


  1. Chosen as BEST ANSWER

    After much help of Emre Bener, thank you for that, I figured it out. My mistake was to try to use the IHttpContextAccessor , whilst everything I needed was already in the TryHandleAsync from the IExceptionHandler.

    Here is my code that works: In program.cs:

    builder.Services.AddExceptionHandler<ErrorHandler>();   //custom error log, still needs UseExceptionHandler
    var app = builder.Build();
    app.UseExceptionHandler("/Home/Error");
    

    And in my general exception handling class ErrorHandler.cs:

    public class ErrorHandler : IExceptionHandler
    {
    
        public ErrorHandler()
        {
    
        }
    
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
    
            string errorReport = GetErrorReport(exception, httpContext);
            SaveError(errorReport);
    
            return ValueTask.FromResult(false);
        }
    
    
    
        private string GetErrorReport(Exception e, HttpContext httpContext)
        {
            StringBuilder sbErrorReport = new StringBuilder(1024);
            try
            {
    
                string errorMessage;
                string CurUrl = "";
                string CurReferrer = "";
                string reqBodyRaw = "";
                string clientIP = "";
                string extra = "";
    
                var exceptionHandlerFeatures = httpContext.Features.Get<IExceptionHandlerFeature>();
    
                //error message:
                errorMessage = e.Message;   //also check e.InnerException
    
                //client IP
                if (httpContext.Connection.RemoteIpAddress != null)
                {
                    clientIP = httpContext.Connection.RemoteIpAddress.ToString();
                }
    
                //Referer / headers:
                CurReferrer = httpContext.Request.Headers["Referer"].ToString();
    
    
                //request posted body 
                using (var reader = new StreamReader(httpContext.Request.BodyReader.AsStream(false), Encoding.UTF8))
                {
                    reqBodyRaw = reader.ReadToEnd();
                }
    
                //get the requested URL
                if (exceptionHandlerFeatures != null)
                {
                    //CurUrl = httpContext.Request.Path;  //this does not work, gives the error page path.
                    CurUrl = exceptionHandlerFeatures.Path; //this does work.
                }
    
            // write details to sbErrorReport....
                
            }
            catch (Exception ex)
            {
                //nothing?
            }
    
            return sbErrorReport.ToString();
        }
    }
    

  2. In the error handler action, you can retrieve error details like this:

    var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();
    

    This will retrieve the error information from the current context. then, you can use exceptionHandlerFeature.Error to retrieve the actual error. for example:

    public string? ExceptionMessage { get; set; }
    
    var exceptionHandlerFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();
    
    var error = exceptionHandlerFeature.Error;
    

    You can also read where the error occurred (request URL) from the Path property of exceptionHandlerFeature like this:

    if (exceptionHandlerFeature.Path == "/")
    {
        ExceptionMessage ??= string.Empty;
        ExceptionMessage += " Page: Home.";
    }
    

    HttpContext.Connection.RemoteIpAddress will give you the client IP. In fact, you can get the current user like this:

    if (context.User.Identity is { IsAuthenticated: true })
        userName = context.User.Identity.Name;
    

    Note that you will have problems retrieving IP if your server is located behind a load balancer or if there is a proxy in place.

    HttpContext.Request.Form will give you the form data.
    Request.Headers["Referer"].ToString() will give you the referrer header value.

    By the way, you can opt for using an inline handler for error handling, something like this:

    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler(exceptionHandlerApp =>
        {
            exceptionHandlerApp.Run(async context =>
            {
                context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                var errorCode = Guid.NewGuid().ToString();
                var exceptionHandlerFeature =
                    context.Features.Get<IExceptionHandlerFeature>();
    
                // do stuff
    
                context.Response.Redirect("/error?customErrorCode=" + errorCode); // perform 302 redirection with error code in query string
            });
        });
    
        app.UseHsts(); // you can configure any other non-development features in this if block too, like hsts or https redirection (note that you can do can set up these two in IIS too, although irrelevant)
    }
    

    see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0 for more info on error handling in asp.net core with the built-in error handler middleware.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search