如何使用 .NET Core 中间件将 HTML 插入响应主体

How to insert HTML into Response body using .NET Core Middleware

我一直在尝试拼凑一些中间件,这些中间件可以让我测量请求的处理时间。 This 示例给了我一个很好的起点,但我 运行 遇到了麻烦。

在下面的代码中,我能够测量处理时间并将其插入 div(使用 HTML Agility Pack)。但是,页面的原始内容会被复制。我想我对 UpdateHtml() 中的 context.Response.Body 属性 做错了什么,但无法弄清楚它是什么。 (我在代码中做了一些评论。)如果您发现任何看起来不正确的地方,请告诉我好吗?

谢谢。

public class ResponseMeasurementMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        var watch = new Stopwatch();
        watch.Start();
        context.Response.OnStarting(async () =>
        {
            var responseTime = watch.ElapsedMilliseconds;
            var newContent = string.Empty;
            var existingBody = context.Response.Body;
            string updatedHtml = await UpdateHtml(responseTime, context);

            await context.Response.WriteAsync(updatedHtml);
        });

        await _next.Invoke(context);
    }

    private async Task<string> UpdateHtml(long responseTime, HttpContext context)
    {
        var newContent = string.Empty;
        var existingBody = context.Response.Body;
        string updatedHtml = "";
        //I think I'm doing something incorrectly in this using...
        using (var newBody = new MemoryStream())
        {
            context.Response.Body = newBody;

            await _next(context);

            context.Response.Body = existingBody;
            newBody.Position = 0;

            newContent = await new StreamReader(newBody).ReadToEndAsync();
            updatedHtml = CreateDataNode(newContent, responseTime);
        }

        return updatedHtml;
    }

    private string CreateDataNode(string originalHtml, long responseTime)
    {
        var htmlDoc = new HtmlDocument();
        htmlDoc.LoadHtml(originalHtml);
        HtmlNode testNode = HtmlNode.CreateNode($"<div><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
        var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
        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;
    }
}

对于重复的html,这是由UpdateHtml中的await _next(context);引起的,它会调用MVC等其他中间件来处理请求和响应。

如果没有 await _next(context);,您不应修改 context.Response.OnStarting 中的响应正文。

对于解决方法,我建议您将 ResponseMeasurementMiddleware 作为第一个中间件,然后像

一样计算时间
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
        // 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><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
        var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
        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;
    }
}

并像

一样注册ResponseMeasurementMiddleware
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMiddleware<ResponseMeasurementMiddleware>();
    //rest middlwares
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

对于这种方式app.UseMiddleware<ResponseMeasurementMiddleware>();,动作将是发送响应之前的最后一个操作,然后处理时间适合处理时间。