2018 年 10 月

33 卷,第 10

此文章由机器翻译

领先技术-在工作 Blazor:事件、 绑定和组合

通过Dino Esposito

Dino Esposito我承认我从未遇到过的 Angular 和 React,两个非常受欢迎的框架用于编写现代 Web 前端十分推崇。我不想将 spark 任何引起热烈的争论,但我认为是安全状态,它是应用程序服务器上所有有关多少工作的执行,并在客户端上可执行多少。很明显,以 Web 的客户端为中心的任何解决方案,并且在响应速度更快的可能性,可能更多重要的是,大大降低在服务器上有影响。但是,没有学习曲线和已使用 JavaScript,或至少还要等 TypeScript 要完成的额外的编程工作。

Blazor 是感兴趣的并受到欢迎尝试使开发人员能够构建客户端为中心的应用程序使用,大多数情况下,C# 和.NET 运行时。随着 Blazor 的发展,将添加越来越多的功能以使它提供一组的功能为 Angular、 React 或 Vue 几乎相同。最后,编写 Blazor 应用程序将一种拼结在一起,HTML 和 CSS 使用 C# 代码中,Razor 模板语言和 JSON 数据从下载某些 HTTP 终结点。

Blazor 的绝妙功能是一种面向对象的 C# 等语言更易于使用比使用 JavaScript 时甚至中等表达复杂的业务逻辑。此外,Blazor 提供客户端为中心的编程环境以是更加熟悉比,说,Angular 的 ASP.NET 开发人员社区。

这将需要 Blazor 成熟和已建立的框架,与竞争的时间,但 C# 代码通过开放的技术 (WebAssembly) 在浏览器中运行而不一种专有插件梦想处于活动状态。

在以前的专栏中 (msdn.com/magazine/mt829756),我看到 Blazor 体系结构的要点,并显示数字时钟和一个计时器,以更新其基础的基本示例。在本专栏中,我将移向期望客户端为中心的应用程序中执行的核心任务的类型。此演示将使用用户界面组件、 参数、 事件、 JavaScript 互操作性和数据绑定。图 1最终结果的概述: 使用自动完成功能以接受一个字符串的窗体上后端运行查询,获取 JSON 数据并刷新 UI。

示例应用程序操作
图 1 示例应用程序操作

创建项目

为 ASP.NET Core 托管应用程序生成示例项目。该解决方案由三个项目 — 客户端 Blazor 项目、 远程操作的 Web API 服务器和一个可选的共享的库链接到这两个项目。此共享的库是所有数据传输对象和客户端和服务器上使用模型视图的理想位置。Visual Studio 中的解决方案的默认配置将 ASP.NET Core 项目设置运行程序。

让我们看看在启动类的内容。除了规范的内容设置 MVC 路由器、 错误处理、 日志记录、 压缩和任何其他可能需要它具有要使用 Blazor 即席中间件:

public void Configure(IApplicationBuilder app)
{
  app.UseResponseCompression();
    ...
  app.UseBlazor<Client.BlazorProgram>();
}

类型作为参数传递到中间件为 Blazor 应用程序中的程序入口点的类型。不用说,ASP.NET Core 服务器应用程序 (作为普通 Web API) 必须具有对 Blazor 项目的引用。尽管这不是严格要求,建议应用程序使用压缩来减少通过网络的流量。压缩服务配置为在启动类的 ConfigureServices 方法中:

services.AddResponseCompression(options =>
{
  options.MimeTypes =
    ResponseCompressionDefaults.MimeTypes.Concat(new[]
    {
      MediaTypeNames.Application.Octet,
      WasmMediaTypeNames.Application.Wasm,
    });
});

不需要有代码检索并提供到客户端 Blazor 前端的 JSON 数据所需的层以外的 Web API 项目中的其他部分。在本文中使用的 Web API 返回有关国家/地区的世界的信息。为内部的 JSON 资源,项目中嵌入的国家/地区列表和存储库类会处理缓存和筛选其内容 (请参阅图 2)。共享库定义的国家/地区类和其辅助类型。如前文所述,将 Web API 和 Blazor 应用程序之间共享这些类型。

Visual Studio 中的示例项目
图 2 Visual Studio 中的示例项目

Blazor 应用程序的 UI 组件

实际上,任何 Blazor 应用程序包含的一系列客户端的可导航的网页,其中每个在 Razor 中编写。从根本上讲,示例.cshtml 页面包含一些 HTML、 某些 Razor 扩展和自定义组件类似于 ASP.NET Core 的自定义标记帮助器。UI 组件是 Razor 代码中,在第一次可能类似于 ASP.NET Core 应用程序的分部视图的非重复的段。在深入了解,不过,事实证明组件是非常不同于分部视图。组件使用参数,有状态、 具有特定的生命周期中,可以定义事件和具有不同的编译模型。

