五、Blazor 入门

在上一章中,我们了解了 Razor 视图引擎的基本原理,并了解了它如何支持不同的 web 框架来呈现 web UI。我们介绍了实际的编码练习,以了解 MVC 和 Razor Pages web 框架,这些框架与 ASP.NET Core 一起提供,用于构建强大的 web 应用。在本章中,我们将了解 ASP.NET Core web 框架的最新添加—Blazor。

BlazorWeb 框架是一个巨大的主题;本书将主题分为两章,便于您轻松掌握开始使用框架所需的核心概念和基础知识。当您完成这两章时,您将了解 Blazor 应用如何与各种技术配合使用,以构建强大而动态的 web 应用。

以下是我们将在本章中介绍的主题:

  • 了解 Blazor web 框架及其不同风格
  • 了解我们将使用各种技术构建的目标
  • 创建一个简单的 webapi
  • 学习如何使用实体框架核心的内存数据库
  • 学习如何使用 SignalR 执行实时更新
  • 为旅游景点应用实现后端应用

本章主要针对具有 C#经验的初学者和中级.NET 开发人员,他们希望跳入 Blazor 并通过实际示例来解决问题。它将帮助您学习 Blazor 编程模型的基础知识,帮助您从头开始构建第一个 web 应用。

技术要求

本章使用 Visual Studio 2019 构建项目。您可以在查看本章的源代码 https://github.com/PacktPublishing/ASP.NET-Core-5-for-Beginners/tree/master/Chapter%2005%20and%2006/Chapter_05_and_06_Blazor_Examples/TouristSpot

在深入阅读本章之前,请确保您对 ASP.NET Core 和 C#有了基本的了解,因为本章将不介绍它们的基本原理。

请访问以下链接查看 CiA 视频:https://bit.ly/3qDiqYY

了解 Blazor web 框架

Blazor 于 2018 年初作为一个实验项目引入。它是基于单页应用SPA)的 ASP.NET Core web 框架的最新添加。你可以把想象成与 React、Angular、Vue 和其他基于 SPA 的框架类似,但它由 C#和 Razor 标记语言提供支持,使你能够创建 web 应用,而无需编写 JavaScript。是的,你听对了——没有 JavaScript!虽然 Blazor 不要求您使用 JavaScript,但它提供了一个名为JavaScript 互操作性JS 互操作性的功能),允许您从 C 代码调用 JavaScript 代码,反之亦然。相当整洁!

无论您是来自 Windows、Xamarin、Web 窗体还是传统的 ASP.NET MVC 开发背景,还是完全不熟悉 ASP.NET Core 并希望将您的技能提升到一个新的水平,Blazor 绝对是一个不错的选择,因为它可以让您使用现有的 C#技能来编写 Web UI。学习框架本身很容易,只要你知道基本的 HTML 和 CSS。它的设计目的是使 C#开发者能够利用他们的技能轻松过渡到 web 范例,以构建基于 SPA 的 web 应用。

回顾 Blazor 的不同口味

在我们讨论 Blazor 的不同口味之前,让我们先简要介绍一下 Razor 组件。

Razor 组件是 Blazor 应用的构建块。它们是 UI 的自包含块,由 HTML、CSS 和使用Razor标记的 C#代码组成。这些组件可以是整个页面、页面中的一个部分、表单或对话框。组件非常灵活、轻量级,并且易于在不同的应用(如 Razor 页面或 MVC 应用)之间重用、嵌套甚至共享。组件中发生的任何更改,例如影响应用状态的按钮单击,都将呈现图形并计算 UIdiff。此diff包含更新 UI 所需的一组 DOM 编辑,并由浏览器应用。

Blazor 已经获得了广泛的欢迎,即使该框架在市场上还是很新的。事实上,大型用户界面提供商,如 Telerik、Syncfusion 和 DevXpress,已经提供了一系列可以集成到应用中的 Razor 组件。还有其他开源项目提供现成的组件,您可以免费使用,例如 MatBlazor 和 RadZen。

Blazor 有两种主要的主机模型:

  • Blazor 服务器
  • BlazorWebAssemblyWASM

让我们对每一个都做一个简短的概述。

Blazor 服务器

