I have an ASP.NET Core project with static files in both the wwwroot
directory and bower_components
directory.
I am able to server these files by adding this to my Startup.cs
class:
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);
And then reference them from my views as follows:
<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>
Even though asp-append-version
seems to work just fine for resources located under wwwroot
, it seems to be completely ignored for resources outside of wwwroot
. All resources are being properly served though; no 404s or anything. The resulting HTML for the code above is as follows:
<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>
What am I doing wrong?
This question is old, but the issue still exists and although the solution provided by Artak works, it is conceptually incorrect in most cases. First let's see the root of the problem:
asp-append-version
looks for the files using IHostingEnvironment.WebRootFileProvider
which by default is a PhysicalFileProvider
pointing to the wwwroot
folder.
The Core docs have an example on how to 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"
});
}
This allows you to server static files from both wwwroot
and MyStaticFiles
folders. If you have an image \MyStaticFiles\pic1.jpg
, you can refer to it in two ways:
<img src="~/pic1.jpg" />
<img src="~/StaticFiles/pic1.jpg" />
Both will work equally. This is conceptually incorrect because you gave the path an alias of /StaticFiles
, so its files shouldn't be combined with the root /
. But at least it works and it gives you what you want.
Sadly, asp-append-version
doesn't know about all of that. It should, but it doesn't. It should because it is meant to be used with static files (JavaScript, CSS, and images), so it makes sense that if we changed the configurations to serve static files from different folders, that asp-append-version
gets a copy of those configurations. It doesn't, so we need to configure it separately by modify IHostingEnvironment.WebRootFileProvider
.
Artak suggested to use CompositeFileProvider
which allows us to assign more than one file provider to IHostingEnvironment.WebRootFileProvider
. That does work, however it has a fundamental issue. CompositeFileProvider
doesn't allow us to define the RequestPath
like in StaticFileOptions
. As a workaround, Artak suggested that we should not use the prefix, which makes use of the above mentioned incorrect behaviour that files can be referenced in both ways. To demonstrate the issue, let's say that the other folder has a structure like this:
|_ MyStaticFiles |_ HTML | |_ privacy.html | |_ faq.html |_ images |_ image1.jpg
Now, what happens to all files in the MyStaticFiles\images
folder? Assuming that wwwroot
also has images
folder, will it work or give you an error for two identically named folders? Where will the file ~/images/image1.jpg
be coming from?
Regardless of whether it works or not, there is often an important reason for why you have your static files in a folder other than wwwroot
. It is often because those static files are for example content files that you don't want mixed with website design files.
We need a provider that allows us to specify the RequestPath
for each folder. Since Core doesn't currently have such provider, we're only left with the option of writing our own. Although not difficult, it's not a task that many programmers like to tackle. Here is a quick implementation, it's not perfect, but it does the job. It is based on the example provided by Marius Zkochanowski with some inhancements:
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;
}
}
}
Now we can update Artak's example to use the new provider:
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.
Here, I'm getting the path from the configurations file, because often it is even outside the app's folder altogether. Now you can reference your content files using /Content
and not ~/
. Example:
<img src="~/Content/images/pic1.jpg" asp-append-version="true" />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With