Reputation: 1
I cannot for the life of me figure out how to resolve this issue. Please help. To simplify and replicate, I made a copy of the .NetCore3.1 MVC template in VS2019, and added the relevant parts.
I have an interceptor middleware that logs all requests and responses, and using a Home/Error view to show that an error occured.
When the call succeeds, the interceptor logs the requests and responses fine. However, if an exception happens in the pipeline, the response interceptor fails with error in
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
, and display a HTTP 500 error instead of the Home/Error view.
An exception was thrown attempting to execute the error handler.
System.ObjectDisposedException: Cannot access a closed Stream.
Object name: 'destination'.
at System.IO.StreamHelpers.ValidateCopyToArgs(Stream source, Stream destination, Int32 bufferSize)
at System.IO.MemoryStream.CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
at System.IO.Stream.CopyToAsync(Stream destination)
at Test.Middleware.Interceptor.Invoke(HttpContext context) in C:\Users\micher03\source\repos\test\Middleware\Interceptor.cs:line 30
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
Startup.cs
namespace Test
{
public class Startup
{
public Startup(IConfiguration configuration, IHostEnvironment hostingEnvironment)
{
Configuration = configuration;
var builder = new ConfigurationBuilder()
.SetBasePath(hostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Log.Information("Application Starting");
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseExceptionHandler("/Home/Error");
app.UseMiddleware<Interceptor>();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Error.cshtml
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
Interceptor
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Serilog;
namespace Test.Middleware
{
public class Interceptor
{
private readonly RequestDelegate _nextRequest;
public Interceptor(RequestDelegate nextRequest)
{
_nextRequest = nextRequest;
}
public async Task Invoke(HttpContext context)
{
await LogRequest(context.Request);
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _nextRequest(context);
await LogResponse(context.Response);
await responseBody.CopyToAsync(originalBodyStream);
}
}
private async Task<string> LogRequest(HttpRequest request)
{
HttpRequestRewindExtensions.EnableBuffering(request);
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body.Seek(0, SeekOrigin.Begin);
Log.Information("Request {Path} {Body}", request.Path, bodyAsText);
return "OK";
}
private async Task<string> LogResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
string bodyAsText = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
Log.Information("Response {@StatusCode} {@Body}", response.StatusCode, bodyAsText);
return "OK";
}
}
}
Upvotes: 0
Views: 318
Reputation: 11
Response memory stream is closed when it reaches DeveloperExceptionPageMiddleware
or ExceptionHandlerMiddleware
You need to set back to original stream in your Interceptor if exception is thrown
public async Task Invoke(HttpContext context)
{
await LogRequest(context.Request);
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
try {
await _nextRequest(context);
}
catch
{
if(!context.Response.HasStarted)
{
context.Response.Body = originalResponseStream;
}
throw;
}
await LogResponse(context.Response);
await responseBody.CopyToAsync(originalBodyStream);
}
}
Upvotes: 1