Может ли Razor Class Library ставить статические файлы (js, css и т.д.)?

Возможно, это уже дубликат, но поскольку у этого поста нет ответа, я публикую этот вопрос.

Новая библиотека классов Razor великолепна, но она не может упаковать файлы библиотек (например, jQuery, общий CSS).

Могу ли я как-то повторно использовать CSS в нескольких проектах Razor Page, используя библиотеку классов Razor или что-то еще (моя цель заключается в том, чтобы на нескольких веб-сайтах использовался один и тот же CSS, и одно изменение применяется ко всем проектам).

Я попытался создать папку wwwroot в проекте библиотеки классов Razor, но она не работает wwwroot (и я могу понять, почему).

Ответ 1

На момент запроса Ehsan был верным (для .NET Core 2.2), для .NET Core 3.0 RCL может включать статические ресурсы без особых усилий:

Чтобы включить сопутствующие ресурсы как часть RCL, создайте папку wwwroot в библиотеке классов и включите в нее все необходимые файлы.

При упаковке RCL все сопутствующие ресурсы в папке wwwroot автоматически включаются в пакет.

Файлы, включенные в папку wwwroot в RCL, доступны приложению-потребителю с префиксом _content/{ИМЯ БИБЛИОТЕКИ}/. Например, библиотека с именем Razor.Class.Lib приводит к пути к статическому содержимому в _content/Razor.Class.Lib/.

Ответ 2

Вам необходимо встроить статические ресурсы в сборку библиотеки классов Razor. Я думаю, что лучший способ понять, как это сделать, - взглянуть на исходные коды пользовательского интерфейса ASP.NET Identity.

Вы должны выполнить следующие 4 шага, чтобы встроить свои активы и обслуживать их.

  1. Отредактируйте файл csproj вашей библиотеки классов Razor и добавьте следующие строки.

     <PropertyGroup>
      ....
           <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
      ....
     </PropertyGroup>
    
     <ItemGroup>
         ....
         <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.2" />
         <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
         <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.1.1" />
         <PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
        .....
     </ItemGroup>
    
    <ItemGroup>
        <EmbeddedResource Include="wwwroot\**\*" />
        <Content Update="**\*.cshtml" Pack="false" />
    </ItemGroup>
    
  2. В библиотеке классов Razor создайте следующий класс для обслуживания и маршрутизации ресурсов. (предполагается, что ваши активы находятся в папке wwwroot)

    public class UIConfigureOptions : IPostConfigureOptions<StaticFileOptions>
    {
        public UIConfigureOptions(IHostingEnvironment environment)
        {
            Environment = environment;
        }
        public IHostingEnvironment Environment { get; }
    
        public void PostConfigure(string name, StaticFileOptions options)
        {
            name = name ?? throw new ArgumentNullException(nameof(name));
            options = options ?? throw new ArgumentNullException(nameof(options));
    
            // Basic initialization in case the options weren't initialized by any other component
            options.ContentTypeProvider = options.ContentTypeProvider ?? new FileExtensionContentTypeProvider();
            if (options.FileProvider == null && Environment.WebRootFileProvider == null)
            {
                throw new InvalidOperationException("Missing FileProvider.");
            }
    
            options.FileProvider = options.FileProvider ?? Environment.WebRootFileProvider;
    
            var basePath = "wwwroot";
    
            var filesProvider = new ManifestEmbeddedFileProvider(GetType().Assembly, basePath);
            options.FileProvider = new CompositeFileProvider(options.FileProvider, filesProvider);
        }
    }
    
  3. Создайте зависимое веб-приложение для использования маршрутизатора Razor Class Library. В методе ConfigureServices класса запуска добавьте следующую строку.

    services.ConfigureOptions(typeof(UIConfigureOptions));
    
  4. Итак, теперь вы можете добавить ссылку на ваш файл. (предположим, что он находится по адресу wwwroot/js/app.bundle.js).

    <script src="~/js/app.bundle.js" asp-append-version="true"></script>
    

Ответ 3

Спасибо за полезную информацию Ehsan.

Вот расширенная версия, позволяющая отлаживать javascript и typcript, а также возможность вносить изменения без перекомпиляции. Отладка TypeScript не работает в Chrome, но в IE. Если вы знаете, почему, пожалуйста, напишите ответ. Спасибо!

public class ContentConfigureOptions : IPostConfigureOptions<StaticFileOptions>
{
    private readonly IHostingEnvironment _environment;

    public ContentConfigureOptions(IHostingEnvironment environment)
    {
        _environment = environment;
    }

