这个问题很老,但问题仍然存在,虽然 Artak 提供的解决方案有效,但在大多数情况下它在概念上是不正确的。首先让我们看看问题的根源:
asp-append-version 使用IHostingEnvironment.WebRootFileProvider 查找文件,默认情况下PhysicalFileProvider 指向wwwroot 文件夹。
核心文档有一个关于如何serve files outside of web root 的示例:
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"
});
}
这允许您从wwwroot 和MyStaticFiles 文件夹中提供静态文件。如果你有图片\MyStaticFiles\pic1.jpg,可以通过两种方式参考:
<img src="~/pic1.jpg" />
<img src="~/StaticFiles/pic1.jpg" />
两者都将同样有效。这在概念上是不正确的,因为您为路径指定了别名/StaticFiles,因此它的文件不应与根/ 组合。但至少它有效,它给你你想要的。
遗憾的是,asp-append-version 并不知道这一切。它应该,但它没有。应该是因为它旨在与静态文件(JavaScript、CSS 和图像)一起使用,所以如果我们更改配置以提供来自不同文件夹的静态文件,asp-append-version 会获得这些配置的副本。没有,所以我们需要单独配置,修改IHostingEnvironment.WebRootFileProvider。
Artak 建议使用CompositeFileProvider,它允许我们将多个文件提供程序分配给IHostingEnvironment.WebRootFileProvider。这确实有效,但它有一个基本问题。 CompositeFileProvider 不允许我们像在 StaticFileOptions 中那样定义 RequestPath。作为一种解决方法,Artak 建议我们不应该使用前缀,这利用了上述错误行为,即文件可以以两种方式引用。为了演示这个问题,假设另一个文件夹的结构如下:
|_ MyStaticFiles
|_ HTML
| |_ 隐私.html
| |_ 常见问题.html
|_ 图片
|_ 图像1.jpg
现在,MyStaticFiles\images 文件夹中的所有文件会发生什么变化?假设 wwwroot 也有 images 文件夹,对于两个同名文件夹,它会起作用还是给你一个错误?文件~/images/image1.jpg 来自哪里?
无论它是否有效,通常有一个重要原因导致您将静态文件放在wwwroot 以外的文件夹中。这通常是因为这些静态文件是您不希望与网站设计文件混合的例如内容文件。
我们需要一个允许我们为每个文件夹指定RequestPath 的提供程序。由于 Core 目前没有这样的提供者,我们只能选择自己编写。虽然不难,但这不是许多程序员喜欢处理的任务。这是一个快速实现,它并不完美,但它可以完成工作。它基于example provided by 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;
}
}
}
现在我们可以更新 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.
在这里,我从配置文件中获取路径,因为它通常甚至完全位于应用程序文件夹之外。现在您可以使用/Content 而不是~/ 来引用您的内容文件。示例:
<img src="~/Content/images/pic1.jpg" asp-append-version="true" />