使用 ASP.NET 核心中间件在 HTML 文件中注入脚本引用

Inject a script reference in HTML files using ASP.NET Core middleware

我正在尝试使用中间件将脚本引用注入到 ASP.NET 核心应用程序生成的所有 HTML 中。我的代码受到 this blog post 的启发,看起来像这样:

public class MyMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        var newContent = string.Empty;
        var existingBody = context.Response.Body;

        using (var newBody = new MemoryStream())
        {
            context.Response.Body = newBody;

            try
            {
                await _next.Invoke(context);
            }
            finally
            {
                context.Response.Body = existingBody;
            }

            newBody.Seek(0, SeekOrigin.Begin);

            newContent = new StreamReader(newBody).ReadToEnd();

            if (context.Response.ContentType.StartsWith("text/html"))
            {
                newContent = newContent.Replace("</body", "<script src=\"my-reference-here\"></script></body");
            }

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

这里的主要挑战是中间件 运行 用于对服务器的所有请求,包括 CSS、javascripts、favicons 等。我希望它 运行 对于 HTML 仅输出,因为上面的代码会导致某些文件类型出现问题,而且我不想将所有响应都写两次。

有什么更好的方法吗?我已经登录 MapWhen,但它似乎不支持查看响应的内容类型。

我个人建议您不要将其作为中间件来实现。从您自己的实现中可以看出,您将整个响应读入内存只是为了能够对其执行一些替换。这真的很低效,并且会阻止您流式传输响应。 BrowserLink does this by modifying the stream as it happens 这样会更有效率。

但是您应该考虑将此逻辑移至 MVC 中。当然这是假设你不需要修改静态文件内容。但是在 MVC 中,您可以编写一个 result filter,它仅在生成 ViewResult 时执行(即当呈现 MVC 视图时)。然后在该过滤器中,您可以修改视图结果以设置一些参数来注入该脚本。

在 MVC 内部,我会在您的布局中添加一个标签助手或某个部分,然后您可以在其中呈现您的脚本标签。这样,您就可以保持对布局的完全控制并将其与 MVC 很好地集成。

也就是说,为了避免 运行 您的中间件出现不相关的响应,当然您也可以简单地将 Response.ContentType 的检查移到更远的位置,这样您实际上就不会将响应读入不需要修改的响应内存流。

我建议在您的视图中使用部分。只需在您的布局中实现它:

@RenderSection("BottomScripts", required: false)

然后在你的视图中你可以使用这样的东西:

@section BottomScripts {
 @if (Model.NeedThisScript)
 {
  await Html.RenderPartialAsync("PartialWithYourScripts");
 }
}