Blazor 服务器,通常被称为服务器端 Blazor,是在服务器上运行的一种 Blazor 应用。这是第一款正式在.NET Core 中发布的 Blazor 机型,已准备好投入生产使用。图 5.1 显示了 Blazor 服务器如何在引擎盖下工作。

Figure 5.1 – Blazor Server

图 5.1–Blazor 服务器

在上图中,我们可以看到基于服务器的 Blazor 应用被包装在 ASP.NET Core 应用中,允许它在服务器上运行和执行。它主要使用信号器管理和驱动实时服务器更新到 UI,反之亦然。这意味着在服务器中维护应用状态、DOM 交互和组件的呈现,Signal 将通过带有diff的集线器通知 UI 在应用状态更改时更新 DOM。

其优点如下:

  • 无需编写 JavaScript 来运行应用。
  • 应用代码保留在服务器上。
  • 由于应用在服务器上运行,因此可以利用 ASP.NET 的核心功能,例如在共享项目中托管 Web API、集成其他中间件以及通过 DI 连接到数据库和其他外部依赖项。
  • 支持快速加载时间和较小的下载大小,因为服务器需要处理繁重的工作负载。
  • 在任何浏览器上运行。
  • 强大的调试能力。

缺点如下:

  • 它需要一台服务器来引导应用。
  • 没有离线支持。信号器需要与服务器的开放连接。当服务器停机时,您的应用也会停机。
  • 网络延迟更高,因为每个 UI 交互都需要调用服务器来重新呈现组件状态。如果您有一个在不同地区托管应用的地理复制服务器,则可以解决此问题。
  • 维护和扩展成本高昂且困难。这是因为每次打开页面实例时,都会创建一个单独的信号器连接,这可能很难管理。在将应用部署到 Azure 时,使用 Azure 信号器服务可以解决此问题。对于非 Azure 云提供商,您可能需要依靠流量管理器来应对这一挑战。

Blazor WebAssembly

简单地说,WASM 是一种抽象,它使高级编程语言(如 C#)能够在浏览器中运行。此过程通过在浏览器中下载所有必需的基于 WASM 的.NET 程序集和应用 DLL 来完成,以便应用可以在客户端浏览器中独立运行。如今,大多数主流浏览器,如谷歌浏览器、微软 Edge、Mozilla Firefox、苹果 Safari 和 WebKit,都支持 WASM 技术。

Blazor WASM 最近被集成到 Blazor 中。在幕后,Blazor WASM 使用基于 WASM 的.NET 运行时来执行应用的.NET 程序集和 DLL。这种类型的应用可以在支持 WASM web 标准的浏览器上运行,无需插件。也就是说,Blazor WASM 并不是 Silverlight 的一种新形式。

图 5.2 显示了 Blazor WASM 应用如何在引擎盖下工作。

Figure 5.2 – Blazor WASM

图 5.2-Blazor WASM

在上图中,我们可以看到 Blazor WASM 应用不依赖于 ASP.NET Core;应用直接在客户机上执行。客户端 Blazor 正在使用 WASM 技术运行。默认情况下,Blazor WASM 应用纯粹在客户端上运行;但是,您可以选择将其转换为 ASP.NET 托管的应用,以获得 Blazor 和全栈.NET web 开发的所有好处。

其优点如下:

  • 无需编写 JavaScript 来运行应用。
  • 没有服务器端依赖关系,这意味着没有延迟或可伸缩性问题,因为应用在客户端计算机上运行。
  • 启用脱机支持,因为应用作为一个自包含的应用卸载到客户端。这意味着您仍然可以在与承载应用的服务器断开连接时运行应用。
  • 支持渐进式 Web 应用PWAs)。PWA 是使用现代浏览器 API 和功能的 web 应用,其行为与本机应用类似。

这些是缺点:

  • 页面的初始加载速度很慢,下载量很大,因为所有必需的依赖项都需要提前拉出来,才能将应用卸载到客户端的浏览器中。在将来实现缓存以减少下载的大小和后续请求处理所需的时间时,可以对此进行优化。
  • 由于 DLL 已下载到客户端,因此您的应用代码已公开。所以,你必须非常小心你放在那里的东西。
  • 需要支持 WASM 的浏览器。请注意,大多数主流浏览器现在都支持 WASM。
  • 这是一个不太成熟的运行时,因为它是新的。
  • 与 Blazor 服务器相比,调试可能更加困难和有限。

