Razor 类库也可以打包静态文件(js、css 等)吗?

Posted

技术标签:

【中文标题】Razor 类库也可以打包静态文件(js、css 等)吗?【英文标题】:Can Razor Class Library pack static files (js, css etc) too? 【发布时间】:2019-01-07 16:20:24 【问题描述】:

也许duplicate of this 已经有了,但由于该帖子没有任何答案,所以我发布了这个问题。

新的Razor Class Library 很棒,但它不能打包库文件(如 jQuery、共享 CSS)。

我能否以某种方式在多个 Razor 页面项目中重用 CSS,无论是使用 Razor 类库还是其他任何东西(我的目的是,多个网站使用相同的 CSS,并且单个更改适用于所有项目)。

我尝试在 Razor 类库项目中创建文件夹 wwwroot,但它没有按预期工作(我可以理解为什么它不应该工作)。

【问题讨论】:

我不知道每次看到 RCL 是否可能,但它可能 - 需要一些努力 - 使用常规类库。 OpenIddict 做过一次(在库中嵌入静态文件)。请参阅我的answer here(它有点过时,应该仍然适用或指向正确的方向)。基本上是具有特定文件提供程序的静态文件中间件,EmbeddedFileProvider) @Tseng 太棒了。是的,一旦您提到文件中间件,我现在就明白了。谢谢你:) 谢谢。我的想法并不是真正的“嵌入”,而是从共享文件夹中提供静态文件(比如说../../shared_wwwroot/)。这种方式改变一个地方并适用于所有网站。 假设您不打算使用 Docker 或分布式应用程序。当它们在不同的机器上运行时,这种方法有其缺陷。实际上,共享 js 库和通用 css(即 bootstrap、jquery-ui css)的最佳方式是使用 CDN 网络,例如 Akamai 或 Azure CDN)。这样,您不仅可以从任何地方获得/可链接的文件,还可以提高性能。如果用户 A 访问了加载 jQuery 3.0 的其他网站(不属于您),然后访问了您的也使用 jQuery 3.0 的网站,则他没有额外的下载,因为它缓存在他的浏览器中 这增加了您网站首次访问的责任并减少了整体流量,并且由于其 CDN 始终使用最近/最快的镜像来发送静态文件 【参考方案1】:

Ehsan 的回答在询问时是正确的(对于 .NET Core 2.2),对于 .NET Core 3.0,RCL can include static assets 毫不费力:

要将伴随资产包含在 RCL 中,请在类库中创建一个 wwwroot 文件夹,并在该文件夹中包含所有必需的文件。

打包 RCL 时,wwwroot 文件夹中的所有伴随资产都会自动包含在包中。

包含在 RCL 的 wwwroot 文件夹中的文件在前缀 _content/LIBRARY NAME/ 下暴露给消费应用程序。例如,名为 Razor.Class.Lib 的库会生成 _content/Razor.Class.Lib/ 处的静态内容路径。

【讨论】:

所以这意味着如果我们在 RCL 的组件中使用图像,我们还需要添加这个 _content/LIBRARY NAME ?这是非常不直观和出乎意料的。我希望框架能够自动翻译此内容以供 RCL 本身内部使用。 正确,但我认为还不错。我通常只是重命名 RCL 程序集名称并使用像“Common”这样的短名称 Intellisense 建议将img/foo.png 用于MyRazorClassLibrary/wwwroot/img/foo.png。我同意使用 _content/MyRazorClassLibrary/img/foo.png 引用资产不是很直观有没有人有任何技巧可以像这些路径一样进行智能感知或自动将丑陋的前缀应用于所有资产? 由于某种原因,这对我不起作用,即使我阅读并遵循了相同的文档。 @jbyrd 您是否碰巧找到了原因?如果您需要 RCL 中的静态文件,但又不想在使用项目中显式引用它们,则此解决方案似乎不起作用。【参考方案2】:

您需要将静态资源嵌入到 Razor 类库程序集中。我认为最好的方法是查看ASP.NET Identity UI source codes。

您应该采取以下 4 个步骤来嵌入您的资产并为其提供服务。

    编辑 Razor 类库的 csproj 文件并添加以下行。

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

    在您的 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);
        
    
    

    使依赖的 Web 应用程序使用您的 Razor 类库路由器。在 Startup 类的 ConfigureServices 方法中,添加以下行。

    services.ConfigureOptions(typeof(UIConfigureOptions));
    

    所以,现在您可以添加对文件的引用。 (假设它位于 wwwroot/js/app.bundle.js)。

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

