了解如何在 .Net Core 3.0 中构建您的第一个 Blazor WebAssembly 应用程序

2025-05-25

了解如何在 .Net Core 3.0 中构建您的第一个 Blazor WebAssembly 应用程序

在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris

第一次听到这个名字,我以为它听起来像火焰。这真是个奇妙的联想。Blazor 是微软在前端和后端全面使用 .Net 平台和 C# 语言的新方案。让我们在本文中了解如何使用它来构建应用程序。

在本文中,我们将介绍:

  • 为什么选择 Blazor?这是另一个 Silverlight 插件吗?还是其他更好的插件?让我们来一探究竟。
  • 什么?让我们来聊聊 Blazor 是什么,以及不同的托管模型如何让你在前端和后端托管 Blazor。这各有优缺点,我们来聊聊。最终的选择权在你手中。
  • 演示,我们将共同构建一个 Blazor 演示,展示如何使用路由、组件和 HTTP

 参考

您可以查看一些优秀的文档来了解更多信息:

为什么

好的,这是 Blazor 的销售宣传。

Blazor 承诺:

  • 使用 C# 而不是 JavaScript创建丰富的交互式 UI。
  • 共享用 .NET 编写的服务器端和客户端应用程序逻辑。
  • 将UI呈现为 HTML 和 CSS,以获得广泛的浏览器支持,包括移动浏览器。

很好,所以您的意思是,作为 .Net 开发人员,我可能更喜欢一直编写 C#,而不是在 JavaScript 和 C# 之间进行大量上下文切换?

正是如此。此外,您将能够在其中一种托管模型中在后端和前端之间共享代码

分享代码,现在很有趣

这次没有插件。

那么不是新的 Silverlight?

不。

你说,而不是JavaScript,什么意思?

你将能够在任何地方编写 C# 代码。JavaScript 如今非常流行,但说实话,如果你经常使用 C#,那么学习一门新语言、新范式等等就像是一次上下文切换。你还可以完全重用代码。如果你能重用大量的逻辑和模型,并在前端和后端都使用它们,那不是很棒吗?

好的,你引起了我的注意:)

好的,我们继续。

它渲染 WebAssembly

WebAssembly,我想我听说过它,你能告诉我更多吗?

当然,

WebAssembly,也称为 WASM,不要与Wassim混淆;),顺便说一句,你应该关注他,优秀的 OSS 开发人员 :)

你关注了吗?关注了吗?太好了 :)

好了,回到 WASM,它是一个开放标准,为可执行程序定义了一种可移植的二进制代码格式,以及相应的文本汇编语言

等一下,浏览器中的汇编语言一定非常快吧?

是的,没错。这意味着你可以在浏览器中获得高性能的应用程序。

那么不再有 JavaScript 了?

嗯,不,它们注定要共存,这是一个很长的故事。

好的

什么

让我们尝试在这里讨论两个不同的主题:

  • 架构,Blazor 如何工作,如何传达变更等等?让我们来直观地展示一些漂亮的图片。
  • 托管模型,您可以在客户端和服务器端托管 Blazor,让我们尝试解释一下它们之间的区别

建筑学

首先要明确一点。Blazor 是一个客户端 Web UI 框架。它负责处理用户交互并在需要时渲染更新。与 Angular、React 和 Vue 类似,Blazor 的组件渲染在 HTML 页面上:

组件不会直接渲染到 DOM 中,而是渲染到 DOM 的虚拟/内存表示中,称为RenderTree。当发生更改时,会计算diff并将其应用于 DOM,如下图所示:

托管模型

关于 Blazor,我们首先需要了解的是它可以托管在不同的地方。它可以像 ASP.NET Web Forms 应用一样托管在 IIS 中。Blazor 应用还可以通过以下方式之一托管:

  • WebAssembly 上的浏览​​器中的客户端。
  • ASP.NET Core 应用程序的服务器端。

客户端

Blazor WebAssembly 应用直接在基于 WebAssembly 的 .NET 运行时的浏览器中执行。Blazor WebAssembly 应用的功能与 Angular 或 React 等前端 JavaScript 框架类似。

根据您告诉我的有关 WebAssembly 的信息,听起来它真的很快?

是的。或者说,Blazor 仍在开发中,并且速度会越来越快。

还有什么?

您无需编写 JavaScript,只需编写 C# 即可。.NET 运行时会随应用程序程序集和任何所需依赖项一起下载。无需任何浏览器插件或扩展程序。

前端使用 C#,很棒 :) 并且没有插件,所以它不是下一个 Silverlight?

正确的。

这肯定有一个缺点,我肯定不能在浏览器中使用整个 .Net 平台?

嗯,您可以使用.Net 标准库,但当然,应用程序是在浏览器安全沙箱中执行的。

什么意思?

您无法访问文件系统或打开任意网络连接

哦,好的,是的,这很有道理。

还有其他问题吗?

是的,实际上,部署怎么样?

部署,可以使用GitHub Pages或者静态站点托管。

好的 :)

服务器端

在这种托管模型中,组件和渲染输出彼此解耦。因此,我们有两个不同的进程,一个负责组件,另一个负责 UI 更新。

在服务器端方法中,组件在服务器上运行,而不是像以前的托管模型那样在客户端运行。因此,要使更改能够到达服务器并进而到达组件,需要实时连接。之后,它与以前的托管模型的思路基本相同,我们渲染组件,生成 UI diff 并将其发送到浏览器,最终将其应用于 DOM。

我应该选择哪种托管模式?

现在我们提出的问题是正确的。让我们来探讨一下每种托管模式的优缺点。

客户端模型

优点

  • 无需服务器端依赖,可独立运行,无需服务器

  • 卸载服务器,客户端承担更多责任并完成所有工作

缺点

  • 受浏览器限制,应用程序的功能受浏览器限制

  • 需要支持 WebAssembly,你需要支持 WebAssembly 的硬件和软件

  • 尺寸,应用程序的有效载荷更大,因此加载应用程序需要更长的时间

  • .Net 的限制、运行时和工具支持不如服务器端版本成熟

服务器端模型

优点

  • 尺寸,尺寸更小,应用程序加载速度更快

  • 工具/调试,它在服务器上运行,因此它充分利用现有的工具和调试

  • 可以使用完整的.Net,可以使用任何与.Net Core兼容的.Net API

缺点

  • 延迟,每次交互都是一次网络调用
  • 不离线,如果客户端无法连接到服务器,应用程序将停止工作
  • 可扩展性,服务器必须管理客户端连接和客户端状态

正如您上面所看到的,有不同的优点和缺点,您必须决定您可以接受什么。

演示

理论已经足够了,你想看一些代码吗?

是的,请

好的,我们将执行以下操作:

  • 先决条件,让我们安装所需的库,例如 .Net Core 3.0 和构建 Blazor 应用程序所需的模板
  • 组件,组件是一个核心概念,让我们学习如何定义组件以及如何使用输入和输出
  • HTTP,让我们学习如何使用 HTTP
  • 路由,一个应用程序几乎总是由多个页面组成,让我们学习如何设置路由并在页面之间导航

先决条件

由于 Blazor 不断更新,请确保您查看最新的安装说明:

https://docs.microsoft.com/en-us/aspnet/core/blazor/get-started?view=aspnetcore-3.1&tabs=visual-studio

您需要下载 .Net Core 3.0。请查看此链接,找到适合您操作系统的发行版。

https://dotnet.microsoft.com/download/dotnet-core/3.0

此外,你还需要一些模板,以便搭建 Blazor 应用的框架。打开终端并输入

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview9.19465.2

完成这些之后,我们将获得一些 Blazor 特定的模板,即blazorwasmblazorserver。让我们像这样创建一个项目blazorwasm

dotnet new blazorwasm -o blazor-web

这应该为你搭建一堆文件,它看起来应该像这样:

现在我们有了一个项目,让我们首先编译它:

dotnet build

这不仅会编译它,还会隐式地获取所有需要的 NuGet 包。你应该看到类似这样的内容:

接下来让我们运行这个例子并看看我们得到了什么:

dotnet run

您应该在终端中看到以下打印:

接下来,我们去http://localhost:5000

太棒了,一切正常!:)

接下来让我们了解所有的构建模块。

成分

Blazor 是一个以组件为中心的框架。这意味着我们应用程序中的所有内容都是由组件组成的。

让我们试着弄清楚我们的应用程序是如何组成的。组件被称为App,可以在文件 中找到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>

以上告诉我们两件事。

  • 正常路由,正常路由由组件负责Found。它使用MainLayout组件设置了一个DefaultLayout
  • 未找到,组件NotFound处理任何未定义的路由并输出错误文本