有关 Blazor 托管模型的更多信息,请参见https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models

移动 Blazor 绑定

Blazor 还提供了一个使用 C#和.NET 为 Android、iOS、Windows 和 macOS 构建本机和混合移动应用的框架。移动 Blazor 绑定使用相同的标记引擎构建 UI 组件。这意味着您可以使用 Razor 语法来定义 UI 组件及其行为。在幕后,UI 组件仍然基于Xamarin.Forms,因为它使用相同的基于 XAML 的结构来构建组件。与 Xamarin.Forms 相比,这个框架的突出之处在于它允许您混合使用 HTML,让开发人员可以选择使用他们喜欢的标记编写应用。使用混合应用,您可以混合使用 HTML 来构建组件,就像构建 web UI 组件一样。这使它成为 ASP.NET 开发人员使用现有技能进行跨平台本机移动应用开发的一个重要垫脚石。话虽如此,MobileBlazor 绑定仍处于实验阶段,在正式发布之前,没有任何保证。

在本章中,我们将不介绍移动 Blazor 绑定的开发。如果您想了解更多信息,可以参考这里的官方文档:https://docs.microsoft.com/en-us/mobile-blazor-bindings

五名球员,一个进球

正如我们从上一节了解到的,Blazor 只是构建 UI 的框架。为了使学习 Blazor 变得有趣有趣,我们将使用各种技术构建一个完整的 web 应用来实现一个目标。该目标是使用尖端技术构建一个简单的数据驱动 web 应用,具有实时性功能:Blazor 服务器Blazor WASMASP.NET Core web API信号器实体框架核心

图 5.3 展示了每种技术如何连接的高级过程。

Figure 5.3 – Five players, one goal

图 5.3–五名球员,一个进球

根据上图,我们需要构建以下应用:

  • 通过 API 调用显示和更新页面信息的 web 应用。该应用还将实现一个 signar 订阅,该订阅充当客户端,对 UI 执行实时数据更新。
  • 公开GETPUTPOST面向公众的 API 端点的 Web API 应用。该应用还将配置内存中的数据存储以持久化数据,并实现 signar 以向中心广播消息,客户端可以在中心订阅并实时获取数据。
  • 通过 API 调用提交新记录的 PWA。

既然您已经知道要构建什么以及使用哪些技术集,那么让我们开始着手编写代码。

构建旅游景点应用

为了在典型的数据驱动 web 应用中涵盖真实场景,我们将构建一个简单的旅游景点应用,该应用由不同的应用组成,以执行不同的任务。您可以将此应用视为旅游目的地的 wiki,用户可以在其中查看和编辑有关地点的信息。用户还可以根据评论查看排名靠前的位置,还可以实时查看其他类似应用提交的新位置。所谓实时,我们的意思是用户无需刷新页面即可查看新数据。

图 5.4 描述了我们的旅游景点应用示例所需的应用和流程的高级流程

Figure 5.4 – The applications to be built

图 5.4–要构建的应用

如果你准备好了,那我们开始吧。我们将从构建后端应用开始,该应用公开 API 端点以提供数据,以便其他应用可以使用它。

创建后端应用

对于旅游景点应用项目,我们将使用 ASP.NET Core Web API 作为后端应用。

让我们继续启动 Visual Studio 2019,然后选择创建新项目选项。在下一个屏幕上,选择ASP.NET Core Web 应用,然后单击下一步配置新项目对话框应如图 5.5 所示出现。

Figure 5.5 – Configure your new project

图 5.5–配置您的新项目

此对话框允许您配置项目和解决方案名称,以及要创建项目的位置路径。对于这个特定的示例,我们只需将项目命名为PlaceApi,并将解决方案名称设置为TouristSpot。现在,点击创建,您将看到如图 5.6 所示的对话框。

Figure 5.6 – Create a new ASP.NET Core web application

图 5.6–创建新的 ASP.NET Core web 应用

此对话框允许您选择要创建的 web 框架的类型。对于本项目,只需选择API,然后点击创建即可让 Visual Studio 为您生成所需的文件。生成的默认文件应该与图 5.7 中的类似。

Figure 5.7 – Web API default project structure

