Loading

第六章-数据存储和微服务

了解 HTTP

在谈论 REST 之前,您应该对超文本传输协议(也称为 HTTP)有一个很好的了解。 HTTP 由 CERN 的 Tim Berners-Lee 于 1989 年创建。CERN 是基础物理研究中心,研究人员完成研究后会做什么? 他们用他们的研究成果发表论文。 在互联网出现之前,发表论文实际上是在纸上完成(因此得名),从撰写论文到将其发表在研究杂志上需要很长时间。 相反,Tim Berners-Lee 设计了一种将论文放在服务器上并允许用户使用现在称为浏览器的程序阅读这些论文的方法。

此外,科学论文包含大量参考文献,当您想阅读这样的论文时,能够访问参考论文会有所帮助。 互联网通过使用超文本标记语言 (HTML) 促进阅读论文。 超文本是一种电子文档格式,可以包含指向其他文档的链接。 您只需单击链接即可阅读另一篇论文,然后只需单击浏览器中的后退按钮即可返回第一篇论文。

通用资源标识符和方法

浏览器是知道如何与 HTTP 通信的应用程序,打开浏览器后要做的第一件事就是输入通用资源标识符 (URI)。 URI 允许浏览器与服务器通信,但还需要更多。 顾名思义,URI 可以通用地标识一个资源,但是您还需要使用 HTTP 方法来指示服务器对 URI 做一些事情。 最常用的方法是 GET。 如图 6-1 所示,当您在浏览器中输入 URI 时,它会在服务器上执行 GET。

image

每次单击 HTML 文档中的超链接时,浏览器都会使用另一个 URI 重复此过程。

但还有其他方法。 如果您想发表一篇新论文,您可以使用 POST 方法将论文发送到服务器,并为其提供一个 URI。 在这种情况下,服务器会将论文存储在请求的 URI 中。 如果您想对论文进行更改,例如纠正拼写错误,您可以使用 PUT 方法。 现在服务器将覆盖由 URI 标识的内容。 最后,您可以使用 DELETE 方法及其 URI 删除论文。

注意 像这样使用 GET、POST、PUT 和 DELETE 方法是一种约定。没有规定您必须这样做,并且存在使用不同方法和状态代码的 REST 服务。

HTTP 状态码

