I have this Configure
method in my Startup.cs.
It does 3 things :
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);
});
});
}
}
The files under wwwroot are in fact a vue.js app with routing. I need to return index.html
for all inexistant request, for the client routing to take control of the page.
Currently it returns a 404 and does not pass in OnPrepareResponse
hook.
How can I configure index fallback for the router to work in history mode ? I think it is acheivable by configuration in web.config, but I'd prefer to configure this in Startup.js so this is all in the same place.
I ended up writing a file provider that does the index fallback. It encapsulate a PhysicalFileProvider
, and return index.html
under certain conditions if the file is not found. In my case conditions are based on folders css, img or js.
It is implemented this way :
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);
}
}
Then, in startup.config, I use this provider.
Plus, I had to set ServeUnknownFileTypes
to true
to respond to requests to a /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);