图 5.7–Web API 默认项目结构

前面的屏幕截图显示了 ASP.NET Core Web API 应用的默认结构。请注意,在本章中我们不会深入探讨 Web API 的细节,但为了让您快速了解,Web API 的工作方式与传统 ASP.NET MVC 相同,只是它是为构建可通过 HTTP 使用的 RESTful API 而设计的。换句话说,Web API 没有Razor 视图引擎,也不打算生成页面。我们将在第 7 章中深入探讨 Web API 的细节,API 和数据访问

现在,让我们进入下一步。

配置内存中数据库

在上一章中,我们学习了如何使用具有实体框架核心的内存中数据库。如果您已经做到了这一点,您现在应该熟悉如何配置内存中的数据存储。在本演示中,我们将使用您现在熟悉的技术轻松创建数据驱动的应用,而无需启动真正的数据库服务器来存储数据。第 7 章、API 和数据访问将介绍如何在实体框架核心中使用真实数据库;现在,为了简化本练习,让我们只使用内存中的数据库。

安装实体框架核心

实体框架核心作为一个单独的 NuGet 包实现,以允许开发人员在需要时轻松集成它。有很多方法可以在应用中集成 NuGet package 依赖项。我们可以通过命令行CLI)或通过集成到 Visual Studio 中的 NuGet 软件包管理界面(UI)来安装它。要使用 UI 安装依赖项,只需右键单击项目的Dependencies文件夹,然后选择Manage NuGet Packages…选项。图 5.8 显示了 UI 应该如何显示。

Figure 5.8 – NuGet package management UI

图 5.8–NuGet 软件包管理 UI

浏览选项卡中,输入此处列出的软件包名称并安装:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.InMemory

成功安装两个软件包后,确保检查项目的Dependencies文件夹,并验证它们是否已添加(如图 5.9 所示)。

Figure 5.9 – Installed project NuGet dependencies

图 5.9–已安装的项目 NuGet 依赖项

注:

撰写本文时,Microsoft.EntityFrameworkCore的最新官方版本为5.0.0。将来的版本可能会更改,并可能影响本章中使用的示例代码。因此,在决定升级到新版本时,请确保始终检查是否有任何中断性更改。

既然我们已经准备好了实体框架核心,让我们继续下一步并配置一些测试数据。

实现数据访问层

在项目根目录中创建名为Db的新文件夹,然后创建名为Models的子文件夹。右键点击Models文件夹,选择添加>。将类别命名为Places.cs,点击添加,然后粘贴以下代码:

using System;
namespace PlaceApi.Db.Models
{
    public class Place
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Location { get; set; }
        public string About { get; set; }
        public int Reviews { get; set; }
        public string ImageData { get; set; }
        public DateTime LastUpdated { get; set; }
    }
}

前面的代码只是一个包含一些属性的普通类。稍后我们将使用该类用测试数据填充每个属性。

现在,在Db文件夹中创建一个名为PlaceDbContext.cs的新类,并复制以下代码:

using Microsoft.EntityFrameworkCore;
using PlaceApi.Db.Models;
namespace PlaceApi.Db
{
    public class PlaceDbContext : DbContext
    {
        public PlaceDbContext(DbContextOptions<PlaceDbContext>         options)
        : base(options) { }
        public DbSet<Place> Places { get; set; }
    }
}

前面的代码定义了一个DbContext实例和一个将Places属性(实体)公开为DbSet实例的单个实体。DbSet<Place>表示内存中的数据集合,是执行数据库操作的网关。例如,在调用DbContextSaveChanges()方法之后,对DbSet<Place>的任何更改都将提交到数据库。

让我们继续在Db文件夹中添加另一个名为PlaceDbSeeder.cs的新类。我们需要做的第一件事是声明以下命名空间引用:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using PlaceApi.Db.Models;
using System;
using System.IO;
using System.Linq;

前面的代码使我们能够访问每个命名空间中的方法和成员,这些方法和成员是我们在实现方法来为测试数据种子时所必需的。

现在,将以下方法粘贴到类中:

private static string GetImage(string fileName, string fileType)
{
    var path = Path.Combine(Environment.CurrentDirectory, Db/    Images, fileName);
    var imageBytes = File.ReadAllBytes(path);
    return $data:{fileType};base64,{Convert.    ToBase64String(imageBytes)};
}

