使用 ASP.NET Core、最小 API 和 .NET 6 创建 Web 应用和服务

什么是最小 API?

已完成100 XP

  • 6 分钟

生成 API 的过程可能很复杂,因为它需要支持多种功能,例如路由、在数据存储中进行读取和写入,以及身份验证。 为了节省时间,请从 .NET 中内置的框架和模板着手,这些框架和模板提供了许多你需要的功能。 但在启动并运行基本 API 之前,这些框架可能需要进行大量设置。 对于适用于 .NET 6 的最小 API,情况并非如此。 通过几行代码即可快速入门。

若要开始使用最小 API,主要要求是至少使用 .NET 6。 然后,需要文本编辑器,如 Visual Studio 或 Visual Studio Code,或者所选的任何其他文本编辑器。 最后,你可以使用 Windows、macOS 或 Linux 操作系统。

什么是最小 API?

如果你已开发 .NET Web API,则已使用一种使用控制器的方法。 其思路是让控制器类方法(表示各种 HTTP 谓词)执行操作来完成特定任务。 例如,GetProducts() 将使用 GET 作为 HTTP 谓词来返回产品。

基于控制器的方法和最小 API 有何区别?

简化 Program.cs:基于控制器的 Web API 模板使用 AddControllers 方法连接控制器。 此外,它还连接 Swagger 以提供开放 API 支持。 默认情况下,最小 API 没有此连接,但可以根据需要添加 Swagger。

路由看起来稍有不同:与基于控制器的 Web API 相比,路由看起来略有不同。 在 Web API 中,对于路由,编写如下所示的代码:

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    // add my own routes
});

对于最小 API,直接在 app 实例上添加路由:

app.MapGet("/todos", await (TodoDb db) => db.Todos.ToListAsync());
app.MapPost("/todos", await (Todo todo) => {});
app.MapPut("/todos", (Todo todo) => {});
app.MapDelete("/todos/{id}", (int id) => {}});

同样的功能仍然存在。 你仍可以按照与过去几乎相同的方式来配置数据库、设置 CORS 和添加身份验证。

那么,如何开始?

使用最小 API 来创建 API

安装 .NET 6 后,此命令示例将创建最小 API 项目:

dotnet new web -o PizzaStore -f net6.0

新创建的 PizzaStore 文件夹包含 API 项目。

检查文件

生成的文件与那些通过基于控制器的 API 获得的文件非常类似:

bin/
obj/
appsettings.Development.json
appsettings.json
PizzaStore.csproj
Program.cs

在 PizzaStore.csproj 中,存在如下所示的条目:

<PropertyGroup>
  <TargetFramework>net6.0</TargetFramework>
  <Nullable>enable</Nullable>
</PropertyGroup>

此代码告知你,你使用的是 .NET 6。

了解代码

Program.cs 包含 API 代码。 让我们详细了解一个程序示例:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

如果你使用过早期版本的 .NET,你会注意到现在缺少 using 语句。 通过 .NET 6,编译器将为你找出 using 语句。 这不是你需要关心的事情。

备注

例如,在添加更多功能(如实体框架)时,需要添加 using 语句。 但对于像前面的示例这样的简单 API,你还不需要它们。

在前两行代码中,你将创建一个生成器。 在 builder 中,你将构造一个应用程序实例 app

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

此生成器具有 Services 属性。 通过使用 Services 属性,你可以添加 CORS、实体框架或 Swagger 等功能。 下面是一个示例:

builder.Services.AddCors(options => {});

在 Services 属性中,你告知 API 这是一项可用的功能。 与之相反,app 实例用于实际使用它。 因此,你可以使用 app 实例来设置路由:

app.MapGet("/", () => "Hello World!");

你还可以使用 app 实例来添加中间件。 下面是如何使用 CORS 等功能的示例:

app.UseCors("some unique string");

备注

中间件通常是用于截获请求并执行检查(例如检查身份验证,或确保客户端获允根据 CORS 执行此操作)的代码。 在中间件执行完以后,将执行实际请求。在存储中读取或写入数据,然后将响应发送回调用客户端。

最后,app.Run() 启动 API,让其侦听来自客户端的请求。

若要运行代码,请使用 dotnet run 来启动项目,就像任何 .NET Core 项目一样。 默认情况下,这意味着你在 http://localhost:{PORT} 上有一个运行的项目,其中 PORT 是介于 5000 和 5300 之间的值。

通过 Swagger 添加文档

文档是你对于 API 所需要的内容。 你需要为你自己、你的同事以及可能想要使用 API 的任何第三方开发人员提供它。 它是使文档与 API 在后者发生更改的情况下保持同步的关键。 好的方法是以标准化方式描述 API,确保它是自文档化的。 自文档化是指在代码发生更改时文档会随之更改。

