Атрибут asp-append-version ядра ASP.NET не работает для статических файлов за пределами каталога wwwroot

У меня есть проект ASP.NET Core со статическими файлами в обоих wwwroot каталог и bower_components каталог.

Я могу сервер эти файлы, добавив это в мой Startup.cs учебный класс:

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);

А потом ссылаться на них с моей точки зрения следующим образом:

<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>

Даже если asp-append-version Кажется, работает просто отлично для ресурсов, расположенных под wwwroot, кажется, полностью игнорируется для ресурсов за пределами wwwroot, Все ресурсы должным образом обслуживаются, хотя; нет 404 или что-нибудь. Полученный HTML-код для приведенного выше кода выглядит следующим образом:

<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>

Что я делаю неправильно?

2 ответа

Это старый вопрос, но проблема все еще существует, и хотя решение, предложенное Артаком, работает, в большинстве случаев оно концептуально неверно. Сначала давайте посмотрим на корень проблемы:

asp-append-version ищет файлы, используя IHostingEnvironment.WebRootFileProvider который по умолчанию PhysicalFileProvider указывая на wwwroot папка.

В основных документах есть пример того, как обслуживать файлы вне корневого веб-сайта:

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.

Артак предложил использовать CompositeFileProvider что позволяет нам назначать более одного поставщика файлов для IHostingEnvironment.WebRootFileProvider. Это действительно работает, но имеет фундаментальную проблему.CompositeFileProvider не позволяет нам определить RequestPath как в StaticFileOptions. В качестве обходного пути Артак предложил не использовать префикс, который использует вышеупомянутое неправильное поведение, когда на файлы можно ссылаться обоими способами. Чтобы продемонстрировать проблему, предположим, что другая папка имеет такую ​​структуру:

|_ MyStaticFiles
       |_ HTML
       | | _ privacy.html
       | | _ faq.html
       | _ изображения
         |_ image1.jpg

Что происходит со всеми файлами в MyStaticFiles\imagesпапка? При условии, чтоwwwroot также имеет imagesпапка, будет ли она работать или выдаст ошибку для двух папок с одинаковыми именами? Где будет файл~/images/image1.jpg быть откуда?

Независимо от того, работает это или нет, часто есть важная причина того, почему у вас есть статические файлы в папке, отличной от wwwroot. Часто это происходит потому, что эти статические файлы представляют собой, например, файлы содержимого, которые вы не хотите смешивать с файлами дизайна веб-сайтов.

Нам нужен провайдер, который позволит нам указать RequestPathдля каждой папки. Поскольку Core в настоящее время не имеет такого провайдера, нам остается только написать собственный. Хотя это и не сложно, это не та задача, которую любят решать многие программисты. Вот быстрая реализация, она не идеальна, но работает. Он основан на примере, представленном Мариусом Зкохановски, с некоторыми улучшениями:

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;
    }
  }
}

Теперь мы можем обновить пример Артака, чтобы использовать нового провайдера:

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" />

Рассмотрите возможность удаления /bower/ префикс при обращении к jquery/dist/jquery.min.js следующее:

<script type="text/javascript" src="~/jquery/dist/jquery.min.js"></script>

Кроме того, вы должны установить HostingEnvironment.WebRootFileProvider в Startup.Configure метод следующим образом:

var compositeProvider = new CompositeFileProvider(
    env.WebRootFileProvider,
    new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "bower_components")));
env.WebRootFileProvider = compositeProvider;
var options = new StaticFileOptions()
{
    FileProvider = compositeProvider,
    RequestPath = "/bower"
};
app.UseStaticFiles(options);

Надеюсь это поможет.

Что я делаю неправильно?

Ничего такого. Согласно источнику ASP.NET Core, они создают FileVersionProvider который начинается с WebRootPath или же wwwroot для этой задачи:

private void EnsureFileVersionProvider()
{
    if (_fileVersionProvider == null)
    {
        _fileVersionProvider = new FileVersionProvider(
            HostingEnvironment.WebRootFileProvider,
            Cache,
            ViewContext.HttpContext.Request.PathBase);
    }
}
Другие вопросы по тегам