前面代码中的GetImage()方法获取Db/Images文件夹中存储的图像文件,并将图像转换为byte数组。然后,它将字节转换为base64字符串格式,并将格式化数据作为图像返回。我们将在下一步中引用此方法。

现在,将以下代码粘贴到类中:

public static void Seed(IServiceProvider serviceProvider)
{
    using var context = new PlaceDbContext(serviceProvider.    GetRequiredService<DbContextOptions<PlaceDbContext>>());
    if (context.Places.Any()){ return; }
    context.Places.AddRange(
        new Place
        {
            Id = 1,
            Name = Coron Island,
            Location = Palawan, Philippines,
            About = Coron is one of the top destinations for             tourists to add to their wish list.,
            Reviews = 10,
            ImageData = GetImage(coron_island.jpg, img/            jpeg),
            LastUpdated = DateTime.Now
        },
        new Place
        {
            Id = 2,
            Name = Olsob Cebu,
            Location = Cebu, Philippines,
            About = Whale shark watching is the most popular             tourist attraction in Cebu.,
            Reviews = 3,
            ImageData = GetImage(oslob_whalesharks.png,             img/png),
            LastUpdated = DateTime.Now
        }
    );
    context.SaveChanges();
}

前面代码中的Seed()方法将在应用启动时初始化两个Place数据集。这是通过将数据添加到PlaceDbContextPlaces实体中完成的。您可以看到,我们通过调用前面创建的GetImage()方法来设置ImageData属性的值。

现在我们已经实现了 seeder 类,接下来我们需要做的是创建一个新类,该类将包含两个扩展方法,用于注册内存中的数据库并将 seeder 类用作中间件。在Db文件夹中,继续添加一个名为PlaceDbServiceExtension.cs的新类,并粘贴以下代码:

using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace PlaceApi.Db
{
    public static class PlaceDbServiceExtension
    {
        public static void AddInMemoryDatabaseService(this         IServiceCollection services, string dbName)
               => services.AddDbContext<PlaceDbContext>(options                => options.UseInMemoryDatabase(dbName));
        public static void InitializeSeededData (this         IApplicationBuilder app)
        {
            using var serviceScope = app.ApplicationServices.            GetRequiredService<IServiceScopeFactory>().            CreateScope();
            var service = serviceScope.ServiceProvider;
            PlaceDbSeeder.Seed(service);
        }
    }
}

前面的代码定义了两个主要的static方法。AddInMemoryDatabaseService()是将PlaceDbContext注册为依赖注入DI容器中的服务的IServiceCollection扩展方法。请注意,我们正在将UseInMemoryDatabase``()扩展方法配置为AddDbContext()方法调用的参数。这告诉框架使用给定的数据库名称启动内存中的数据库。InitializeSeededData()扩展方法负责在应用运行时生成测试数据。它使用ApplicationServices类的GetRequiredService()方法引用用于从范围中解析依赖关系的服务提供者。然后它调用我们前面创建的PlaceDbSeeder.Seed()方法,并传递服务提供者来初始化测试数据。

this关键字位于每个方法参数中的对象类型之前,表示一个方法是一个扩展方法。扩展方法允许您向现有类型添加方法。对于这个特定的示例,我们将AddInMemoryDatabaseService()方法添加到IServiceCollection类型的对象中,并将InitializeSeededData()方法添加到IApplicationBuilder类型的对象中。有关扩展方法的更多信息,请参见https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

现在,我们有了一个DbContext实例,它使我们能够访问我们的Places``DbSet、一个将生成一些数据的助手类,以及两个用于注册内存服务的扩展方法。我们接下来需要做的是将它们连接到Startup.cs中,以便在应用启动时填充我们的数据。

修改启动类

让我们将Startup类的ConfigureServices()方法更新为以下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddInMemoryDatabaseService(“PlacedDb”);
    services.AddControllers();
}

在前面的代码中,我们调用了前面创建的AddInMemoryDatabaseService()扩展方法。同样,这个过程在IServiceCollection中注册PlaceDbContext,并定义一个名为PlacedDb的内存数据库。将DbContext注册为 DI 容器中的服务,使我们能够通过 DI 在应用中的任何类中引用此服务的实例。