让我们假设你有 CountrySelector.cshtml 文件位置在项目中。常见位置是 Blazor 应用程序的视图/共享文件夹,但该文件夹不是非常重要。除了一些可能的 CSS 样式和 HTML 布局信息,该组件将包含输入的字段和一个按钮,如下所示:

<input type="text" 
  bind="@SelectedCountry"
  placeholder="@Placeholder" />
<button type="button" onclick="@GetCountryInfo">
  <i class="fa fa-search"></i>
</button>

单击该按钮,在运行 GetCountryInfo 方法。在文本框中显示的值与名为 SelectedCountry 组件级别字段都相关联。下面是需要的组件来处理点击和查询 Web API 中具有最少的代码:

@functions
{
  private string SelectedCountry;
  async void GetCountryInfo()
  {
    var url = WebUtility.UrlEncode($"/country/search/{SelectedCountry}");
    var countries = await
      Http.GetJsonAsync<IList<Country>>(url);
  }
}

输入元素上使用的绑定属性可确保的 value 属性的 DOM 元素和 SelectedCountry 字段之间的双向数据绑定。绑定是自动的按 onchange 事件。

有一些需要关注的事情上评价有关 UI 组件。例如,一个 UI 组件,可以公开父设置的参数。下面是如何的示例页图 1引用 CountrySelector UI 组件:

<CountrySelector
  uniqueId="cs"
  placeholder="Start typing the country name"
  typeAheadUrl="/hint/countries"
  onSelectionMade="@PopulateGrid" />

请注意,属性的用例是个人喜好问题。您可以使用客户端的常见驼峰式大小写样式 (如在此代码段所示) 或使用 pascal 命名法样式,以使它清楚.NET land 内这些属性。

列出的所有属性都定义为 UI 组件的 @functions {...} 块中的参数如下所示:

[Parameter]
string UniqueId { get; set; } = "country-selector";
[Parameter]
string TypeAheadUrl { get; set; } = "";
[Parameter]
string Placeholder { get; set; } = "Country";
[Parameter]
Action<IList<Country>> OnSelectionMade { get; set; }

大部分示例 CountrySelector 中设置的参数用于自定义 HTML 输出。例如,占位符属性在空文本框中,设置的帮助程序文本和 TypeAheadUrl 属性可设置最终提供自动完成功能的 URL。(对此进行详细详加论述。)

激发和处理事件

OnSelectionMade 参数最终是一个事件处理程序,由父组件设置为指定要执行由 CountrySelector 组件触发的事件响应的代码。在最简单的形式定义一个事件作为操作 < T > 委托。在刚才向您展示的代码段,OnSelectionMade 事件提供处理程序替换的国家/地区来处理列表:

[Parameter]
Action<IList<Country>> OnSelectionMade { get; set; }

让我们了解组件内从触发事件如何。示例 CountrySelector 组件有两个主要用途: 捕获的文本作为查询的参数使用一个字符串,并针对远程 HTTP 终结点运行查询。OnSelectionMade 自定义事件,通过为 whom 地定 concern 公开的查询从接收到的国家/地区列表如下所示:

async void GetCountryInfo()
  {
    var url = $"/country/search/{SelectedCountry}";
    var countries = await
      Http.GetJsonAsync<IList<Country>>(url);            
    OnSelectionMade?.Invoke(countries);
  }

如果不为 null,< T > 操作调用委托时使用的国家/地区列表。一般情况下,较好的做法将事件调用代码包装 NotifyStateChanged 类似的常规用途和无参数方法中。在这种情况下,可能有必要,在集中的数据结构中捕获要通知的状态。

让我们专注于现在如何从父组件,例如主机页处理事件。主页面中呈现图 1包含以下 @functions 块:

@functions
{
  public string Message = "";
  IList<Country> Countries = new List<Country>();
  private void PopulateGrid(IList<Country> countries)
  {
    Message = $"{countries.Count} countries found.";
    Countries = countries;
    StateHasChanged();
  }
}

在激发事件 OnSelectionMade 时调用方法 PopulateGrid。该方法接收的所选国家/地区列表,并在内部将其保存到的国家/地区字段。此外,国家/地区的总数将保存到消息字段。这两个字段,国家/地区和消息,绑定到 Blazor 组件 (CountryGrid) 和 HTML SPAN 元素,如下所示:

<CountrySelector onSelectionMade="@PopulateGrid" />
<span class="badge badge-primary">@Message</span>
<CountryGrid dataSet="@Countries" />

