我的 Startup.cs 中有这个
Configure
方法。
它做了三件事:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
// Defaults to index.html
var defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear();
defaultFilesOptions.DefaultFileNames.Add("index.html");
app.UseDefaultFiles(defaultFilesOptions);
var staticFileOptions = new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// Add CSP for index.html
if (ctx.File.Name == "index.html")
{
ctx.Context.Response.Headers.Append(
"Content-Security-Policy", "default-src 'self'" // etc
);
}
}
};
app.UseStaticFiles(staticFileOptions); // wwwroot
app.UseRouting();
app.UseEndpoints(endpoints =>
{
// Settings.json endpoint
endpoints.MapGet("/settings.json", async context =>
{
string json = $@"
{{
""myConfig"": ""{_configuration["myParameter"]}""
}}";
await context.Response.WriteAsync(json);
});
});
}
}
wwwroot下的文件实际上是一个带有路由的vue.js应用程序。我需要为所有不存在的请求返回
index.html
,以便客户端路由控制页面。
目前它返回404并且没有传入
OnPrepareResponse
钩子。
如何配置索引回退以使路由器在历史模式下工作? 我认为可以通过 web.config 中的配置来实现,但我更喜欢在 Startup.js 中配置它,所以这一切都在同一个地方。
我最终编写了一个执行索引回退的文件提供程序。它封装了一个
PhysicalFileProvider
,如果没有找到文件,在一定条件下返回index.html
。在我的例子中,条件基于文件夹 css、img 或 js。
它是这样实现的:
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using System.Linq;
public class IndexFallbackFileProvider : IFileProvider
{
private readonly PhysicalFileProvider _innerProvider;
public IndexFallbackFileProvider(PhysicalFileProvider physicalFileProvider)
{
_innerProvider = physicalFileProvider;
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
return _innerProvider.GetDirectoryContents(subpath);
}
public IFileInfo GetFileInfo(string subpath)
{
var fileInfo = _innerProvider.GetFileInfo(subpath);
if(!fileInfo.Exists && MustFallbackToIndex(subpath))
{
if(!_staticFilesFolders.Any(f => subpath.Contains(f)))
{
fileInfo = _innerProvider.GetFileInfo("/index.html");
}
}
return fileInfo;
}
// Plain 404 are OK for css, img, js.
private static string[] _staticFilesFolders = new string[] { "/css/", "/img/", "/js/" };
private static bool MustFallbackToIndex(string subpath)
{
return !_staticFilesFolders.Any(f => subpath.Contains(f));
}
public IChangeToken Watch(string filter)
{
return _innerProvider.Watch(filter);
}
}
然后,在startup.config中,我使用这个提供程序。 另外,我必须将
ServeUnknownFileTypes
设置为 true
才能响应对 /path/without/extension
的请求。
var physicalFileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "wwwroot"));
var fileProvider = new IndexFallbackFileProvider(physicalFileProvider);
var staticFileOptions = new StaticFileOptions
{
FileProvider = fileProvider,
ServeUnknownFileTypes = true
};
app.UseStaticFiles(staticFileOptions);
我喜欢@Johnny5的解决方案,但想返回index.html文件和任何JS或CSS文件,即使重新加载页面时基目录是错误的。
通过一次删除一个目录的前缀,我们可以找到这些文件,即使它们位于像
/assets/index-2562.js
这样的路径上。
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using System.Linq;
/// <summary>
/// Strips off any directories until it finds a match
/// This allows an SPA to work even after page reload with some SPA path still on the URL
/// </summary>
public class IndexFallbackFileProvider : IFileProvider
{
private readonly PhysicalFileProvider innerProvider;
public IndexFallbackFileProvider(PhysicalFileProvider physicalFileProvider)
{
innerProvider = physicalFileProvider;
}
// For an SPA we may be called with a path like /foo/bar left over from some earlier SPA navigation
// We need to remove these prefix directories until we find a match
public IDirectoryContents GetDirectoryContents(string path)
{
string subpath = path.TrimStart(System.IO.Path.DirectorySeparatorChar); // remove leading /
while (true)
{
var result = innerProvider.GetDirectoryContents(subpath);
if (result.Exists) return result;
string? directory = System.IO.Path.GetDirectoryName(subpath);
if (directory is null) break;
int index = directory.IndexOf(System.IO.Path.DirectorySeparatorChar);
if (index < 0) break;
subpath = subpath.Substring(index + 1);
}
return innerProvider.GetDirectoryContents("");
}
public IFileInfo GetFileInfo(string path)
{
string subpath = path.TrimStart(System.IO.Path.DirectorySeparatorChar); // remove leading /
while (true)
{
var result = innerProvider.GetFileInfo(subpath);
if (result.Exists) return result;
string? directory = System.IO.Path.GetDirectoryName(subpath);
if (directory is null) break;
int index = directory.IndexOf(System.IO.Path.DirectorySeparatorChar);
if (index < 0) break;
subpath = subpath.Substring(index + 1);
}
return innerProvider.GetFileInfo("index.html");
}
public IChangeToken Watch(string filter)
{
return innerProvider.Watch(filter);
}
}
相同的启动代码:
var physicalFileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "wwwroot"));
var fileProvider = new IndexFallbackFileProvider(physicalFileProvider);
var staticFileOptions = new StaticFileOptions
{
FileProvider = fileProvider,
ServeUnknownFileTypes = true
};
app.UseStaticFiles(staticFileOptions);