现在,我们需要做的最后一步是调用Configure()方法中的InitializeSeededData()扩展方法,如下所示:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.InitializeSeededData();

    //removed other middlewares for brevity
}

此时,我们的测试数据应该在应用启动时加载到内存中的数据库中,并准备好在应用中使用。

利用信号机实现实时功能

如今,将实时功能添加到任何 ASP.NET Core 服务器应用中都非常容易,因为 Signal 完全集成到框架中。这意味着不需要下载或引用单独的 NuGet 软件包就可以实现实时功能。

ASP.NET Signal 是一种技术,它提供了一套干净的 API,可为您的 web 应用实现实时行为,其中服务器将数据推送到客户端,而传统的方法是让客户端不断从服务器提取数据以进行更新。

要开始使用ASP.NET Core 信号器,我们需要先创建一个集线器Hub是信号器中的一个特殊类,使我们能够从服务器对连接的客户端调用方法。本例中的服务器是我们的 Web API,我们将为其定义一个供客户端调用的方法。本例中的客户端是Blazor 服务器应用

让我们在应用的根目录下创建一个名为PlaceApiHub的新类,然后粘贴以下代码:

using Microsoft.AspNetCore.SignalR;
namespace PlaceApi
{
    public class PlaceApiHub : Hub
    {
    }
}

前面的代码只是从Hub类继承的一个类。我们将Hub类保留为空,因为我们没有从客户端调用任何方法。相反,API 将通过集线器发送事件。

接下来,我们将在 DI 容器中注册SignalRResponseCompression服务。在Startup类的ConfigureServices()方法中添加以下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR();
    services.AddResponseCompression(opts =>
    {
        opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.        Concat(
            new[] { “application/octet-stream” });
    });
    // Removed other services for brevity
}

接下来,我们需要在管道中添加ResponseCompression中间件,并映射我们的Hub。在Configure()方法中增加以下代码:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Removed other code for brevity
    app.UseResponseCompression();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapHub<PlaceApiHub>(/PlaceApiHub);
    });
}

前面的代码通过映射PlaceApiHub类定义信号集线器的路由。此使客户端应用能够连接到集线器并侦听从服务器发送的事件。

这很简单。我们将在下一节创建 Web API 端点时实现发送事件。

创建 API 端点

既然我们的内存数据库都设置好了,并且我们已经为我们的实时功能配置了 Signal,现在是我们创建 API 控制器并公开一些端点以向客户端提供数据的时候了。在这个特定示例中,我们需要以下 API 端点来处理数据的获取、创建和更新:

  • GET: api/places
  • POST: api/places
  • PUT: api/places

继续,右键点击Controllers文件夹,然后选择添加>控制器>API 控制器清空,然后点击添加

将类命名为PlacesController.cs,然后点击添加。现在,替换默认生成的代码,使您拥有的代码如下所示:

using Microsoft.AspNetCore.Mvc;
using PlaceApi.Db;
using PlaceApi.Db.Models;
using System;
using System.Linq;
namespace PlaceApi.Controllers
{
    [ApiController]
    [Route(“api/[controller])]
    public class PlacesController : ControllerBase
    {
        private readonly PlaceDbContext _dbContext;
        private readonly IHubContext<PlaceApiHub> _hubContext;
        public PlacesController(PlaceDbContext dbContext,
                        IHubContext<PlaceApiHub> hubContext)
        {
            _dbContext = dbContext;
            _hubContext = hubContext;
        }
        [HttpGet]
        public IActionResult GetTopPlaces()
        {
            var places = _dbContext.Places.OrderByDescending(o             => o.Reviews).Take(10);
            return Ok(places);
        }
    }
}

前面的代码显示了 APIController类的典型结构。API 应该实现ControllerBase抽象类,以利用框架中内置的现有功能来构建 RESTful API。我们将在下一章更深入地讨论 API。现在,让我们来看看我们在前面的代码中做了什么。PlacesController类的前两行定义了PlaceDbContextIHubContext<PlaceApiHub>privateread-only字段。下一行定义类构造函数,并将PlaceDbContextIHubContext<PlaceApiHub>作为依赖项注入类。在这种情况下,PlacesController类中的任何方法将能够访问PlaceDbContextIHubContext的实例,允许我们调用其所有可用的方法和属性。

