I have a middleware which calculates Razor Page execution time:
app.Use(async (context, next) =>
{
var sw = new Stopwatch();
sw.Start();
await next.Invoke();
sw.Stop();
await context.Response.WriteAsync($"<div class=\"container\">Processing time: {sw.ElapsedMilliseconds} ms<div>");
});
It works fine, however HTML created by WriteAsync
is added at the very end of the response, after closing </html>
tag.
How can I place - pass or inject - a value of sw.ElapsedMilliseconds
calculated by the middleware in specific place inside HTML body? Using ASP.NET Core 6.0.
Maybe you can use the HTML Agility Pack.
You can use the LoadHtml
and HtmlNode.CreateNode
methods to create the html element you want to insert, and then use DocumentNode.SelectSingleNode
to select the position you want to insert.
For testing,you can refer to the following code:
Custom middleware:
//Install-Package HtmlAgilityPack
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalBody = context.Response.Body;
var newBody = new MemoryStream();
context.Response.Body = newBody;
var watch = new Stopwatch();
long responseTime = 0;
watch.Start();
await _next(context);
// read the new body
responseTime = watch.ElapsedMilliseconds;
newBody.Position = 0;
var newContent = await new StreamReader(newBody).ReadToEndAsync();
// calculate the updated html
var updatedHtml = CreateDataNode(newContent, responseTime);
// set the body = updated html
var updatedStream = GenerateStreamFromString(updatedHtml);
await updatedStream.CopyToAsync(originalBody);
context.Response.Body = originalBody;
}
public static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div class=\"container\">Processing time: {responseTime.ToString()} ms.</div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode(".//body/div");
if (htmlBody != null)
{
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
}
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
htmlDoc.DocumentNode.SelectSingleNode(".//body/div")
and htmlBody.InsertBefore(testNode, htmlBody.FirstChild)
can customize where you want to insert.
And use custom middleware:
app.UseMiddleware<ResponseMeasurementMiddleware>();
Test Result: