# 是否可以在应用的一部分使用 Blazor?

Blazor (opens new window) 被设计成一个可以创建完整网页应用的平台,你可以看到最近我们为我的博客创建的独立搜索网站就是一个 Blazor实践 (opens new window)。 但是就像你工具箱里的任意一个工具一样,它可能不总是适用于你的工作。

以我的博客举例,它更多的是一个只读形式的内容存储在Github (opens new window)上的网站,它将markdown格式文件转换成HTML文件。 并不确定,我们可能把它做成一个Blazor WASM应用,使用一个.NET的markdown库去动态生成页面,但这可能对于运行我的网站并带给读者一个良好体验来说是一个不高效的方法。

但是如果我们想要集成一个我们已有的搜索应用,我们又该如何决策呢?

# 了解Blazor是如何开始的

想知道我们要如何在另一个应用里运行Blazor WebAssembly。我们需要先学习一下Blazor WebAssessbly应用是如何运行的。

当你创建一个新的项目,里面包含一个你或许从未打开过的文件 wwwroot/index.html , 但这是拼图很重要的一片。这个文件看起来就像:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Project Name</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>Loading...</app>

    <script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

实际上,它非常简单,我们需要的重要代码是这两行:

<app>Loading...</app>

<script src="_framework/blazor.webassembly.js"></script>

在快速找到<app>元素之前,我们首先看一下JavaScript文件。你或许会注意到这个文件并没有出现在硬盘里,这是因为它是属于构建输出的部分。你可以在ASP.NET Core的Github仓库src/Components/Web.JS/src/Boot.WebAssembly.ts (opens new window)里找到它的源码(at the time of writing anyway)。此文件与Blazor服务器共享一部分内容,但是与使用MonoPlatform (opens new window)的最大区别是它进行一堆WASM交互操作。

这个文件至关重要,没有它你的Blazor应用将无法启动,它先负责(通过注入一个脚本文件到DOM (opens new window))初始化托管在Mono的WASM环境。然后它使用另一个生成的文件 _framework/blazor.boot.json去找出需要将哪些.NET dll文件加载到Mono/WASM环境中。

因此你需要把这个js文件包含在内,同时把_framework文件夹放在根路径下以确保它可以找到JSON文件(见 此评论 (opens new window))。

# 延迟加载Blazor

我在钻研源代码时发现的一个有趣的题外话是,你可以通过添加autostart="false"<script>标签里来延迟Blazor的加载。就像这里 (opens new window)提到的一样,然后使用JavaScript调用window.Blazor.start()以启动Blazor应用。

我不打算使用它来进行这种集成,但很容易理解你可以用一个用户启动的初始化过程,而不是在页面中加载。

# 放置你的Blazor应用

既然我们已经明白了是什么使Blazor应用开始运行,那么我们如何了解它出现在DOM的何处呢?那就是我们HTML文件里<app>元素的用处,但是Blazor又怎么知道它呢?

事实证明,那是我们通过Startup 类控制的一些事情:

using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace DemoProject
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IComponentsApplicationBuilder app)
        {
            app.AddComponent<App>("app");
        }
    }
}

可以看到在14行的位置我们使用了AddComponent并指定一个DOM selector app。这就是它如何知道应用启动时DOM里有哪些元素。这些操作你是可以修改的,或许修改selector为一个DOM元素的ID或者一个<div>,又或者是任意你想要的内容,但那都不重要,因此我将它叫做<app>

题外话:我并没有进行尝试,但考虑到你指定了DOM元素和入口组件(通过泛型,上面例子中指向App.razor)你可以在页面上运行多个Blazor应用程序。至于为什么这个么做我不知道,但理论上你是可以这么做的。

# 托管 Blazor

当托管一个Blazor WASM有一些可选项 (opens new window)的时候,我想专注于Azure Storage 静态站点 (opens new window)的方法,这也是我的博客托管的方法。

首先我们要做的事情是使用命令dotnet publish --configuration Release发布应用。然后我们可以得到bin/Release/{TARGET FRAMEWORK}/publish/{ASSEMBLY NAME}/dist/_framework文件夹的内容,包括:blazor.boot.json, blazor.server.js, blazor.webassembly.js,一个叫做_bin的文件夹和一个叫做wasm的文件夹。

我们将拷贝这个_framework文件夹并防止在静态站点的根目录下,维护所有的路径,以确保Blazor可以启动。

注意:根据文档使用dotnet run托管站点时你是可以修改content-rootpath-base,但我没有发现他们发布后也可以正常工作。此外,Hugo非常积极使用绝对路径,因此我发现最简单的办法是把我的WASM文件放在和dotnet run使用的相同的结构中。

由于这是一个搜索应用,我们来创建一个新的搜索 (opens new window)页,并把它放在我们需要的HTML文件里:

<app></app>

<script src="/_framework/blazor.webassembly.js"></script>

现在生成你的静态站点(或者任意你选择的托管方式)并导航至/search路径下。

如果一切都没有问题你将会收到一个错误页面!

抱歉,这个地址没有任何内容。

D'oh

# Blazor 路由

如果你记得我们上一篇博客 (opens new window)我们了解了Razor组件中的@page指令。此处你指定的路由对应的页面将匹配已有的@page "/"。但是我们现在的路由是/search,并且Blazor的路由引擎找到了URL并执行你的App.razor组件:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

Router无法找到一个匹配的路由去使用RouteView,它就会被丢往NotFound页面,这就是我们收到这个错误页面的原因。

别担心,这个很容易修复,只需要更新@page指令以匹配您希望它在已发布网站中匹配的路由 ,或者简化 App.razor 以不关心路由。

一旦一个新的版本发布完成,并且文件拷贝完成,这个页面就会正常呈现。

# 结论

Blazor是一个构建应用非常棒的方法,但是相比创建应用程序,在提前生成静态内容并使用Blazor增强现有应用程序更有价值。

我们研究了一下在HTML页面中运行Blazor应用所需要的重要文件,同时研究了将其放入其它类型应用需要什么。