目前,我们在PlaceController中只定义了一种方法。GetTopPlaces()方法负责从内存数据存储返回前 10 行数据。我们已经使用了Enumerable类型的LINQOrderByDescending()Take()扩展方法,根据Reviews值获取最上面的行。您可以看到,该方法已使用[HttpGet]属性修饰,这表示该方法只能由 HTTPGET请求调用。

现在,让我们添加另一个处理新记录创建的方法。在类中附加以下代码:

[HttpPost]
public IActionResult CreateNewPlace([FromBody] Place place)
{
    var newId = _dbContext.Places.Select(x => x.Id).Max() + 1;
    place.Id = newId;
    place.LastUpdated = DateTime.Now;
    _dbContext.Places.Add(place);
    int rowsAffected = _dbContext.SaveChanges();
    if (rowsAffected > 0)
    {
       _hubContext.Clients.All.SendAsync(“NotifyNewPlaceAdded”,    place.Id, place.Name);
    }
    return Ok(“New place has been added successfully.”);
}

前面的代码负责在内存数据库中创建一个新的Place记录,同时向中心广播一个事件。在本例中,我们正在调用Hub类的Clients.All.SendAsync()方法,并将place.Idplace.Name传递给NotifyNewPlaceAdded事件。请注意,您也可以将对象传递给SendAsync()方法,而不是传递单个参数,就像我们在本例中所做的那样。您可以看到CreateNewPlace()方法已经用[HttpPost]属性修饰,这意味着方法只能通过 HTTPPOST请求调用。请记住,我们通过增加数据存储中现有的最大 ID 来手动生成Id。在使用真实数据库的真实应用中,您可能不需要这样做,因为您可以让数据库自动为您生成Id

让我们创建应用所需的最后一个端点。将以下代码块添加到类中:

[HttpPut]
public IActionResult UpdatePlace([FromBody] Place place)
{
    var placeUpdate = _dbContext.Places.Find(place.Id);
    if (placeUpdate == null)
    {
        return NotFound();
    }
    placeUpdate.Name = place.Name;
    placeUpdate.Location = place.Location;
    placeUpdate.About = place.About;
    placeUpdate.Reviews = place.Reviews;
    placeUpdate.ImageDataUrl = place.ImageDataUrl;
    placeUpdate.LastUpdated = DateTime.Now;
    _dbContext.Update(placeUpdate);
    _dbContext.SaveChanges();
    return Ok(“Place has been updated successfully.”);
}

前面的代码负责更新内存数据库中现有的Place记录。UpdatePlace()方法将Place对象作为参数。首先根据 ID 检查记录是否存在,如果记录不在数据库中,则返回NotFound()响应。否则,我们更新数据库中的记录,然后返回带有消息的OK()响应。请注意,本例中的方法用[HttpPut]属性修饰,这表示此方法只能由 HTTPPUT请求调用。

启用 CORS

现在已经准备好了 API,下一步要做的就是启用跨源资源共享CORS)。我们需要对此进行配置,以便托管在不同域/端口中的其他客户端应用可以访问 API 端点。要在 ASP.NET Core Web API 中启用 CORS,请在Startup类的ConfigureServices()方法中添加以下代码:

services.AddCors(options =>
{
    options.AddPolicy(“AllowAll”,
    builder =>
    {
        builder.AllowAnyOrigin()
                .AllowAnyHeader()
                .AllowAnyMethod();
    });
});

前面的代码添加了 CORS 策略,以允许任何客户端应用访问我们的 API。在本例中,我们使用AllowAnyOrigin()AllowAnyHeader()AllowAnyMethod()配置设置了 CORS 策略。但是,请记住,在 To.T3 将真实 API 暴露在真实世界的 T4 应用之前,您应该考虑设置允许的来源、方法、标头和凭据。有关 CORS 的详细信息,请参见此处的官方文档:https://docs.microsoft.com/en-us/aspnet/core/security/cors

现在,在UseRouting()中间件后的Configure()方法中添加以下代码:

app.UseCors(“AllowAll”);

就这样。

测试端点

