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

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.


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

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


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>