ASP.NET Core 的 asp-append-version 属性不适用于 wwwroot 目录之外的静态文件

Axi*_*xus 6 c# asp.net asp.net-core wwwroot asp-append-version

我有一个 ASP.NET Core 项目,wwwroot目录和bower_components目录中都有静态文件。

我可以通过将其添加到我的Startup.cs班级来为这些文件提供服务:

StaticFileOptions rootFileOptions = new StaticFileOptions();
rootFileOptions.OnPrepareResponse = staticFilesResponseHandler;
StaticFileOptions bowerFileOptions = new StaticFileOptions();
bowerFileOptions.OnPrepareResponse = staticFilesResponseHandler;
string bowerDirectory = Path.Combine(Directory.GetCurrentDirectory(), "bower_components");
PhysicalFileProvider bowerPhysicalFileProvider = new PhysicalFileProvider(bowerDirectory);
bowerFileOptions.FileProvider = bowerPhysicalFileProvider;
bowerFileOptions.RequestPath = new PathString("/bower");
app.UseStaticFiles(rootFileOptions);
app.UseStaticFiles(bowerFileOptions);
Run Code Online (Sandbox Code Playgroud)

然后从我的观点中引用它们如下:

<script type="text/javascript" src="/bower/jquery/dist/jquery.min.js" asp-append-version="true"></script>
<script type="text/javascript" src="/Libs/jQuery-UI/jquery-ui.min.js" asp-append-version="true"></script>
Run Code Online (Sandbox Code Playgroud)

虽然asp-append-version看起来就好工作了位于下资源wwwroot,似乎对资源的外部完全忽略wwwroot。尽管如此,所有资源都得到了适当的服务;没有 404 或任何东西。上述代码的结果 HTML 如下:

<script type="text/javascript" src="/bower/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="/Libs/jQuery-UI/jquery-ui.min.js?v=YZKMNaPD9FY0wb12QiluqhIOWFhZXnjgiRJoxErwvwI"></script>
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

Rac*_*lan 9

这个问题很老了,但问题仍然存在,尽管 Artak 提供的解决方案有效,但在大多数情况下在概念上是不正确的。首先我们来看看问题的根源:

asp-append-version查找文件,IHostingEnvironment.WebRootFileProvider默认情况下使用该文件PhysicalFileProvider指向wwwroot文件夹。

核心文档有一个关于如何在 Web 根目录之外提供文件的示例:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(); // For the wwwroot folder

    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
        RequestPath = "/StaticFiles"
    });
}
Run Code Online (Sandbox Code Playgroud)

wwwroot这允许您从和文件夹服务器静态文件MyStaticFiles。如果您有图像\MyStaticFiles\pic1.jpg,可以通过两种方式引用它:

<img src="~/pic1.jpg" />
<img src="~/StaticFiles/pic1.jpg" />
Run Code Online (Sandbox Code Playgroud)

两者将同等工作。这在概念上是不正确的,因为您为路径指定了别名/StaticFiles,因此其文件不应与 root 组合/。但至少它有效并且可以给你你想要的东西。

可悲的是,asp-append-version他并不知道这一切。应该如此,但事实并非如此。应该是这样,因为它旨在与静态文件(JavaScript、CSS 和图像)一起使用,因此如果我们更改配置以提供来自不同文件夹的静态文件,那么就asp-append-version可以获取这些配置的副本。没有,所以我们需要通过修改单独配置它IHostingEnvironment.WebRootFileProvider

Artak 建议使用CompositeFileProvider它,它允许我们将多个文件提供程序分配给IHostingEnvironment.WebRootFileProvider. 这确实有效,但有一个根本问题。CompositeFileProvider不允许我们RequestPath在 中定义类似的内容StaticFileOptions。作为一种解决方法,Artak 建议我们不要使用前缀,这利用了上述不正确的行为,即文件可以通过两种方式引用。为了演示该问题,假设另一个文件夹具有如下结构:

|_ MyStaticFiles
       |_ HTML
       | |_ 隐私.html
       | |_ 常见问题解答.html
       |_ 图像
         |_ image1.jpg