当您向服务器询问它没有的东西时会发生什么? 服务器应该返回什么? 服务器不仅返回 HTML,而且还返回有关结果的状态代码。 当服务器可以成功处理请求时,它通常会返回状态代码 200(存在其他成功状态代码 - 您可以在 https://en.wikipedia.org/wiki/List_of_HTTP_status_codes 找到完整列表)。 当服务器找不到资源时,它会返回一个状态码 404。状态码 404 只是表示“未找到”。 客户端将收到此状态代码并可以做出适当的反应。 当浏览器收到状态码 200(“OK”)时,它会显示 HTML; 当它收到 404 时,它会显示一个未找到的屏幕; 等等

使用 REST 调用服务器功能

想想我们刚才讲的这些方法。 使用 POST,您可以在服务器上创建一些东西; 使用 GET,您可以读回它; 使用 PUT,您可以更新服务器上的内容; 并且使用 DELETE,您可以删除服务器上的内容。它们也称为 CRUD 操作 (CREATE-READ-UPDATE-DELETE)。 REST 的发明者 Roy Fielding 意识到,使用 HTTP 协议,您还可以使用 HTTP 处理存储在数据库中的数据。 例如,如果您使用带有 URI http://someserver/categories 的 GET 方法,服务器可以执行一些代码来从类别关系表中检索数据并返回它。 当然,服务器会使用更适合传输数据的格式,例如 XML 或 JSON。 因为数据有许多不同的格式,服务器也需要一种方法来传达它正在发送的格式。 (在 Web 的开始,仅使用 HTML 作为格式。)这是通过 HTTP 标头完成的。

HTTP 标头

HTTP 标头是客户端和服务器之间交换的指令。 标头是键/值对,客户端和服务器在键上达成一致。 存在许多标准 HTTP 标头,您可以在 https://en.wikipedia.org/wiki/List_of_HTTP_header_fields 找到它们。 例如,服务器可以使用 Content-Type 标头告诉客户端期望特定格式。 另一个标头是Accept标头,它是客户端发送给服务器的,用于礼貌地要求服务器发送该格式的内容; 这也称为内容协商。 目前,最流行的格式是 JavaScript Object Notation (JSON)。 这是您将与 Blazor 一起使用的交换格式。

JavaScript 对象表示法

JSON 是一种用于传输数据的紧凑格式。 请看清单 6-1 中的示例。

清单 6-1 JSON的一个例子

{ 
    "book" : {
    "title" : "Microsoft Blazor",
    "chapters" : [ "Your first Blazor project", "Data Binding"]
	}
}

这种 JSON 格式描述了一本书,可以很容易地将其转换为内存中的对象。 最简单的 JSON 对象是字符串,例如“Hello world!”,但我们也可以创建复杂的对象和 JSON 对象数组。

这种 JSON 格式描述了一本书,可以很容易地将其转换为内存中的对象。 最简单的 JSON 对象是字符串,例如“Hello world!”,但我们也可以创建复杂的对象和 JSON 对象数组。

对象使用花括号表示,在花括号内,您将看到以逗号分隔的属性列表。 每个属性都使用一个键:值表示法。清单 6-1 包含一个书本对象,其值是另一个嵌套的 JSON 对象。 这个嵌套的 JSON 对象包含两个属性:标题和章节。 标题是一个字符串“Microsoft Blazor”。 请注意,属性名称也作为字符串传输。 最后, chapters 属性是一个字符串数组,您可以在其中使用方括号来表示一个数组。

JSON 格式用于在两台机器之间传输数据,但现在也大量用于配置工具,例如 ASP.NET Core(只需查看 ASP.NET 服务器项目中的 appsettings.json)。 今天的 JSON 在 Web 上比 XML 更流行,可能是因为它的简单性。

REST 调用的一些示例

您需要来自服务器的比萨饼列表,服务器在 URI http://someserver/pizza 处公开比萨饼。 要获取比萨饼列表,请使用 GET 方法,并使用带有值 application/json 的 Accept 标头来请求 JSON 格式。 看这个例子的图 6-2。

image

也许您的客户想要显示 id 为 5 的披萨的详细信息。在这种情况下,它可以将 id 附加到 URI 并执行 GET。 如果服务器没有任何具有该 ID 的披萨,它可以返回状态码 404,如图 6-3 所示。
image

作为最后一个示例,让我们从客户端向服务器发送一些数据。 假设客户已经填写了订单的所有详细信息并单击了“订单”按钮。 然后,您使用 POST 方法将订单作为 JSON 发送到服务器(请记住 POST 表示插入)。 然后服务器可以以它喜欢的任何方式处理订单; 例如,它可以将订单插入其数据库并返回 201: Created 状态码,如图 6-4 所示。 REST 建议返回状态代码 201,并将 Location 标头设置为新创建资源的 URI。

image

使用 ASP.NET Core 构建简单的微服务

服务和单一职责

服务是监听请求的东西(这里是一个软件); 当它收到请求时,服务会处理请求并返回响应。 在第 5 章中,我们构建了一个可以返回比萨饼列表的菜单服务。 在现实生活中,您也会遇到服务,它们非常相似。 考虑一家银行。 你走进一家银行,给出纳员你的帐号、一些身份证件,然后索要 100 美元。 出纳员会检查您的账户; 如果你的账户里有足够的钱,出纳员会扣钱给你现金。 如果您的帐户太低,柜员将拒绝。 在这两种情况下,您都会收到回复。

服务也要坚持责任单一的原则。 他们应该把一件事做好,就是这样。 例如,披萨服务将允许客户端检索、添加、更新和删除披萨。 而已。 一个单一的责任,在这种情况下,PIZZAS。

您也可以拥有其他服务,每个服务都有自己的责任。 处理一件事的服务称为微服务。

Pizza服务

打开您在前几章中使用的 PizzaPlace 解决方案。 在本章中,您将专注于 PizzaPlace.Server 项目。 此项目当前的唯一角色是托管您的 Blazor 客户端应用程序,但现在您将通过添加一些微服务来增强此角色。

打开 Startup.cs 并查看 Configure 方法,如清单 6-2 所示。

清单 6-2 启动类的配置方法

public void Configure(IApplicationBuilder app,
                      IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseWebAssemblyDebugging();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        // The default HSTS value is 30 days. ...
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseBlazorFrameworkFiles();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseEndpoints(endpoints =>
                     {
                         endpoints.MapRazorPages();
                         endpoints.MapControllers();
                         endpoints.MapFallbackToFile("index.html");
                     });
}

endpoints.MapFallbackToFile("index.html") 方法的最后一行负责您的 Blazor 客户端项目。 但就在它之前,您会看到用于托管服务的 endpoints.MapControllers() 方法。

简而言之,ASP.NET MVC 会将 HTTP 请求提供给应该继承 ControllerBase 类的控制器类,然后控制器将执行其方法之一。 它是如何决定的? MapControllers 方法将获取请求的 URL,例如 /pizzas,并使用 URL 的第一段来搜索具有匹配名称的控制器,例如 PizzasController。 因为我们使用的是 REST,所以选择的控制器将选择与动词匹配的方法,例如 GET。 如果您的方法称为 Get(),它将被调用,但您也可以在方法上使用 HttpGet 属性。 在这种情况下,方法的名称并不重要。 使用 HttpGet 属性,您可以指定 URL 的外观,这允许您将 URL 中的参数传递给方法。 我们将很快看一个例子。

下一行是服务器项目的 Controllers 文件夹。 最初,这个文件夹是空的,你的想法是你把你的服务类放在这里。 在 ASP.NET 中,服务类称为控制器,因此是文件夹的名称。

如果您使用的是 Visual Studio,请右键单击此文件夹并选择 Add ➤ Controller。从图 6-5 中选择 API Controller - Empty 并单击 Add。

image

键入 PizzasController 并再次单击添加。
如果您使用的是 Code,只需右键单击 Controllers 文件夹并选择 Add File。将其命名为 PizzasController.cs。 现在完成示例 6-3 中的类。
这将添加一个名为 PizzasController 的新类,它继承自 ControllerBase,如清单 6-3 所示。 这个类也有两个属性。 [ApiController] 属性告诉 ASP.NET 运行时这是 REST 服务的控制器。 [Route] 属性告诉 ASP.NET 运行时它将暴露自己的 URI 是“api/pizzas”。 路由的“[controller]”部分是控制器(Pizzas)名称的占位符,但 买有“Controller”部分。

清单 6-3 添加 PizzasController

using Microsoft.AspNetCore.Mvc;
namespace PizzaPlace.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PizzasController : ControllerBase
    {
    }
}

让我们添加一个 GET 方法来检索比萨饼列表。 目前,您将对列表进行硬编码,但在下一节中,您将从数据库中检索它。 修改 PizzasController,如清单 6-4 所示。

清单 6-4 向 PizzaController 添加方法以检索披萨列表

using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PizzasController : ControllerBase
    {
        private static readonly List<Pizza> pizzas = new List<Pizza>
        {
            new Pizza(1, "Pepperoni", 8.99M, Spiciness.Spicy ),
            new Pizza(2, "Margarita", 7.99M, Spiciness.None ),
            new Pizza(3, "Diabolo", 9.99M, Spiciness.Hot )
        };
        [HttpGet("/pizzas")]
        public IQueryable<Pizza> GetPizzas()
            => pizzas.AsQueryable();
    }
}

让我们来看看这个实现。 首先,您声明一个硬编码的静态比萨饼列表。 接下来是 GetPizzas 方法,它具有属性 HttpGet("/pizzas")。 此属性表示,当您使用 /pizzas URI 在服务器上执行 GET HTTP 方法时,服务器应调用 GetPizzas 方法。 该属性会覆盖类的 Route 属性,因此默认的 api/pizzas 不会调用 GetPizzas 方法。
GetPizzas 方法返回一个 IQueryable<Pizza>,ASP.NET Core 会将此结果作为比萨饼列表发送回客户端。 IQueryable<Pizza> 接口在 .NET 中用于表示可以查询的数据,例如数据库数据,并由 LINQ 查询返回。 为什么是 IQueryable<Pizza>? 因为在本章后面,我们将从公开为这种类型的数据库中返回数据。

