At the top of my Startup.Configure
pipeline, I created a custom Middleware which handles errors of my asp net core application like this :
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception)
{
if (context.Response.StatusCode != (int)HttpStatusCode.ServiceUnavailable && context.Response.StatusCode != (int)HttpStatusCode.InternalServerError)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
}
finally
{
if (context.Response.StatusCode / 100 != (int)HttpStatusCode.OK / 100)
{
string message = ((HttpStatusCode)context.Response.StatusCode) switch
{
HttpStatusCode.ServiceUnavailable => "The application is currently on maintenance. Please try again later.",
HttpStatusCode.Unauthorized => "You are not authorized to view this content.",
HttpStatusCode.NotFound => "The content you asked for doesn't exist.",
HttpStatusCode.InternalServerError => "There was an internal server error. Please try again in a few minutes.",
_ => "Something went wrong. Please verify your request or try again in a few minutes.",
};
StringBuilder sb = new StringBuilder();
sb.Append($"<html><head><title>Error {context.Response.StatusCode}</title></head><body>");
sb.AppendLine();
sb.Append($"<h1>Error {context.Response.StatusCode} : {(HttpStatusCode)context.Response.StatusCode}</h1>");
sb.AppendLine();
sb.Append($"<p>{message}</p>");
sb.AppendLine();
sb.Append("<br /><input style='display: block;' type='button' value='Go back to the previous page' onclick='history.back()'><br /><br />");
sb.AppendLine();
if (context.Response.StatusCode != (int)HttpStatusCode.Unauthorized && context.Response.StatusCode != (int)HttpStatusCode.NotFound)
{
int delay = 5; //Minutes
sb.Append($"<i>This page will be automatically reloaded every {delay} minutes</i>");
sb.AppendLine();
sb.Append("<script>setTimeout(function(){window.location.reload(1);}, " + (delay * 1000 * 60) + ");</script>");
sb.AppendLine();
}
sb.Append("</body></html>");
await context.Response.WriteAsync(sb.ToString());
}
}
}
}
My errorhandling system relies mainly on the Response.StatusCode
.
Basically, it just wraps the entire code procedure in a try/catch
statement. If an error is catched, the StatusCode
become 500.
I have some others middlewares, especially a MaintenanceMiddleware
which can set the StatusCode
to 503 instead, to specify that the API is under maintenance.
I also have a service which communicates directly with the API and modify the StatusCode
too, depending on the response it got from the API.
So before sending the response to the client, if anything during the procedure modified the StatusCode
to something else than a 2XX StatusCode, the errorhandler middleware catches the response and must erase its content to writes a simple html code which displays an error message.
To achieve that, I use the await context.Response.WriteAsync()
method which writes the html code to the response. It works really great and fast if the application is under maintenance or if it catches an error in the code.
But if there is no error or if none of the middlewares short-circuit the request, if everything is going well but only the StatusCode
is changed, for example if my service returns a 401 Unauthorized error code as following :
var response = await client.SendAsync(request);
//_contextAccessor.HttpContext.Response.StatusCode = (int)response.StatusCode;
_contextAccessor.HttpContext.Response.StatusCode = 401;
Then it will ends up with the original html code from the successful request, and the html code from the errorhandler middleware as shown in the following illustration :
In the Browser Console, my response will contains the 401 StatusCode
as expected. But the page displays the whole html code as shown in the illustration.
So I was looking to a way to clear the content of the current response before my errorhandling middleware writes to the response, but nothing seems to work for my case.
I tried to add context.Response.Clear()
before calling the await context.Response.WriteAsync()
, but I get the following error :
System.InvalidOperationException: The response cannot be cleared, it has already started sending.
So instead of it I tried to reset the response body as following : context.Response.Body = new MemoryStream()
, but this no longer writes the errorhandling code to my response, and does not reset the content at all. Only the original code from the request is displayed.
I also tried with context.Response.Body.SetLength(0)
but this gave me the following error :
System.NotSupportedException: Specified method is not supported.
I'm running out of solutions actually. How can I reset the Response content before writing to it again ?
question from:
https://stackoverflow.com/questions/65901584/middleware-clearing-response-content