asp.net core prints Request information when an exception occurs

cause

It is well known that printing logs is a technical activity. The log information printed by a qualified developer is very beautiful, but some newbies may be negligent in logging, so that they may be lost when an exception occurs. delete important information;
Therefore, it is very friendly for developers to print out the Request information for debug ging when an exception occurs.

solve

It is actually very simple to implement this idea, just add a middleware for global exception handling before request processing, and print the request information when the catch is abnormal;

public class ExceptionHandlerMiddleware
{
    public async Task Invoke(HttpContext context)
    {
         try
         {
              await _next(context);
         }
         catch(Exception ex)
         {
               // Print Request information
         }
    }
}

So why write a blog about it?
There is a small problem here, that is the problem of reading Request.Body. We know that Request.Body is not allowed to be read repeatedly (the specific version is not clear, the .net core version I used when I encountered this problem is 3.1),
That is to say, when the Body has been read in the Action, even if an exception occurs later, the content of the Body cannot be read during the catch.
If you want to dig deeper, you can look at the big guy A deep dive into the correct way to read Request.Body in ASP.NET Core
Then this is not easy, just change it directly to the following code;

public class ExceptionHandlerMiddleware
{
    public async Task Invoke(HttpContext context)
    {
         try
         {
              context.Request.EnableBuffering();
              await _next(context);
         }
         catch(Exception ex)
         {
               // Print Request information
         }
    }
}

Yes yes this solves the problem of reading Request.Body, but is this the best solution?
I think it may not be, because after the introduction of context.Request.EnableBuffering(), it is equivalent to fully exposing Request.Body to the underlying methods,
And the function of our middleware is only a global error record, so it is quite unreasonable to destroy Request.Body, so how should we do it so as not to destroy the original design of Body?
The answer is the proxy mode, which builds a Stream proxy and overrides the read method.

internal class ProxyRequestStream : Stream
{
    private Stream _innerStream;
    public ProxyRequestStream(Stream innerStream)
    {
        _innerStream = innerStream;
    }
    public override bool CanRead => _innerStream.CanRead;
    public override bool CanSeek => _innerStream.CanSeek;
    public override bool CanWrite => _innerStream.CanWrite;
    public override long Length => _innerStream.Length;
    public override long Position
    {
        get => _innerStream.Position;
        set => _innerStream.Position = value;
    }
    // 1w lines are omitted here
    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
    {
        var len = await _innerStream.ReadAsync(buffer, cancellationToken);
        // buffer can be saved here
        return len;
    }
    // Again omit the 1w line
}

public class ExceptionHandlerMiddleware
{
    public async Task Invoke(HttpContext context)
    {
         ProxyRequestStream proxy;
         try
         {
              proxy = new ProxyRequestStream(context.Request.Body);
              context.Request.Body = proxy;
              await _next(context);
         }
         catch(Exception ex)
         {
               // Print Request information
         }
         finally
         {
              if(proxy != null)
                  proxy.Dispose();
         }
    }
}

I think this kind of treatment should be more elegant (hemp) and elegant (annoying).

Posted by koencalliauw on Sun, 08 May 2022 17:52:25 +0300