请注意,GetPizzas 方法不包含有关如何将数据传输到客户端的任何内容。 这一切都由 ASP.NET Core 为您处理! 默认情况下,您在 ASP.NET Core 中的实现将使用 JSON,这正是您想要的。 ASP.NET Core 允许您选择其他格式,包括您的自定义格式。 客户端可以使用请求中的 Accept 标头请求某种格式,例如 XML 或 JSON。这里,我们将使用默认的 JSON 格式。

是时候看看它是否有效。 首先,确保 PizzaPlace.Server 项目是启动项目(使用 Visual Studio,右键单击 PizzaPlace.Server 项目并从下拉菜单中选择设置为启动项目。PizzaPlace.Server 项目应显示为粗体) .

现在运行您的项目并等待浏览器打开,因为您将执行 GET; 您可以将浏览器用于 GET 方法,但对于其他方法,您将使用一个名为 Postman 的好工具。
将浏览器中的 URI 更改为 http://localhost:xxxx/pizzas,其中 xxxx 是浏览器中的原始端口号(端口号由主机选择,可能与我的不同)。 您应该看到如图 6-6 所示的结果。

image

JSON 编码的比萨饼列表! 有用! 所以在这里我们看到一个对象数组,每个对象都有我们 Pizza 类的属性(除了属性使用小写,这是 JSON 的约定)。
现在您已准备好使用 Entity Framework Core 从真实数据库中检索数据。

什么是Entity Framework Core?

Entity Framework Core 是 Microsoft 推荐用于处理数据库的框架。 Entity Framework Core (EF) 是一个对象关系映射器,它允许您将类编写为普通的 C# 类,然后从数据库中存储和检索 .NET 对象,而无需成为 SQL 专家。 它将为您处理查询、插入、更新和删除数据库中的对象。 这也称为持久性无知,您的代码不需要知道数据的存储方式和位置!Entity Framework Core 支持 SQL Server、SQLite 等。

使用代码优先方法

但是当然,你需要向 Entity Framework Core 解释你想要存储什么样的数据。 Entity Framework Core 使用一种称为 Code First 的技术,您可以在其中编写代码来描述数据以及数据应如何存储在数据库中。 然后,您可以使用它来生成数据库、表和约束。 如果要更改数据库,可以使用代码优先迁移来更新数据库架构。

使用代码优先方法,您可以描述将映射到数据库表的类(也称为实体)。 您已经有 Pizza 类(可以在 PizzaPlace.Shared 项目中找到)来描述数据库中的 Pizza 表。 但是你需要做更多。

在这一部分中,您将使用 SQL Server,或者如果您无权访问 SQL Server,则使用 SQLite。 如果您在 Windows 计算机上安装了 Visual Studio,则也安装了 SQL Server。

您可以按如下方式检查是否安装了 SQL Server:启动 Visual Studio 并从菜单中选择 View > SQL Server Object Explorer。 现在单击添加 SQL Server。 展开本地节点。 如果您有 SQL Server,则应该在本地列出它。

让我们首先将 Entity Framework Core 添加到 PizzaPlace.Server 项目。 如果您使用的是 Visual Studio,请右键单击服务器项目并选择管理 NuGet 包。NuGet 窗口将在 Visual Studio 中打开。 NuGet 是一种将 Entity Framework Core 等依赖项安装到项目中的非常实用的方法。 它不仅会安装 Microsoft.EntityFrameworkCore.SqlServer 库,还会安装它的所有依赖项。

选择浏览选项卡并在搜索框中键入 Microsoft.EntityFrameworkCore.SqlServer(或 Microsoft.EntityFrameworkCore.Sqlite,如果您正在使用它)。 您应该会将此库视为顶部搜索结果(如果没有,请查看 NuGet 窗口的右上角,您将在其中看到 Package Source;选择 nuget.org 作为源)。 选择它,然后从版本下拉列表中选择最新稳定版本,然后单击安装按钮。

使用 Code 打开命令提示符,当前文件夹设置为 PizzaPlace.Server 项目所在的位置,然后键入以下命令:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer

如果您选择使用 SQLite,请使用以下命令:

dotnet add package Microsoft.EntityFrameworkCore.Sqlite

将一个名为 PizzaPlaceDbContext 的新类添加到 PizzaPlace.Server 项目中,如清单 6-5 所示。 此类表示数据库,您确实需要提供一些提示,说明您希望数据如何存储在 SQL Server(或其他一些数据库引擎;这使用相同的代码)中。

清单 6-5 PizzaPlaceDbContext 类

using Microsoft.EntityFrameworkCore;
using PizzaPlace.Shared;
namespace PizzaPlace.Server
{
    public class PizzaPlaceDbContext : DbContext
    {
        public PizzaPlaceDbContext(DbContextOptions<PizzaPlaceDbContext> options)
            : base(options) { }
        public DbSet<Pizza> Pizzas { get; set; } = default!;
        protected override void OnModelCreating(
            ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            var pizzaEntity = modelBuilder.Entity<Pizza>();
            pizzaEntity.HasKey(pizza => pizza.Id);
            pizzaEntity.Property(pizza => pizza.Name)
                .HasMaxLength(80);
            pizzaEntity.Property(pizza => pizza.Price)
                .HasColumnType("money");
            pizzaEntity.Property(pizza => pizza.Spiciness)
                .HasConversion<string>();
        }
    }
}

首先,您需要为带有 DbContextOptions<PizzaPlaceDbContext> 参数的 PizzaPlaceDbContext 类创建一个构造函数。 这用于传递一些选项和与数据库服务器的连接,您将在本节后面进行。

接下来,您使用 DbSet<Pizza> 类型的公共属性向数据库添加一个表来表示您的比萨饼。 DbSet<T> 是 Entity Framework Core 用来表示数据库中的表的集合类,但您可以将其视为 List<T>(Entity Framework Core 的一个很酷的事情是您可以使用集合来代替 使用 SQL 与数据库对话)。 Entity Framework Core 将使用 DbSet 与数据库表交互,在本例中为 Pizzas 表。

最后,您重写 OnModelCreating 方法,该方法采用 modelBuilder 参数。在 OnModelCreating 方法中,您描述了每个 DbSet 应该如何映射到数据库;例如,您可以告诉它要使用哪个表,应该如何调用每列,在数据库中使用哪种类型等。在这种情况下,您可以告诉模型构建器 Pizza 表应该有一个主键,即 Id 属性比萨课。我们告诉它让 Name 最多 80 个字符以及如何将 Price 属性映射到 SQL 类型。您将为此使用 MONEY 类型。最后,我们告诉 EF 应该使用 HasConversion() 方法将 Spiciness 枚举映射到一个字符串。这样,我们最终得到了可读性很好的辣度条目,而不是数字。目前,这对于您当前的实现来说已经足够了。您不必解释每个属性的所有内容,因为有很多可用的默认值。例如,来自 .NET 的字符串类型将映射到数据库中用于字符串的类型。

为代码优先迁移准备项目

现在您已准备好告诉 PizzaPlaze.Server 项目使用 SQL Server(或 SQLite)作为数据库。 你可以通过依赖注入来做到这一点。 在 ASP.NET Core 中,您可以在 Startup 类的 ConfigureServices 方法中配置依赖注入。 让我们看一下清单 6-6 中所示的这个方法。

清单 6-6 Startup.ConfigureServices 方法

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
}

还记得第 5 章中的 IServiceCollection 吗? 在这里,添加了 ASP.NET Core 的依赖项,例如 Controller 和 Razor 页面的依赖项,这些都是您的服务所必需的。
Startup 类还带有一个构造函数,如清单 6-7 所示。

清单 6-7 Startup类的构造函数

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public IConfiguration Configuration { get; }

您需要此构造函数才能访问项目的配置文件。 配置将包含数据库要与之交谈的连接字符串。

现在我们将提供 PizzaPlaceDbContext 类作为 ConfigureServices 方法中的依赖项。 如果您使用的是 SQL Server,请将清单 6-8 中的以下代码添加到 ConfigureServices 方法的末尾。

清单 6-8 添加Entity Framework依赖项

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddDbContext<PizzaPlaceDbContext>(options =>
                                               options.UseSqlServer(
                          Configuration.GetConnectionString("PizzaPlaceDb")));
}

这条语句告诉 ASP.NET Core 您将使用 PizzaPlaceDbContext 并将其存储在 SQL Server 中。 此代码还在配置中查找数据库的连接字符串,您仍需要添加该连接字符串。
如果您选择 SQLite,则需要添加清单 6-9 中的代码。

清单 6-9 使用 SQLite 作为数据库

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddDbContext<PizzaPlaceDbContext>(options =>
                                               options.UseSqlite(
                                Configuration.GetConnectionString("PizzaPlaceDbLite"))
                                              );
}

ASP.NET Core 允许您将配置设置放置在许多不同的地方,例如 JSON 配置文件、环境变量等。我们的服务器项目已经有一个名为 appsettings.json 的配置文件,所以打开它。

您需要添加一个允许访问数据库的连接字符串。 数据库连接字符串告诉您的代码在哪里可以找到数据库服务器、使用哪个数据库以及应该使用哪些凭据登录。更新 appsettings.json 配置文件,如清单 6-10 所示。 这实际上包含两个连接字符串,一个用于 SQL Server,一个用于 SQLite。 SQL Server 连接字符串使用(localdb)\\MSSQLLocalDB 服务器,它是随 Visual Studio 安装的服务器。 当然,如果您使用其他数据库服务器,您还必须更改服务器名称。 www.connectionstrings.com/ 上有很多示例,或者继续阅读以了解如何使用 Visual Studio 获取连接字符串。 SQLite 连接字符串要简单得多,它包含我们将使用 SQLite 存储数据的文件的名称。

清单 6-10 appsettings.json 配置文件

{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "AllowedHosts": "*",
    "ConnectionStrings": {
        "PizzaPlaceDb": "Server=(localdb)\\MSSQLLocalDB;Database=PizzaPlaceDb;
        Trusted_Connection=True;MultipleActiveResultSets=true",
        "PizzaPlaceDbLite": "Data Source=PizzaPlace.db"
    }
}

查找数据库服务器的连接字符串

如果您不确定要使用哪个连接字符串,可以通过选择 View > SQL Server Object Explorer 在 Visual Studio 中找到 SQL Server 的连接字符串。

您可以通过单击带有绿色小 + 号的服务器图标来连接到数据库,如图 6-7 所示。

image

您可以通过展开本地、网络或 Azure 扩展器来查找可用的数据库服务器,如图 6-8 所示。 我建议您尝试查找 MSSQLLocalDB 数据库服务器。 如果您使用其他数据库服务器,您可能需要更改登录数据库的方式。 准备好后,单击连接。

image

接下来,在图 6-9 中的 SQL Server 对象资源管理器中展开 SQL Server 并选择您的服务器。 右键单击它并选择属性。 现在从属性窗口复制连接字符串,将其粘贴到 appsettings.json 中,并将数据库名称更改为 PizzaPlaceDb。

image

创建您的第一个代码优先迁移

您几乎已准备好从代码生成数据库。 首先将 Microsoft.EntityFrameworkCore.Design NuGet 包添加到 PizzaPlace.Server 项目。您需要此包来执行代码优先迁移。