Swagger 实现 Open API 规范。 此格式描述路由,以及它们接受和生成的数据。 Swagger UI 是工具的集合,可将 Open API 规范呈现为网站,并使你可以通过网站与 API 交互。

若要在 API 中使用 Swagger 和 Swagger UI,需要执行以下两项操作:

安装包。 若要安装 Swagger,请指定安装名为 Swashbuckle 的包:

dotnet add package Swashbuckle.AspNetCore --version 6.1.4

配置它。 安装包后,通过代码对其进行配置。 添加几个不同的条目:

添加命名空间。 稍后调用 SwaggerDoc() 并提供 API 的标头信息时,需要此命名空间。

using Microsoft.OpenApi.Models;

添加 AddSwaggerGen()。 此方法在 API 上设置标头信息,例如它调用的内容和版本号。

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
  {
      c.SwaggerDoc("v1", new OpenApiInfo { Title = "Todo API", Description = "Keep track of your tasks", Version = "v1" });
  });

添加 UseSwagger() 和 UseSwaggerUI()。 这两个代码行会告知 API 项目使用 Swagger,并告知在何处查找规范文件 swagger.json。

app.UseSwagger();
app.UseSwaggerUI(c =>
  {
     c.SwaggerEndpoint("/swagger/v1/swagger.json", "Todo API V1");
  });

这就是构建最小 API 所涉及的全部内容! 启动项目并导航到 http://localhost:5000/swagger,将显示如下内容:

练习 – 创建最小 API

你是公司的开发人员,并且你和你的公司已听说过新的最小 API。 经理要求你为它创建一个项目,这样你就可以讨论一下是否要在下一个项目中使用它。

备注

本模块使用 .NET CLI(命令行接口)和 Visual Studio Code 进行本地开发。 完成本模块后,你可以使用 Visual Studio (Windows)、Visual Studio for Mac (macOS) 来应用概念,或使用 Visual Studio Code(Windows、Linux & macOS)继续开发。

此模块使用 .NET 6.0 SDK。 通过在首选终端中运行以下命令,确保你已安装 .NET 6.0:

dotnet --list-sdks

将显示类似于下面的输出:

3.1.100 [C:\program files\dotnet\sdk]
5.0.100 [C:\program files\dotnet\sdk]
6.0.100 [C:\program files\dotnet\sdk]

确保列出了以 6 开头的版本。 如果未列出任何版本或未找到命令,请安装最新的 .NET 6.0 SDK

创建项目的基架

首先,需要搭建项目的基架。 你已安装 .NET 6,现已准备就绪。

在终端中,运行 dotnet new 命令,在名为 PizzaStore 的目录中创建一个 Web API:

dotnet new web -o PizzaStore -f net6.0

切换到新的 PizzaStore 目录。

cd PizzaStore

通过调用 dotnet run 运行应用。 它生成应用,并将其托管在 5000 到 5300 之间的端口上。 HTTPS 为其选择了范围为 7000 到 7300 的端口。 

备注

如果要替代随机端口选择行为,可以设置要在 launchSettings.json 中使用的端口。

dotnet run

下面是输出在终端中的情况:

Building...
 info: Microsoft.Hosting.Lifetime[14]
       Now listening on: https://localhost:7200
 info: Microsoft.Hosting.Lifetime[14]
       Now listening on: http://localhost:5100
 info: Microsoft.Hosting.Lifetime[0]
       Application started. Press Ctrl+C to shut down.
 info: Microsoft.Hosting.Lifetime[0]
       Hosting environment: Development
 info: Microsoft.Hosting.Lifetime[0]
       Content root path: /<path>/PizzaStore

在浏览器中,转到指示的端口。 根据终端 http://localhost:{PORT},应会看到文本“Hello World!”

祝贺你! 你已使用最小的 API 模板创建了一个 API。

添加 Swagger

使用 Swagger 来确保你有一个自文档化 API,其中的文档会随着代码的更改而更改。

按 Ctrl+C 停止运行 API。

安装 Swashbuckle 包:

dotnet add package Swashbuckle.AspNetCore --version 6.2.3

在 Visual Studio Code 中打开项目:

code .

在 Visual Studio Code 的“资源管理器”窗格中,打开“PizzaStore.csproj”。 你应该有一个如下所示的条目:

<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />

接下来,将项目配置为使用 Swagger。

打开“Program.cs”,并添加已突出显示的代码。 请务必保存所做的更改。

using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);
    
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
     c.SwaggerDoc("v1", new OpenApiInfo { Title = "PizzaStore API", Description = "Making the Pizzas you love", Version = "v1" });
});
    