    public void PostConfigure(string name, StaticFileOptions options)
    {
        // Basic initialization in case the options weren't initialized by any other component
        options.ContentTypeProvider = options.ContentTypeProvider ?? new FileExtensionContentTypeProvider();

        if (options.FileProvider == null && _environment.WebRootFileProvider == null)
        {
            throw new InvalidOperationException("Missing FileProvider.");
        }

        options.FileProvider = options.FileProvider ?? _environment.WebRootFileProvider;

        if (_environment.IsDevelopment())
        {
            // Looks at the physical files on the disk so it can pick up changes to files under wwwroot while the application is running is Visual Studio.
            // The last PhysicalFileProvider enalbles TypeScript debugging but only wants to work with IE. I'm currently unsure how to get TS breakpoints to hit with Chrome.
            options.FileProvider = new CompositeFileProvider(options.FileProvider, 
                                                             new PhysicalFileProvider(Path.Combine(_environment.ContentRootPath, $"..\\{GetType().Assembly.GetName().Name}\\wwwroot")),
                                                             new PhysicalFileProvider(Path.Combine(_environment.ContentRootPath, $"..\\{GetType().Assembly.GetName().Name}")));
        }
        else
        {
            // When deploying use the files that are embedded in the assembly.
            options.FileProvider = new CompositeFileProvider(options.FileProvider, 
                                                             new ManifestEmbeddedFileProvider(GetType().Assembly, "wwwroot")); 
        }

        _environment.WebRootFileProvider = options.FileProvider; // required to make asp-append-version work as it uses the WebRootFileProvider. https://github.com/aspnet/Mvc/issues/7459
    }
}

public class ViewConfigureOptions : IPostConfigureOptions<RazorViewEngineOptions>
{
    private readonly IHostingEnvironment _environment;

    public ViewConfigureOptions(IHostingEnvironment environment)
    {
        _environment = environment;
    }

    public void PostConfigure(string name, RazorViewEngineOptions options)
    {
        if (_environment.IsDevelopment())
        {
            // Looks for the physical file on the disk so it can pick up any view changes.
            options.FileProviders.Add(new PhysicalFileProvider(Path.Combine(_environment.ContentRootPath, $"..\\{GetType().Assembly.GetName().Name}")));
        }
    }
}

Ответ 4

Я дошел до этой проблемы давным-давно. Вы можете прочитать статью о том, как включить статические файлы в библиотеку бритв, которая объясняет, как обойти эту проблему.

Решение, которое я нашел после долгих исследований, не далеко от принятого ответа на этот вопрос:

1 Установите файлы как встроенные ресурсы

  <ItemGroup>
    <EmbeddedResource Include="wwwroot\**\*" />
    <EmbeddedResource Include="Areas\*\wwwroot\**\*" />
  </ItemGroup>

2 Включите статические файлы в манифест

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  </PropertyGroup>

3 Найдите соответствующие папки

(Вы можете найти более подробную информацию в связанной статье, даже автоматизировать этот поиск из этой статьи)

// first we get the assembly in which we want to embedded the files
var assembly = typeof(TypeInFeatureAssembly).Assembly;

// we filter only files including a wwwroot name part
filePaths = (from rn in assembly.GetManifestResourceNames().Where(rnn => rnn.Contains(".wwwroot."))
    let hasArea = rn.Contains(".Areas.")
    let root = rn.Substring(0, rn.LastIndexOf(".wwwroot.") + ".wwwroot.".Length)
    let rootPath = !hasArea ? root : root.Substring(0, root.IndexOf(".Areas."))
    let rootSubPath = !hasArea ? "" : root.Substring(root.IndexOf(".Areas.")).Replace('.', '/')
    select  hasArea ? rootSubPath.Substring(1, rootSubPath.Length - 2) : "wwwroot" )
    .Distinct().ToList();

Это извлекает все встроенные ресурсы в папку wwwroot расположенную по соответствующему пути.

4 Добавьте найденные пути к поставщику файлов webroot

var allProviders = new List<IFileProvider>();
allProviders.Add(env.WebRootFileProvider);
allProviders.AddRange(filePaths.Select(t => new ManifestEmbeddedFileProvider(t.Assembly, t.Path)));
env.WebRootFileProvider = new CompositeFileProvider(allProviders);

Ответ 5

Существует более простое решение: в вашем проекте RCL вы можете пометить wwwroot для копирования в каталог публикации:

<ItemGroup>
  <Content Include="wwwroot\**\*.*" CopyToPublishDirectory="Always" />
</ItemGroup>

При развертывании приложения, которое зависит от RCL, все файлы доступны, как и ожидалось. Вам просто нужно быть осторожным, чтобы не было конфликта имен.

Предупреждение: это работает только при развертывании в Azure, но не на локальном компьютере (для этого вам понадобится поставщик).

Ответ 6

Обратите внимание, что предоставленные решения будут работать только для серверных приложений. Если вы используете клиентскую часть Blazor, она не будет работать. Чтобы включить статические активы на стороне клиента Blazor из библиотеки классов бритв, вам необходимо напрямую ссылаться на такие активы, как показано ниже:

<script src="_content/MyLibNamespace/js/mylib.js"></script>

Я потратил впустую часы, пытаясь выяснить это. Надеюсь, это кому-нибудь поможет.