现在您需要创建代码优先迁移。 迁移是一个 C# 类,其中包含需要对数据库进行的更改以将其启动(或关闭)到应用程序所需的数据库架构。 这是通过一个名为 dotnet-ef 的工具完成的。

首先从 Visual Studio 菜单中选择 View > Other Windows > Package Manager Console。 如果您愿意,也可以使用命令行 (cmd.exe)。 如果您使用的是 Code,请使用集成终端或打开命令提示符。

您必须在 PizzaPlace.Server 目录中运行下一个命令,因此请确保您位于正确的目录中(包含 PizzaPlace.Server.csproj 文件的目录)。

您可能还需要安装全局 dotnet-ef 命令行工具。 这是您用来从代码生成迁移并在您对生成的迁移感到满意后更新数据库的工具。 运行以下命令安装迁移工具。 您只需安装此工具一次。

dotnet tool install --global dotnet-ef

现在执行以下命令来创建迁移:

dotnet-ef migrations add CreatingPizzaPlaceDb

在这里,您使用 dotnet-ef 工具添加一个名为创建PizzaPlaceDb。 您可以为迁移选择任何您想要的名称; 一定要选择一个有意义的。 您应该看到以下输出:

Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'

如果您收到错误或警告,请查看 Pizza 和 PizzaPlaceDbContext 类的代码(并可能与本书提供的源代码进行比较),确保所有实体框架包都使用相同的版本,然后重试。

该工具在 PizzaPlace.Server 项目中创建了一个新的 Migrations 文件夹,其中包含两个类似于图 6-10 但时间戳不同的文件。

image

打开清单 6-11 中的 CreatingPizzaDb.cs 文件,看看该工具做了什么。 如果您一直在使用 SQLite,请参阅示例 6-12。

清单 6-11 SQL Server 的 CreatingPizzaDb.cs 文件

namespace PizzaPlace.Server.Migrations
{
    public partial class CreatingPizzaPlaceDb : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Pizzas",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(80)",
                                                maxLength: 80, nullable: false),
                    Price = table.Column<decimal>(type: "money",
                                                  nullable: false),
                    Spiciness = table.Column<string>(type:
                                                     "nvarchar(max)", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Pizzas", x => x.Id);
                });
        }
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Pizzas");
        }
    }
}

迁移类有两种方法:Up 和 Down。 Up 方法将升级数据库模式。 在这种情况下,它将创建一个名为 Pizzas 的新表,其中包含 Id、Name、Price 和 Spiciness 列。

Down 方法通过删除列来降级数据库架构。 在开发过程中,您将对数据库模式进行小幅更改;每次更改都将成为一次迁移。 然后,我们可以使用这些迁移来更新数据库或返回到数据库的先前模式。 当您想要更新生产数据库以匹配开发数据库的模式时,您还可以应用一系列更改。

清单 6-12 SQLite 的迁移类

using Microsoft.EntityFrameworkCore.Migrations;
namespace PizzaPlace.Server.Migrations
{
    public partial class Created : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Pizzas",
                columns: table => new
                {
                    Id = table.Column<int>(type: "INTEGER",
                                           nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    Name = table.Column<string>(type: "TEXT",
                                                maxLength: 80,
                                                nullable: false),
                    Price = table.Column<decimal>(type: "money",
                                                  nullable: false),
                    Spiciness = table.Column<string>(type: "TEXT",
                                                     nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Pizzas", x => x.Id);
                });
        }
        protected override void Down(
            MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Pizzas");
        }
    }
}

生成数据库

现在您已准备好从迁移中生成数据库。 使用 Visual Studio,返回命令行或包管理器控制台窗口(查看 ➤ 其他窗口 ➤ 包管理器控制台),或者使用代码,打开集成终端(查看 ➤ 终端)。 确保您位于包含 PizzaPlace.Server 项目的文件夹中并键入以下命令:

dotnet-ef database update

这刚刚为您创建了数据库! 让我们看一下数据库。 首先,让我们看一下 SQL Server。

