Can Razor Class Library pack static files (js, css etc) too?

我的梦境 提交于 2020-01-19 04:50:48

问题


Maybe duplicate of this already, but since that post does not have any answer, I am posting this question.

The new Razor Class Library is awesome, but it cannot pack libraries files (like jQuery, shared CSS).

Can I somehow reuse the CSS across multiple Razor Page projects, either using Razor Class Library or anything else (my purpose is that, multiple websites use the same CSS, and a single change applies to all projects).

I have tried creating the folder wwwroot in the Razor Class Library project, but it does not work as expected (I can understand why it should not work).


回答1:


You need to embed your static assets into your Razor Class Library assembly. I think the best way to get how to do it is to take a look at ASP.NET Identity UI source codes.

You should take the following 4 steps to embed your assets and serve them.

  1. Edit the csproj file of your Razor Class Library and add the following lines.

     <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. In your Razor Class Library, create the following class to serve and route the assets. (it assumes your assets are located at wwwroot folder)

    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. Make the dependent web application to use your Razor Class Library router. In the ConfigureServices method of Startup Class, add the following line.

    services.ConfigureOptions(typeof(UIConfigureOptions));
    
  4. So, now you can add a reference to your file. ( let's assume it's located at wwwroot/js/app.bundle.js).

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



回答2:


Ehsan answer was correct at the time of asking (for .NET Core 2.2), for .NET Core 3.0, RCL can include static assets without much effort:

To include companion assets as part of an RCL, create a wwwroot folder in the class library and include any required files in that folder.

When packing an RCL, all companion assets in the wwwroot folder are automatically included in the package.

The files included in the wwwroot folder of the RCL are exposed to the consuming app under the prefix _content/{LIBRARY NAME}/. For example, a library named Razor.Class.Lib results in a path to static content at _content/Razor.Class.Lib/.




回答3:


Thanks for the helpful information Ehsan.

Here is an expanded version to allow for debugging javascript and typescript as well has being able to make changes without recompile. TypeScript debugging isn't working in Chrome but is in IE. If you happen to know why please post a response. Thanks!

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:


In .NET Core 3.1, RCL includes assets inside wwwroot folder to consuming app under _content/{LIBRARY NAME}.

We can change _content/{LIBRARY NAME} path to different path name by editing RCL project propeties and placing StaticWebAssetBasePath.

PropertyGroup>
    <StaticWebAssetBasePath Condition="$(StaticWebAssetBasePath) == ''">/path</StaticWebAssetBasePath>
  </PropertyGroup>

Now you can access files with /path/test.js.




回答5:


I reached this issue long time ago. You may follow the article how to include static files in a razor library that explains how to get around this issue.

The solution I reached after a lot of investigation is not far from the accepted answer in this question:

1 Set the files as embedded resources

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

2 Include static files in the manifest

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

3 Find the relevant folders

(you can find more details in the linked article, even automatize this search from this article )

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

This extracts all embedded resources in a wwwroot folder located in a relevant path.

4 Add the found paths to the webroot file provider

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




回答6:


There's a simpler solution: in your RCL's project, you can flag the wwwroot to be copied to the publish directory:

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

When you deploy an app that depends on the RCL, all the files are accessible as expected. You just need to be careful that there's no naming conflict.

Caveat: this only works when deploying on Azure, but not on your local machine (you'll need a provider for that).




回答7:


Please take note that this solutions provided will only work for server side applications. If you are using Blazor client side it will not work. To include static assets on Blazor client side from a razor class library you need to reference directly the assets like this:

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

I wasted hours trying to figure out this. Hope this helps someone.



来源:https://stackoverflow.com/questions/51610513/can-razor-class-library-pack-static-files-js-css-etc-too

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!