【讨论】:

哇,这是新东西。抱歉这个问题很久以前就被问过了,所以现在我还没有时间测试你的答案。你能确认它有效吗?如果是这样,会标记它。看起来很不错! 终于有了另一个可以测试的项目。工作得很好。 asp-append-version="true" 无法正常工作,但这是一个简单的问题。 如果我可以问,你做了什么来解决asp-append-version="true" 不工作的问题? @Chris.ZA 我用这个:***.com/questions/53518244/… @Chris.ZA 如果您仍然感兴趣并且可以使用 .NET Core 3,如果您使用文档中所写的方式,asp-append-version 可以工作。【参考方案3】:

在 .NET Core 3.1 中,RCL 将 wwwroot 文件夹中的资产包含在 _content/LIBRARY NAME 下的消费应用程序中。

我们可以通过编辑 RCL 项目属性并放置 StaticWebAssetBasePath,将 _content/LIBRARY NAME 路径更改为不同的路径名。

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

现在您可以使用 /path/test.js 访问文件了。

【讨论】:

嗨,欢迎来到 ***。感谢您的更新,但您能否提供演示代码以及相关文档(如果有)? 只是想说这对我有用。我基本上将 StaticWebAssetBasePath 行添加到 RCL 项目文件中的第一个 PropertyGroup 元素。请注意,路径“_content/LIBRARY NAME”仍然可以使用。我发现与此相关的最佳参考:github.com/dotnet/aspnetcore/issues/14568 这听起来不错,但似乎对我没有任何作用 (好吧,它似乎确实做了某事,因为它破坏了我的链接,但它不允许我访问我指定的路径上的资源)。 如果有帮助,这是我的脚本标签的格式 【参考方案4】:

感谢 Ehsan 提供的有用信息。

这是一个扩展版本,允许调试 javascript 和 typescript 并且能够在不重新编译的情况下进行更改。 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")));
        
    

【讨论】:

【参考方案5】:

请注意,提供的此解决方案仅适用于服务器端应用程序。如果您使用的是 Blazor 客户端,它将无法正常工作。要在 Blazor 客户端从 razor 类库中包含静态资产,您需要像这样直接引用资产:

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

我浪费了好几个小时试图弄清楚这一点。希望这对某人有所帮助。

【讨论】:

【参考方案6】:

有一个更简单的解决方案:在您的 RCL 项目中,您可以标记 wwwroot 以复制到发布目录:

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

当您部署依赖于 RCL 的应用程序时,所有文件都可以按预期访问。您只需要注意没有命名冲突。

警告:这仅适用于在 Azure 上部署,而不是在您的本地计算机上(您需要一个提供程序)。

【讨论】:

【参考方案7】:

补充@revobtz的答案

我的项目文件的名称与我的命名空间不同,所以我发现应该是项目文件的名称而不是命名空间:

<script src="_content/<Name of project file>/js/mylib.js"></script>

【讨论】:

【参考方案8】:

我很久以前就遇到过这个问题。 您可以关注文章how to include static files in a razor library,该文章解释了如何解决此问题。

我经过大量调查得出的解决方案与这个问题的公认答案相差不远:

1 将文件设置为嵌入资源

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

2 在清单中包含静态文件

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

3 找到相关文件夹

(您可以在链接的文章中找到更多详细信息,甚至可以从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();

这会提取位于相关路径中的 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);

【讨论】:

这里好像少了什么。无法为程序集“MyLib”加载嵌入文件清单“Microsoft.Extensions.FileProviders.Embedded.Manifest.xml”

以上是关于Razor 类库也可以打包静态文件(js、css 等)吗?的主要内容,如果未能解决你的问题,请参考以下文章

iOS - 静态类库 打包 C,C++文件及和OC混编

动态script标签同步加载 ps:无打包编译,静态实现静态资源入口动态配置,无编译打包静态资源添加版本号

webpack打包Js文件

webpack打包的CSS含有两个相同的引入?

MVC Razor,包含来自另一个项目的 JS/CSS 文件

webpack编译打包基本配置