0

I'm creating ASP.Net Core Web API using .Net Core 2.1 and I need to create custom middleware for global exception handling. What I am trying to do is capture JSON request when exception happened in my application. And since I'm using custom middleware, I want the JSON request available in my middleware. How I can accomplish this?

I've tried creating custom middleware for centralized exception handling by following an article from Marinko Spasojevic and slightly modified it to capture the json request. It seems the request already unavailable since the exception occurred inside controllers action (not in the middleware). Here is my code:

Here is my Error Log model

public class ErrorLog
{
    public DateTime LogDate { get; set; }
    public string URL { get; set; }
    public string Request { get; set; }
    public string Source { get; set; }
    public string Message { get; set; }
}

Here is standard response model used in my project

public class BaseResponse<T> : IBaseResponse where T : class
{
    public bool Status { get; set; }
    public string Message { get; set; }
    public IEnumerable<T> Data { get; set; }
}

Here is my custom exception middleware

public class GlobalException
{
    private readonly RequestDelegate _next;
    private readonly ICustomLogger _logger;

    public GlobalException(RequestDelegate next, ICustomLogger logger)
    {
        _logger = logger;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            ErrorLog log = new ErrorLog();
            log = await HandleLogError(httpContext, ex);

            _logger.LogError(log); // Custom build logger

            await HandleExceptionAsync(httpContext, ex);
        }
    }

    private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        BaseResponse<object> response = new BaseResponse<object>();
        response.Status = false;
        response.Message = "There is an exception occured.";
        response.Data = new List<object>();

        await context.Response.WriteAsync(response.Serialize());
    }

    private static async Task<ErrorLog> HandleLogError(HttpContext context, Exception exception)
    {
        Stream body = context.Request.Body;

        context.Request.EnableRewind();

        byte[] buffer = new byte[Convert.ToInt32(context.Request.ContentLength)];

        await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);

        string requestText = Encoding.UTF8.GetString(buffer);

        context.Request.Body = body;

        ErrorLog log = new ErrorLog();
        UriBuilder builder = new UriBuilder();

        builder.Scheme = context.Request.Scheme;
        builder.Host = context.Request.Host.Host;
        builder.Path = context.Request.Path.ToString();
        builder.Query = context.Request.QueryString.ToString();

        log.LogDate = DateTime.Now;
        log.URL = builder.Uri.ToString();
        log.Request = requestText;
        log.Source = exception.Source;
        log.Message = exception.Message;

        return log;
    }
}

And finnally register the middleware

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
        app.UseMiddleware<GlobalException>();
        ...
    }

So, anyone can give me an enlightenment? Any help would be appreciated and thank you before.

Wulung Triyanto
  • 185
  • 2
  • 7

2 Answers2

3

It seems the request already unavailable since the exception occurred inside controllers action (not in the middleware).

Firstly, you can get the root error by ex.InnerException if this is what you wanna get.

What I am trying to do is capture JSON request when exception happened in my application.

Also, you can read and log requests & responses (in this case I assume it is serialized to json) as shown below in your error handling middleware.

public async Task InvokeAsync(HttpContext context)
{  
    var requestAsString = await FormatRequest(context.Request);

    var originalBodyStream = context.Response.Body;

    using (var responseBody = new MemoryStream())
    {
        context.Response.Body = responseBody;

        await _next(context);

        var responseString = await FormatResponse(context.Response);

        await responseBody.CopyToAsync(originalBodyStream);
    }
}

private async Task<string> FormatRequest(HttpRequest request)
{
    var body = request.Body;
    request.EnableRewind();

    var buffer = new byte[Convert.ToInt32(request.ContentLength)];
    await request.Body.ReadAsync(buffer, 0, buffer.Length);
    var bodyAsText = Encoding.UTF8.GetString(buffer);
    request.Body = body;

   return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}

private async Task<string> FormatResponse(HttpResponse response)
{
    response.Body.Seek(0, SeekOrigin.Begin);
    var text = await new StreamReader(response.Body).ReadToEndAsync(); 
    response.Body.Seek(0, SeekOrigin.Begin);

    return $"Response {text}";
}

Btw, I have made small changes to make it fit to your problem properly, but the credits goes to this gist page. Hope this solves your problem.

Hasan
  • 1,143
  • 10
  • 25
0

Apparently this part of code causing the request cannot retrieved

...
try
{
    await _next(httpContext); // this will change the httpContext contents
}
...

Here is my final code

public class GlobalException
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public GlobalException(RequestDelegate next, ILogger logger)
    {
        _logger = logger;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        HttpContext tempCtx = context; // had to contain the http context
        string request = await FormatRequest(context.Request);

        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            ErrorLog log = new ErrorLog();
            UriBuilder builder = new UriBuilder();

            builder.Scheme = tempCtx.Request.Scheme;
            builder.Host = tempCtx.Request.Host.Host;
            builder.Path = tempCtx.Request.Path.ToString();
            builder.Query = tempCtx.Request.QueryString.ToString();

            log.LogDate = DateTime.Now;
            log.URL = builder.Uri.ToString();
            log.Request = request;
            log.Source = ex.Source;
            log.Message = ex.Message;

            await _logger.LogError(log); // custom logger

            await HandleExceptionAsync(context);
        }
    }

    private async Task<string> FormatRequest(HttpRequest request)
    {
        request.EnableRewind();
        var body = request.Body;

        byte[] buffer = new byte[Convert.ToInt32(request.ContentLength)];

        await request.Body.ReadAsync(buffer, 0, buffer.Length);

        string requestBody = Encoding.UTF8.GetString(buffer);

        body.Seek(0, SeekOrigin.Begin);

        request.Body = body;

        return requestBody;
    }

    private async Task HandleExceptionAsync(HttpContext context)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        BaseResponse<object> response = new BaseResponse<object>();
        response.Status = false;
        response.Message = "There is an exception occured.";
        response.Data = new List<object>();

        await context.Response.WriteAsync(response.Serialize());
    }
}

Great thanks to Hasan for giving me an enlightenment.

Wulung Triyanto
  • 185
  • 2
  • 7