现在,文件夹中的所有文件会发生什么情况MyStaticFiles\images?假设wwwroot也有images文件夹,它会工作还是会为您提供两个同名文件夹的错误?文件从哪里来~/images/image1.jpg

不管它是否有效,通常有一个重要原因导致您将静态文件放在wwwroot. 通常是因为这些静态文件是您不希望与网站设计文件混合的内容文件。

我们需要一个允许我们指定RequestPath每个文件夹的提供程序。由于 Core 目前没有这样的提供程序,因此我们只能选择编写自己的提供程序。尽管并不困难,但这并不是许多程序员喜欢解决的任务。这是一个快速实现,它并不完美,但它可以完成工作。它基于Marius Zkochanowski 提供的示例,并进行了一些改进:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Extensions.FileProviders {
  class CompositeFileWithOptionsProvider : IFileProvider {
    private readonly IFileProvider _webRootFileProvider;
    private readonly IEnumerable<StaticFileOptions> _staticFileOptions;

    public CompositeFileWithOptionsProvider(IFileProvider webRootFileProvider, params StaticFileOptions[] staticFileOptions)
  : this(webRootFileProvider, (IEnumerable<StaticFileOptions>)staticFileOptions) { }

    public CompositeFileWithOptionsProvider(IFileProvider webRootFileProvider, IEnumerable<StaticFileOptions> staticFileOptions) {
      _webRootFileProvider = webRootFileProvider ?? throw new ArgumentNullException(nameof(webRootFileProvider));
      _staticFileOptions = staticFileOptions;
    }

    public IDirectoryContents GetDirectoryContents(string subpath) {
      var provider = GetFileProvider(subpath, out string outpath);
      return provider.GetDirectoryContents(outpath);
    }

    public IFileInfo GetFileInfo(string subpath) {
      var provider = GetFileProvider(subpath, out string outpath);
      return provider.GetFileInfo(outpath);
    }

    public IChangeToken Watch(string filter) {
      var provider = GetFileProvider(filter, out string outpath);
      return provider.Watch(outpath);
    }

    private IFileProvider GetFileProvider(string path, out string outpath) {
      outpath = path;
      var fileProviders = _staticFileOptions;
      if (fileProviders != null) {
        foreach (var item in fileProviders) {
          if (path.StartsWith(item.RequestPath, StringComparison.Ordinal)) {
            outpath = path.Substring(item.RequestPath.Value.Length, path.Length - item.RequestPath.Value.Length);
            return item.FileProvider;
          }
        }
      }
      return _webRootFileProvider;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以更新 Artak 的示例以使用新的提供程序:

app.UseStaticFiles(); //For the wwwroot folder.
//This serves static files from the given folder similar to IIS virtual directory.
var options = new StaticFileOptions {
  FileProvider = new PhysicalFileProvider(Configuration.GetValue<string>("ContentPath")),
  RequestPath = "/Content"
};
//This is required for asp-append-version (it needs to know where to find the file to hash it).
env.WebRootFileProvider = new CompositeFileWithOptionsProvider(env.WebRootFileProvider, options);
app.UseStaticFiles(options); //For any folders other than wwwroot.
Run Code Online (Sandbox Code Playgroud)

在这里,我从配置文件中获取路径,因为它通常甚至完全位于应用程序的文件夹之外。现在您可以使用/Content而不是引用您的内容文件~/。例子:

<img src="~/Content/images/pic1.jpg" asp-append-version="true" />
Run Code Online (Sandbox Code Playgroud)


Vah*_*idN 0

我究竟做错了什么?

没有什么。根据 ASP.NET Core 的来源,他们创建了一个FileVersionProviderWebRootPath或开始wwwroot的任务:

private void EnsureFileVersionProvider()
{
    if (_fileVersionProvider == null)
    {
        _fileVersionProvider = new FileVersionProvider(
            HostingEnvironment.WebRootFileProvider,
            Cache,
            ViewContext.HttpContext.Request.PathBase);
    }
}
Run Code Online (Sandbox Code Playgroud)