var app = builder.Build();
    
app.UseSwagger();
app.UseSwaggerUI(c =>
{
   c.SwaggerEndpoint("/swagger/v1/swagger.json", "PizzaStore API V1");
});
    
app.MapGet("/", () => "Hello World!");
    
app.Run();

按 Ctrl+` 在 Code 中打开终端。 在新终端中,再次运行应用:

dotnet run

导航到应用的 Swagger 终结点 http://localhost:{PORT}/swagger。应会看到以下输出:

在终端中,按 Ctrl+C 终止程序。

你已成功使用 Swagger 向最小 API 添加了 Open API 支持!

了解如何添加路由和使用其他高级命令

现在,你已详细了解了什么是最小 API 以及为何使用它。 但如何生成应用并处理诸如路由参数、已发布的正文以及返回比文本字符串更高级的数据之类的事项呢?

若要支持所谓的 RESTful API,需要支持使用 HTTP 谓词并将这些谓词附加到不同的路由。 不同的谓词具有不同的含义。 请遵守 HTTP 谓词的含义。

HTTP 谓词说明
GET返回数据
POST发送创建资源的数据
PUT发送更新资源的数据
DELETE删除资源

备注

资源是一段数据。 例如,它可以是产品、用户或订单。 这是你可能要操作的内容,并且你想要管理其生命周期。

最小 API 中的 HTTP 谓词

当客户端发起请求时,它会向服务器终结点发起请求。 假设向 GET http://localhost:3000/products 发出请求。 服务器验证请求,以查看使用了什么 HTTP 谓词。 服务器还需要知道请求的去向,这由“/products”指示。然后,服务器会尝试通过生成响应来解析请求。

备注

服务器还可能尝试验证客户端在请求过程中发送的任何身份验证凭据。 该活动超出了本模块的范畴。

最小 API 通过提供便捷方法来处理路由和 HTTP 谓词。 可在对 localhost:3000/products 的请求中通过 HTTP 谓词和路由(其中路由为 “/products”)来映射请求。 下面是此类便利方法的示例:

app.MapGet("/products", () => data);

应按以下方式解读此代码:如果客户端对路由 "/products" 使用 GET HTTP 谓词,则使用 data 进行响应。

GET:提取资源

使用 GET 请求进行路由时,需了解两个主要情况:

仅路由:你已经见过此路由。 例如:

app.MapGet("/products", () => data);

使用路由参数:路由参数用于查找特定资源。 如果 “/products” 表示所有产品的列表,则 /products/1 表示特定记录。 唯一标识符的值为“1”。 若要处理此类请求,请使用通配符来匹配它。 在前面的示例中,我们使用“{ID}”来捕获“1”。 还可以将捕获的值映射到参数:

app.MapGet("/products/{id}", (int id) => data.SingleOrDefault(product => product.Id == id));

在此代码中,id 参数已捕获客户端发送的路由参数,即 /products/1 中的“1”。

POST:创建资源

通常还需要创建资源。 若要进行创建,请使用 POST HTTP 谓词。 要使用的方法称为 MapPost()

app.MapPost("/products", (Product product) => /**/);

请注意如何将 product 发送到处理请求的 Lambda 中。 假设下面的 JSON 是客户端发送并已由框架序列化的正文。 假设客户端已发送以下 JSON 作为其正文:

{
  "Name" : "New product",
  "Description": "a description"
}

那么,此 JSON 就可以将这些字段映射到相同形状的对象实例。 下面是与所述的已发布正文匹配的 Product 类:

public record Product(int Id, string Name); 

PUT:更新资源

谓词 PUT 用于更新资源。 因此,框架使用方法 MapPut()。 MapPut() 方法在语义上类似于 MapPost()。 其思路是,作为客户端,你发送的已发布正文应带有包含更改的资源。 需要将这些更改应用于服务器的现有资源。 下面介绍如何使用 MapPut()

app.MapPut("/products", (Product product) => /* Update the data store using the `product` instance */);

DELETE:删除资源

若要支持 HTTP 谓词 DELETE,请使用 MapDelete()。 这里的思路是让客户端通过独一无二的标识符进行发送,该标识符有助于服务器识别要删除的记录。 下面是此方法的典型用法:

app.MapDelete("/products/{id}", (int id) => /* Remove the record whose unique identifier matches `id` */);

返回响应

默认情况下,当你使用某些类型进行响应时,框架会认识到这些类型应序列化为 JSON。 下面是一些用例:

app.MapGet("/products", () => products);
app.MapGet("/products/{id}", (int id) => products.SingleOrDefault(product => product.Id == id));
app.MapGet("/product", () => new { id = 1 });

对于这些用例,你会收到类似于以下示例的 JSON 响应:

// app.MapGet("/products", () => products);
[{
  "id": 1,
  "name": "a product"
}, {
  "id": 2,
  "name": "another product"
}]

// app.MapGet("/products/{id}", (int id) => products.SingleOrDefault(product => product.Id == id));
[{
  "id": 1,
  "name": "a product"
}]

// app.MapGet("/product", () => new { id = 1 });
{
  "id": 1
}

练习 – 添加路由

第一个使用最小 API 的项目反响良好。 现在,你的公司想要看到一个执行更多功能(例如管理资源)的应用程序。 使用的资源由你决定。 你觉得饿了,那么就做一个管理披萨的应用程序?

添加数据

首先需要一些数据。 你将使用内存中存储来存储和管理数据。

使用 Visual Studio Code,在项目根目录中创建一个名为 Db.cs 的文件,并为它提供以下内容:

namespace PizzaStore.DB; 

 public record Pizza 
 {
   public int Id {get; set;} 
   public string ? Name { get; set; }
 }

 public class PizzaDB
 {
   private static List<Pizza> _pizzas = new List<Pizza>()
   {
     new Pizza{ Id=1, Name="Montemagno, Pizza shaped like a great mountain" },
     new Pizza{ Id=2, Name="The Galloway, Pizza shaped like a submarine, silent but deadly"},
     new Pizza{ Id=3, Name="The Noring, Pizza shaped like a Viking helmet, where's the mead"} 
   };

   public static List<Pizza> GetPizzas() 
   {
     return _pizzas;
   } 

   public static Pizza ? GetPizza(int id) 
   {
     return _pizzas.SingleOrDefault(pizza => pizza.Id == id);
   } 

   public static Pizza CreatePizza(Pizza pizza) 
   {
     _pizzas.Add(pizza);
     return pizza;
   }

   public static Pizza UpdatePizza(Pizza update) 
   {
     _pizzas = _pizzas.Select(pizza =>
     {
       if (pizza.Id == update.Id)
       {
         pizza.Name = update.Name;
       }
       return pizza;
     }).ToList();
     return update;
   }

   public static void RemovePizza(int id)
   {
     _pizzas = _pizzas.FindAll(pizza => pizza.Id != id).ToList();
   }
 }

有了数据存储后,接下来让 API 使用它。

将数据连接到路由

为了将内存中存储连接到 API,需要执行以下操作:

  1. 添加命名空间。 这种添加很简单,只需添加适当的 using 语句即可。
  2. 设置路由。 请确保添加创建、读取、更新和删除所需的所有路由映射。
  3. 在路由中调用它。 最后,需要根据每个路由调用内存中存储,并传入请求中的任何参数或正文(如果适用)。

现在,在 API 中连接数据。

在 Program.cs 文件顶部添加以下代码行:

using PizzaStore.DB;

在 app.Run() 前面添加以下代码:

app.MapGet("/pizzas/{id}", (int id) => PizzaDB.GetPizza(id));
app.MapGet("/pizzas", () => PizzaDB.GetPizzas());
app.MapPost("/pizzas", (Pizza pizza) => PizzaDB.CreatePizza(pizza));
app.MapPut("/pizzas", (Pizza pizza) => PizzaDB.UpdatePizza(pizza));
app.MapDelete("/pizzas/{id}", (int id) => PizzaDB.RemovePizza(id));

确保你已保存所有更改。 在终端运行应用:

dotnet run

在浏览器中转到 http://localhost:{PORT}/swagger。你会看到以下页面在呈现:

就这么简单! 你已完全实现最小 API。 可以使用 Swagger UI 测试每个路由。

总结

在此模块中,你了解了如何使用适用于 .NET 6 的最小 API 模板来创建 API。

使用最小 API,只需几行代码就可以创建 API。 它具有用于依赖项注入、与数据库通信和路由管理之类的操作的所有主要功能。 最小 API 不同于基于控制器的 API,因为你需要显式指定所需的路由,而不是依赖于基于约定的方法(例如使用基于控制器的 API)。

这种方法有多个好处:

  • 更易于入门:通过四行代码,就可以快速启动并运行 API。
  • 渐进式增强:根据需要添加功能。 在此之前,程序代码将保持较小。
  • .NET 6 最新功能:使用 .NET 6 中的所有最新功能,例如顶级语句和记录。

作为本模块的一部分,你了解了如何添加 Swagger。 还添加了用于创建、读取、更新和删除资源的路由。

给TA打赏
共{{data.count}}人
人已打赏
.NET.Net Core

在ASP.NET和ASP.NET Core之间共享代码教程

2022-8-22 16:38:07

.NET

为微服务生成 Dockerfile

2022-8-19 12:06:14

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索