现在我们已经为我们的应用实现了所需的 API 端点,让我们做一个快速的测试,以确保我们的 API 端点工作正常。按Ctrl+F5在浏览器中启动应用,然后导航到https://localhost:44332/api/places端点。您应该看到如图 5.10 所示的输出。

Figure 5.10 – API’s HTTP GET request output

图 5.10–API 的 HTTP GET 请求输出

前面的屏幕截图以 JSON 格式显示了我们的GetTopPlaces()``GET端点的结果。请注意 API 当前运行的 localhost 端口值,因为在调用 Blazor 应用中的端点时,我们将使用完全相同的端口号。在本例中,我们的 API 在 IIS Express 的本地端口44332上运行。通过查看Properties文件夹中的launchSettings.json文件,可以看到这是如何定义的,如下代码所示:

{
  “$schema”: “http://json.schemastore.org/launchsettings.json”,
  “iisSettings”: {
    “windowsAuthentication”: false,
    “anonymousAuthentication”: true,
    “iisExpress”: {
      “applicationUrl”: “http://localhost:60766”,
      “sslPort”: 44332
    }
  },
  //Removed other configuration for brevity
}

前面的代码显示了在本地运行应用(包括 IIS Express)时的配置文件配置。您可以更新配置并添加新的配置文件,以便在不同的环境中运行应用。在本例中,为了简单起见,我们将保留默认配置。默认 IIS Express 配置在http中运行时将applicationUrl端口设置为60766,在https中运行时将端口设置为44332。默认情况下,应用使用Startup类的Configure()方法中的UseHttpsRedirection()中间件。这意味着,当您尝试使用http://localhost:60766URL 时,应用将自动将您重定向到安全端口,在本例中为端口44332

使用浏览器只允许我们测试 HTTPGET端点。要测试其余端点,例如POSTPUT,您可能需要安装浏览器应用扩展。在 Chrome 中,您可以安装高级 REST 客户端扩展。您还可以下载Postman来测试我们之前创建的 API 端点。Postman 是一个非常方便的测试 API 的工具,无需创建 UI,而且绝对免费。您可以在这里找到:https://www.getpostman.com/

图 5.11 显示了在 Postman 中测试的 API 的示例屏幕截图。

Figure 5.11 – Testing with POSTMAN

图 5.11-邮递员测试

此时,我们有工作 API 端点,可以用来在页面上显示数据。学习创建 Web API 的基础知识对于整个项目的实现非常重要。

总结

在本章中,我们了解了不同类型 Blazor 托管模型背后的概念。在学习 Blazor 的同时,我们已经确定了我们将要构建的应用的目标,并确定了实现该目标所需的各种技术。我们开始使用 ASP.NET Core API 创建后端应用,并了解了如何使用 Entity Framework Core 的内存提供程序功能轻松配置测试数据,而不必设置真实的数据库。这使我们能够在执行概念验证POC项目时轻松启动数据驱动的应用。我们还学习了如何创建简单的 RESTWebAPI 来服务数据,以及如何配置 SignalR 来执行实时更新。理解本章中使用的技术和框架的基本概念对于成功使用实际应用非常重要。

我们已经了解到,我们在本章中看到的两种 Blazor 模型都是不错的选择,尽管它们都有缺点。Blazor 背后的编程允许希望避免 JavaScript 障碍的 C#开发人员在不必学习新编程语言的情况下构建 SPA。尽管 Blazor 是相当新的,但很明显,Blazor 将成为其他著名 SPA 框架(如 Angular、React 和 Vue)中令人难以置信的热门和有力的竞争者,这是因为 WASM 在本质上是如何取代 JavaScript 的。当然,JavaScript 及其框架不会有任何进展,但是能够使用现有的 C#skillset 构建一个能够产生与 JavaScript web 应用相同输出的 web 应用是一个巨大的优势,因为它可以避免仅仅为了构建 web UI 而学习一种新的编程语言。除此之外,我们还了解到 Blazor 不仅限于 web 应用;mobileblazor 绑定正在为开发者提供一个框架来编写跨平台的本地移动应用。

在下一章中,我们将继续探索 Blazor,并构建其余部分,以完成我们的旅游景点应用。

问题

  1. Blazor 应用有哪些不同类型?
  2. 为什么使用 Blazor 而不是其他 SPA web 框架?

进一步阅读