CountryGrid Blazor 组件的内容是一个普通的 HTML 表基于模板动态填充围绕其数据集参数的内容:

@functions
{
  [Parameter]
  IList<Country> DataSet { get; set; } = new List<Country>();
}

将国家/地区字段设置为新值的事件处理程序不会自动刷新当前视图中的任何组件。如果状态更新因显式用户操作,UI 会自动刷新。否则,若要刷新视图,请对 StateHasChanged 方法的显式、 以编程方式调用是必需的:

void PopulateGrid(IList<Country> countries)
{
  Countries = countries;
  StateHasChanged();
}

数据绑定组件的基础

任何客户端为中心的 Web 框架花费在执行数据绑定,其正常运行时间的大共享,以及大多数时间可绑定数据作为 JSON 字符串来自某些远程 HTTP 终结点。提取数据,客户详细信息、 航班或天气预测是一种相当常见方案。若要下载数据,需要在组件中引用的 HTTP 客户端。若要执行此操作的最简单方法使用依赖关系注入,如下:

@inject HttpClient Http

Http 引用可用于获取 JSON 的 HttpClient 的单一实例的符号源,并序列化为 C# 集合和类。下面是用于从远程 URL Blazor 中获取可绑定的 JSON 数据的代码:

var countries = await Http.GetJsonAsync<IList<Country>>(url);

数据后,您需要找到一种方法将其绑定到可视元素。目前,在 Blazor 您不引用 UI 组件 (或 HTML 元素) 的名称和代码对其编程接口。相反,公开的公共事件,通过它接收数据并使用组件参数将数据传递给子组件。在上一示例中,CountryGrid 组件具有未分配的 ID 属性和它的任何实例以编程方式引用接收要绑定的数据。相反,父字段被分配 (国家/地区) 的是数据绑定到 CountryGrid 组件的数据集属性。

JavaScript 互操作性

Blazor,可使用 C# 代码来实现客户端的操作。但是,仍以 HTML 和 CSS 使用,可能需要一些 JavaScript 表示 UI (布局和样式)。Blazor 不强制要求使用 JavaScript 的互操作性,但实际上,某种形式的互操作可能需要时间。一种方案是将 JavaScript 代码的一些现有的和相当复杂,部分集成 Blazor UI 中。

例如,让我们考虑自动完成的常用 typeahead.js 库。库是 jQuery 插件以的形式提供你以编程方式将连接到的输入字段。在加载后,该插件必须调用来响应输入事件中字段的用户类型中触发 dom。如何将调用从 JavaScript 中 Blazor 视图?截至 Blazor 0.5.0,需要你想要调用从.NET 中注册的顶级函数在浏览器的窗口对象,此类的任何 JavaScript 代码的总结:

window.MyFunctions = {
  typeAhead = function() {
    $(document).ready(function() {      // Typeahead initialization code
    });
  },
  uxDebug = function() {
    $(document).ready(function() { ... });
  }
}

类似的 JavaScript 代码置于 Blazor 启动脚本标记的右方主要 index.html 文件中引用的文件。浏览器的窗口对象仅修饰的自定义方法,但仍有任何调用。若要启用 typeahead.js,请调用从 Blazor 组件的 OnInit 方法中的 JavaScript 函数。因此,CountrySelector 组件将包含以下信息:

protected override void OnInit()
{
  JSRuntime.Current.InvokeAsync<Task>(
    "MyFunctions.typeAhead", "#input-field",
    TypeAheadUrl);
}

因此,只要呈现 Blazor 视图,并且由于实现,其效果将绑定到的 DOM 就绪事件调用 typeAhead 的 JavaScript 包装器。图 3显示如下所示的 typeAhead 库应用程序 UI 中的实现。

Typeahead 中操作
图 3 Typeahead 中操作

黑色功能区中的 Web 页顶部显示图 3是一个相对简单的 JavaScript,以使用 WURFL 的服务。JS 终结点 (wurfl.io) 来收集可靠的浏览器的信息。在这里,它显示当前的启动大小、 实际宽度和某些浏览器信息。

总结

Blazor 提升的基于组件的 UI 与数据绑定是主要工具来整合成数据和布局元素。HTML 和 CSS 的基础语言来表达视觉对象。完全支持 JavaScript,但一般情况下,复杂的 UI 元素更好地使用实现的特定于框架的本机组件。


Dino Esposito撰写了超过 20 本书籍和 1,000-plus 文章在他 25 年的职业生涯。Esposito 不仅是舞台剧《事业中断》的作者,还是 BaxEnergy 的数字策略分析师,正忙于编写有助于建设环保世界的软件。可以在 Twitter 上关注他 (@despos)。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Daniel Roth


在 MSDN 杂志论坛讨论这篇文章