主布局

接下来我们来看一下这个MainLayout组件。它可以在下面找到Shared/MainLayout.razor,如下所示:

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

上面告诉我们我们有两个部分sidebarmainsidebar使用组件呈现导航菜单NavMenumain使用呈现内容@Body

索引组件

接下来,让我们通过查看一个非常简单的组件来尝试理解组件Index,该组件存储在文件中Pages/Index.razor

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

文件的顶部@page告诉我们它处理的是哪个路由,在本例中/是默认路由。然后是一段 HTML。

柜台

该组件可以在下方找到Pages/Counter.razor,其外观如下:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

这有点意思,因为它演示了如何显示值和设置方法。在顶部,我们可以看到它处理了路由/counter

currentCount接下来,我们看看如何使用 Razor 指令输出变量@

之后,我们看看如何设置方法IncrementCount()来处理按钮的点击:

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

最后,我们使用 Razor 指令@code编写一些 C# 代码

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

正如您在上面看到的,我们声明了变量currentCount和方法IncrementCount(),看起来几乎可行,对吧?:)

组件通信

组件的意义在于能够将你的应用拆分成不同的组件,每个组件都有各自的职责。因此,让我们像这样将Counter组件添加到我们的组件中:Index

<Counter />

保存并重新启动应用程序。你的浏览器现在应该如下所示:

组件输入

智能组件和组件之间通常存在区别智能组件会保存状态,而哑组件只会渲染数据。组件的一个例子ProductDetail只列出所传入数据的组件。那么,我们该如何设置呢?让我们按照以下步骤操作:

  1. 在目录ProductDetail.razor创建文件Pages
  2. 创建呈现标题和描述的内容
  3. 将组件放置Index在组件中

首先,让我们添加一些代码ProductDetail.razor

<h2>@title</h2>  
<div>@description</div> 

@code {
  string title = "Product detail";
  string description ="description";
}

接下来,我们将其放入Index组件中<ProductDetail/>,然后保存并启动我们的应用程序。它现在应该如下所示:

我们不应该依赖内部的静态数据而应该ProductDetail使用input属性。

那么我们该怎么做呢?

我们采取以下措施:

  1. 对名为的变量使用装饰器Parameter
  2. 使变量public成为property
  3. 测试一下

在我们的ProductDetail组件中,将其更改为以下内容:

<h2>@title</h2>  
<div>@description</div> 

@code {
  [Parameter]
  public string title { get; set; } = "Product detail";

  [Parameter]
  public string description { get; set; } ="description";
}

我们已经完成了步骤 1+2,现在让我们通过在组件中分配值来尝试一下Index

<ProductDetail title="Tomato" description="It's a red vegetable" />

浏览器中的结果如下:

有效:)

组件输出

组件输出是什么意思?我们指的是如何从子组件向父组件进行通信。答案是通过函数。也许我们应该回到我们这样做的场景。让我们来谈谈Todo 应用。Todo应用是新的hello world,是一个入门应用,我们可以在其中学习所选框架的一些基础知识。原因之一是它涵盖了很多很棒的概念,比如显示列表,而且当我们通常单击列表中的特定项目时,我们可能还希望从子组件向父组件进行通信。

可以像这样创建一个简单的列表组件:

// Todos.razor

<div>
  <h2>Todos</h2>
  @foreach (var todo in todos)
  {
      <div>@todo.title</div>
  }
</div>

@code {
  public class Todo {
    public string title { get; set; }
  }

  List<Todo> todos = new List<Todo>(){ new Todo { title="clean" }, new Todo { title="shop" } };
}

太棒了,它展示了@foreach助手组件。但是,它没有展示从子组件到父组件的组件通信。所以我们来为此创建一个新组件,并将其命名为Products。我们定义如下:

// Products.razor

<h2>Products</h2>

You clicked: @message

@foreach (var item in products)
{
    <ProductDetail OnClick="Clicked"  title="@item.Title" description="@item.Description" />
}

@code  {
  string message = "";

  List<Product> products  = new List<Product> 
  {
    new Product(){  Title = "book", Description = "a Book" },
    new Product(){  Title = "DVD", Description = "a DVD" }
  };

  void Clicked(string name) 
  {
    message = name;
  }

  public class Product 
  {
    public string Title { get; set; }
    public string Description { get; set; }
  } 
}

