Thanks for visiting my blog!
My recent video, SPAs in ASP.NET Core Another Attempt, elicited several interesting responses. But immediately, I noticed that using ASP.NET Core’s cache busting (e.g. asp-append-version) wasn’t working quite right for additional StaticFiles middleware. Evidently, I wasn’t doing it correctly. Let’s walk you through it.
I forked the ASP.NET Core Repository and decided I’d get digging into the source. I had thought I’d try to do a Pull Request to fix what I thought was a bug in the framework. But as they say, “Pride before a Fall” and all that. It was my misunderstanding of how some internals worked.
I naively recommended that you added the StaticFiles middleware twice:
// WRONG WRONG WRONG
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions()
{
// Add the other folder, using the Content Root as the base
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "OtherFolder"))
});
On the face of it, this seems to work. The middleware will look for the files in the typical wwwroot
folder and then in my OtherFolder
and return them. When I would try to add asp-append-version
to files in the OtherFolder
, it would just fail to add a version. Appending the version depends on a checksum of the file and since it couldn’t find it, it just skipped it.
I mistakenly believed that StaticFiles depended on the middleware to find the file. Instead, it relies on the WebRootFileProvider
. But first, what is a FileProvider
(e.g. implements the IFileProvider
interface)?
The docs have a good write it up on it (here), but i’ll summarize by simply saying service that knows how to find files in different ways. The framework has several implementations including PhysicalFileProvider
and ManifestEmbeddedFileProvider
. In fact, our bad example above actually uses the PhysicalFileProvider. Since we know about these providers, what the UseStaticFiles actually does is just use the WebRootFileProvider
as the way to find static files (and that one is just looking in wwwroot
). So the real way to handle this is to change the FileProvider.
So, we need a way to take the two providers we care about and use both of them. First we create the providers we need (two providers in this example):
// Build the different providers you need
var webRootProvider =
new PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider =
new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, @"Other"));
Next we can use the third kind of FileProvider that is supplied in ASP.NET Core. It allows us to compose our providers into a combination of the other providers. This provider is called CompositeFileProvider
. So to solve our issue, we need to create a new CompositeFileProvider:
// Create the Composite Provider
var compositeProvider =
new CompositeFileProvider(webRootProvider,
newPathProvider);
Finally we replace the WebRootFileProvider
with our new CompositeFileProvider
:
// Replace the default provider with the new one
app.Environment.WebRootFileProvider = compositeProvider;
If you want to see the entire sample, you can see it in the reproduction case I wrote for the Issue I created:
P.S. I closed the issue I created in the ASP.NET Core repo since it was a user error, like it is too many times.