'Custom middleware breaks Developer Exception Page

Custom middleware breaks Developer Exeption Page. Instead of page with exception details there is HTTP 500 response. Debugging line by line runs only until await _next(context);.

Once the custom middleware is removed from WebApplication configuration by deleting app.UseResponseTime(); all works fine.

I suspect the problem is connected to Response stream I am interfering in, but have no more ideas how to diagnose the problem further and solve it.

Using ASP.NET Core 6.

public class ResponseTimeMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var originalBody = context.Response.Body;
        var newBody = new MemoryStream();
        context.Response.Body = newBody;

        var sw = new Stopwatch();
        sw.Start();

        await _next(context);

        sw.Stop();

        // read the new body
        newBody.Position = 0;
        var newContent = await new StreamReader(newBody).ReadToEndAsync();

        // calculate the updated html
        var updatedHtml = UpdateHtmlElement(newContent, sw.ElapsedMilliseconds);

        // set the body = updated html
        var updatedStream = GenerateStreamFromString(updatedHtml);
        await updatedStream.CopyToAsync(originalBody);
        context.Response.Body = originalBody;
    }

    private static Stream GenerateStreamFromString(string s)
    {
        // convert string to stream
        return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(s ?? ""));
    }

    private static string UpdateHtmlElement(string originalHtml, long responseTime)
    {
        var htmlDoc = new HtmlDocument();
        htmlDoc.LoadHtml(originalHtml);

        var ProcTimeHtmlElement = htmlDoc.GetElementbyId("proctime");
        if (ProcTimeHtmlElement != null)
        {
            ProcTimeHtmlElement.InnerHtml = $"{responseTime} ms";
        }

        return htmlDoc.DocumentNode.OuterHtml;
    }
}

public static class ResponseTimeMiddlewareExtensions
{
    public static IApplicationBuilder UseResponseTime(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ResponseTimeMiddleware>();
    }
}

Very standard Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllers();
builder.Services.AddHttpContextAccessor();

var app = builder.Build();

app.UseResponseTime();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.MapControllers();

app.Run();


Solution 1:[1]

I had this kind of problem in the past, and I solved it by changing the line of the custom middleware, I don't know it works for you but it doesn't hurt to try, I hope it helps.

Solution 2:[2]

It was caused beacuse you replaced your default reponse body with MemoryStream I tried as below and could see the err page,but I don't think it's a good solution:

public async Task InvokeAsync(HttpContext context)
        
        {

            var originalBody = context.Response.Body;
            var newBody = new MemoryStream();
            var sw = new Stopwatch();
            sw.Start();
            try
            {
                
                await _next(context);
            }
            catch (Exception ex) 
            {
                context.Response.Body = originalBody;
                await _next(context);
            }
            
            newBody.Position = 0;
            sw.Stop();            
            // read the new body                        
            var newContent = await new StreamReader(newBody).ReadToEndAsync();
            // calculate the updated html
            var updatedHtml = UpdateHtmlElement(newContent, sw.ElapsedMilliseconds);
            // set the body = updated html
            var updatedStream = GenerateStreamFromString(updatedHtml);
            await updatedStream.CopyToAsync(originalBody); 
            context.Response.Body = originalBody;
        }

enter image description here

Update

I tried to custom a error handle middleware and it worked:

 public class ExceptionHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        
        public ExceptionHandlerMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            var originalBody = context.Response.Body;
            try
            {
                await _next.Invoke(context);
            }
            catch (Exception ex)
            {
                 context.Response.Body=originalBody;
                await HandleExceptionMessageAsync(context, ex).ConfigureAwait(false);
            }
        }
        private static Task HandleExceptionMessageAsync(HttpContext context, Exception exception)
        {
            context.Response.ContentType = "application/json";
            int statusCode = (int)HttpStatusCode.InternalServerError;
            var result = JsonConvert.SerializeObject(new
            {
                StatusCode = statusCode,
                ErrorMessage = exception.Message
            });
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = statusCode;
            return context.Response.WriteAsync(result);
        }
    }

In program.cs and before other middleware:

app.UseMiddleware<xxx.ExceptionHandlerMiddleware>();

The result: enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 srhtyt1
Solution 2