在 Visual Studio 中,打开 View > SQL Server Object Explorer 并展开 PizzaPlaceDb 数据库的树,如图 6-11 所示(您可能需要刷新数据库:右键单击 Databases 并选择 Refresh)。

image

增强Pizza微服务

让我们为 Pizza 微服务添加一些功能,以便它使用数据库而不是硬编码数据,并添加一个在数据库中插入披萨的方法。

打开位于 Controllers 文件夹中的 PizzaController 类 PizzaPlace.Server 项目。 首先添加一个以 PizzaPlaceDbContext 作为参数的构造函数,如清单 6-13 所示。

清单 6-13 将 PizzaPlaceDbContext 实例注入Controller

using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PizzasController : ControllerBase
    {
        private readonly PizzaPlaceDbContext db;
        public PizzasController(PizzaPlaceDbContext db)
        {
            this.db = db;
        }
        ...
    }
}

为了与数据库通信,PizzasController 需要一个 PizzaPlaceDbContext 实例,正如您在第 5 章中所了解的,您可以使用构造函数来执行此操作。 构造函数只需要将引用保存在本地字段中(现在)。

您不需要硬编码的比萨饼列表,因此删除静态字段,并更新 GetPizza 方法以改用 PizzaPlaceDbContext,如清单 6-14 所示。 要获取所有比萨饼,您可以简单地使用 PizzaPlaceDbContext 的 Pizzas 属性。实体框架在访问 Pizzas 属性时将访问数据库并返回 Pizza 表中的所有行。 还要删除 Route 属性,因为 GetPizzas 方法指定了它的 URL。

清单 6-14 从数据库中检索比萨饼

using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
    [ApiController]
    public class PizzasController : ControllerBase
    {
        private readonly PizzaPlaceDbContext db;
        public PizzasController(PizzaPlaceDbContext db)
        {
            this.db = db;
        }
        [HttpGet("/pizzas")]
        public IQueryable<Pizza> GetPizzas() => db.Pizzas;
    }
}

现在让我们添加一个在数据库中插入新披萨的方法。将清单 6-15 中的 InsertPizza 方法添加到 PizzasController 类。此方法将从客户端接收一个披萨实例作为 POST 请求正文的一部分,因此您添加 HttpPost 属性和您应该发布到的 URI。 Pizza 对象将发布在请求正文中,这就是为什么 InsertPizza 方法的 Pizza 参数具有 FromBody 属性来告诉 ASP.NET MVC Core 将请求正文转换为 Pizza 实例的原因。该方法将披萨添加到 PizzaPlaceDbContext Pizzas 表中,然后使用 SaveChanges 方法将其保存到数据库中。然后,InsertPizza 方法返回一个 201 Created 状态代码,并将披萨的 URI 作为响应,这与 REST 的约定一样。您可以从控制器的方法返回许多可能的 HTTP 状态代码。但其中最常见的有特殊的帮助方法,可以很容易地返回某个状态码,例如 Ok()、NotFound()。在这种情况下,您返回 201 – Created 状态代码。在本章的下一部分中,您将使用 Postman 来检查此响应。

清单 6-15 InsertPizza 方法

using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PizzasController : ControllerBase
    {
        private readonly PizzaPlaceDbContext db;
        public PizzasController(PizzaPlaceDbContext db)
        {
            this.db = db;
        }
        [HttpGet("/pizzas")]
        public IQueryable<Pizza> GetPizzas()
            => db.Pizzas;
        [HttpPost("/pizzas")]
        public IActionResult InsertPizza([FromBody] Pizza pizza)
        {
            db.Pizzas.Add(pizza);
            db.SaveChanges();
            return Created($"pizzas/{pizza.Id}", pizza);
        }
    }
}

这是对 REST 服务的介绍。 使用所有不同的方法和最佳实践构建真正的服务可能会占用一整本书。 本章的想法是让您启动并运行.

posted @ 2022-09-04 13:58  F(x)_King  阅读(98)  评论(0编辑  收藏  举报