如何打造单文件 Blazor Server 应用

前言

上次,我们介绍了《如何打造单文件前后端集成 ASP.NET Core 应用》。

但是,网友说,对于 Blazor Server 项目此方法无效。

于是,我们测试了一下:.

BlazorApp1.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
 <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.8" />
  </ItemGroup>
 <ItemGroup>
  <EmbeddedResource Include="wwwroot\**" />
 </ItemGroup>
</Project>

Program.cs

public static void Main(string[] args)
{
...
    app.UseFileServer(new FileServerOptions
    {
        FileProvider = new ManifestEmbeddedFileProvider(
            typeof(Program).Assembly, "wwwroot"
        )
    });

    app.UseStaticFiles();
...
}

发布成单文件后运行,发现除了BlazorApp1.styles.css,其他静态文件都可以正常加载:

如何打造单文件 Blazor Server 应用

BlazorApp1.styles.css 在哪里?

查找BlazorApp1.styles.css文件,我们发现它并不在项目源码下的wwwroot目录,而是在obj\Debug\net6.0\scopedcss\bundle目录。

这说明,它是在编译中生成的。

查看官方文档,原来这是被称为CSS 隔离的特性。

CSS 隔离

若要定义组件特定的样式,需要创建一个同名的.razor.css文件,例如NavMenu.razor.css

定义的样式仅应用于对应组件的呈现输出。在应用的其他位置定义的任何同名 CSS 声明都不会与组件的样式冲突。

CSS 隔离在生成时发生。Blazor 会重写 CSS 选择器以匹配组件呈现的标记。重写的 CSS 样式被作为静态资产捆绑和生成。

也就是说,生成时会将所有组件样式合并到{ASSEMBLY NAME}.styles.css文件中,其中占位符 {ASSEMBLY NAME} 是项目的程序集名称。

解决思路

既然BlazorApp1.styles.css是编译中生成的,那么只需要在编译时,将生成的 css 文件复制到wwwroot目录下,再打包到资源文件中,不就可以实现吗?

但是,查看 MSBuild 生成日志(具体使用方法请查看我以前的文章)。我们发现,生成 CSS 隔离文件的 Target _GenerateScopedCssFiles 是在编译之后执行的,而生成资源文件的 Target _GenerateEmbeddedFilesManifest是在编译之前就执行了。

如何打造单文件 Blazor Server 应用

所以打包到资源文件时,根本拿不到BlazorApp1.styles.css文件。看来此路不通!

但是转念一想,为什么一定要拿本次生成的BlazorApp1.styles.css文件呢?我们拿上次生成的文件不就行了!!!

实现

修改代码,指定打包的资源文件源地址:

BlazorApp1.csproj

<ItemGroup>
    <EmbeddedResource Include="bin\Release\net6.0\publish\wwwroot\**" />
</ItemGroup>

Program.cs

app.UseFileServer(new FileServerOptions
{
    FileProvider = new ManifestEmbeddedFileProvider(
        typeof(Program).Assembly, "bin\\Release\\net6.0\\publish\\wwwroot"
    )
});

然后点击 2 次"发布"按钮,第一次点击的目的是为了生成需要打包到资源文件的"bin\Release\net6.0\publish\wwwroot"目录。

再次运行单文件,成功!

结论

本次,我们使用了一个小 trick,将生成后的文件打包成资源文件。