让我们放大看看有趣的部分:

@foreach (var item in products)
{
    <ProductDetail OnClick="Clicked"  title="@item.Title" description="@item.Description" />
}

我们正在渲染一些ProductDetail实例,并分配属性OnClick并赋予其值Clicked,这是一个如下所示的函数:

void Clicked(string name) 
{
  message = name;
}

一个非常简单的函数,以string作为输入参数。

然而,为了理解如何ProductDetail与父级进行通信,我们需要查看组件定义:

// ProductDetail.razor

<div>
  <span>@title</span>
  <span>@description</span>
  <button @onclick="@(() => OnClick.InvokeAsync(title))" >Click me</button>
</div>

@code {
  [Parameter]
  public string title { get; set; } = "Product detail";

  [Parameter]
  public string description { get; set; } ="description";

  [Parameter]
  public EventCallback<string> OnClick { get; set; }

}

这里有两部分,第一部分是参数的定义OnClick,如下所示:

[Parameter]
public EventCallback<string> OnClick { get; set; }

看一下上面的类型EventCallback<string>,这意味着在调用时它需要传递一个string作为参数。

另一个难题是如何调用它?答案是:

 <button @onclick="@(() => OnClick.InvokeAsync(title))" >Click me</button>

从上面我们看到,我们调用时会将组件的标题作为参数,这就是它的工作原理。如果我们愿意,也可以将一些更独特的参数传递给父组件,例如InvokeAsync()但我们需要使用这样的参数来扩展组件。stringid

处理 HTTP

好的,我们已经讲了很多,包括如何开始,布局如何工作,如何使用组件进行架构以及它们如何通信。还剩下什么?答案就是“很多”。在结束本文之前,我只想再展示一件事,那就是如何使用 HTTP。

为此,我们将创建一个组件Planets。它将与外部端点通信,获取数据并将其转换为我们可以渲染的内容。让我们先看一下它的定义,然后解释一下具体实现:

@inject HttpClient Http

<div>
  @if(request == null) 
  {
    <div>Loading...</div>
  } 
  else 
  {
    <div>
    @foreach (var planet in request.results)
    {
        <div>@planet.Name</div>
    }
    </div>
  }
</div>

@code {
  PlanetRequest request = null;

  protected override async Task OnInitializedAsync()
  {
    request = 
        await Http.GetJsonAsync<PlanetRequest>("https://swapi.co/api/planets");
  }

  public class PlanetRequest {
    public Planet [] results { get; set; }
  }

  public class Planet {
    public string Name { get; set; }
  }
}

有三件有趣的事情正在发生:

  • 注入 HttpClient,这是一个帮助我们进行 HTTP 调用的服务
  • 调用 HttpClient,我们使用该方法GetJsonAsync()从定义的端点获取 JSON
  • 创建结果类型,我们正在创建PlanetRequest基于传入的 JSON 结构的类型,该类型能够挑选出在属性上找到的相关数据results

注入 HttpClient

这是在页面顶部使用以下代码完成的:

@inject HttpClient Http

我们正在请求一个类型的实例HttpClient并将其命名为Http

调用 HttpClient

这里我们主动获取数据。我们通过定义一个OnInitializedAsync()保证在初始化时运行的生命周期方法来实现这一点。

protected override async Task OnInitializedAsync()
  {
    request = 
        await Http.GetJsonAsync<PlanetRequest>("https://swapi.co/api/planets");
  }

我们可以看到上面我们将其声明为async因为我们用来await等待Task解析我们的数据。

创建结果类型

最后,我们定义了一个结果类型PlanetRequest,它模拟了传入的 JSON 的样子,其形状如下:

{
  "results": []
}

 概括

我们现在想讲的就这些,不然就写成一本书了。关于 Blazor 还有很多内容可以讲,但这应该是一个简洁明了的介绍,希望能传达出 Blazor 是一个面向组件的框架,它允许你使用 C# 编写,同时仍然使用你熟悉的 HTML 和 CSS 等元素。

Blazor改变了游戏规则,这只是一个开始。:)

文章来源:https://dev.to/dotnet/blazor-the-future-of-net-web-apps-a-first-look-m8
PREV
对于回归的 .NET 开发人员来说,C# 的新功能非常棒
NEXT
如何为生产应用程序构建大规模 Vuex 存储