使用 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");
}
}
我正在尝试使用中间件将脚本引用注入到 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");
}
}