C# 13 和 .NET 9 全知道 :15 构建和使用 Web 服务 (1)
bigegpt 2025-01-07 10:59 4 浏览
本章将介绍如何使用 ASP.NET Core Minimal APIs 构建 Web 服务(即 HTTP 或表现层状态转移(REST)服务)。然后,您将学习如何使用 HTTP 客户端消费 Web 服务,这可以是任何其他类型的 .NET 应用程序,包括网站或移动或桌面应用程序。我们将创建一个 Blazor WebAssembly 客户端。
本章需要您在第 10 章《使用 Entity Framework Core 处理数据》和第 12 至 14 章关于使用 ASP.NET Core 和 Blazor 构建网站中获得的知识和技能。
在本章中,我们将涵盖以下主题:
- 使用 ASP.NET Core 构建 Web 服务
- 为 Northwind 数据库创建一个 Web 服务
- 记录和尝试网络服务
- 使用 HTTP 客户端消费 Web 服务
使用 ASP.NET Core 构建 Web 服务
在我们构建现代网络服务之前,我们需要介绍一些背景,以为本章设定上下文。
理解网络服务缩略语
尽管 HTTP 最初是为了请求和响应 HTML 及其他供人类查看的资源而设计的,但它也适合构建服务。
罗伊·菲尔丁在他的博士论文中描述了 REST 架构风格,他指出 HTTP 标准适合构建服务,因为它定义了以下内容:
- URIs to uniquely identify resources, like https://localhost:5151/products/23.
- 在这些资源上执行常见任务的方法,如 GET 、 POST 、 PUT 和 DELETE 。
- 在请求和响应中协商交换内容的媒体类型的能力,例如 XML 和 JSON。当客户端指定请求头如 Accept: application/xml,*/*;q=0.8 时,会发生内容协商。ASP.NET Core Web 服务使用的默认响应格式是 JSON,这意味着响应头之一将是 Content-Type: application/json; charset=utf-8 。
网络服务使用 HTTP 通信标准,因此有时被称为 HTTP 服务或 RESTful 服务。
理解 HTTP 请求和响应
HTTP 定义了标准请求类型和标准代码以指示响应类型。它们中的大多数可以用于实现 Web 服务。
最常见的请求类型是 GET ,用于检索由唯一路径标识的资源,附加选项包括可接受的媒体类型,以设置为请求头,例如 Accept ,如下例所示:
GET /path/to/resource
Accept: application/json
常见的响应包括成功和多种类型的失败,如表 15.1 所示:
状态码 | 描述 |
101 Switching Protocols | 请求者已要求服务器切换协议,服务器已同意这样做。例如,通常会从 HTTP 切换到 WebSockets(WS)以实现更高效的通信。 |
103 Early Hints | 用于传达提示,以帮助客户端准备处理最终响应。例如,服务器可能会在发送正常的 200 OK 响应之前,先发送以下响应,用于使用样式表和 JavaScript 文件的网页:
|
200 OK | 路径正确形成,资源成功找到,序列化为可接受的媒体类型,然后在响应体中返回。响应头指定了 Content-Type 、 Content-Length 和 Content-Encoding ,例如 GZIP 。 |
301 Moved Permanently | 随着时间的推移,Web 服务可能会更改其资源模型,包括用于识别现有资源的路径。Web 服务可以通过返回此状态代码和一个名为 Location 的响应头来指示新路径,该响应头包含新路径。 |
302 Found | 这与 301 是一样的。 |
304 Not Modified | 如果请求包含 If-Modified-Since 头部,则网络服务可以使用此状态码进行响应。响应体为空,因为客户端应该使用其缓存的资源副本。 |
307 Temporary Redirect | 请求的资源已临时移动到 Location 头中的 URL。浏览器应使用该 URL 发出新请求。例如,如果您启用 UseHttpsRedirection 并且客户端发出 HTTP 请求,则会发生这种情况。 |
400 Bad Request | 请求无效,例如,它使用了一个产品的路径,但该路径使用了一个缺失 ID 值的整数 ID。 |
401 Unauthorized | 请求是有效的,资源已找到,但客户端未提供凭据或未被授权访问该资源。重新认证可能会启用访问,例如,通过添加或更改 Authorization 请求头。 |
403 Forbidden | 请求是有效的,资源已找到,但客户端没有权限访问该资源。重新认证不会解决此问题。 |
404 Not Found | 请求是有效的,但未找到资源。如果稍后重复请求,可能会找到该资源。要表示资源将永远无法找到,请返回 410 Gone 。 |
406 Not Acceptable | 如果请求有一个 Accept 头,只列出网络服务不支持的媒体类型。例如,如果客户端请求 JSON,但网络服务只能返回 XML。 |
451 Unavailable for Legal Reasons | 一个在美国托管的网站可能会对来自欧洲的请求返回这个,以避免必须遵守通用数据保护条例(GDPR)。这个数字是作为对小说《华氏 451 度》的参考,其中书籍被禁止和焚烧。 |
500 Server Error | 请求是有效的,但在处理请求时服务器端出现了问题。稍后重试可能会有效。 |
503 Service Unavailable | 该网络服务繁忙,无法处理请求。稍后再试可能会成功。 |
表 15.1:GET 方法的常见 HTTP 状态码响应
其他常见的 HTTP 请求类型包括 POST 、 PUT 、 PATCH 或 DELETE ,用于创建、修改或删除资源。
要创建一个新资源,您可以发出一个 POST 请求,正文包含新资源,如以下代码所示:
POST /path/to/resource
Content-Length: 123
Content-Type: application/json
要创建一个新资源或更新现有资源,您可能会发出一个 PUT 请求,主体包含现有资源的全新版本,如果资源不存在,则会创建它;如果资源存在,则会替换它(有时称为插入或更新操作),如下代码所示:
PUT /path/to/resource
Content-Length: 123
Content-Type: application/json
为了更高效地更新现有资源,您可以发出一个 PATCH 请求,主体包含一个仅包含需要更改的属性的对象,如以下代码所示:
PATCH /path/to/resource
Content-Length: 123
Content-Type: application/json
要删除现有资源,您可以发出一个 DELETE 请求,如以下代码所示:
DELETE /path/to/resource
除了上表中显示的针对 GET 请求的响应外,所有创建、修改或删除资源的请求类型还有其他可能的通用响应,如表 15.2 所示:
状态码 | 描述 |
201 Created | 新资源创建成功,响应头名为 Location 包含其路径,响应体包含新创建的资源。立即 GET 该资源应返回 200 。 |
202 Accepted | 新的资源无法立即创建,因此请求被排队以便稍后处理,并且立即 GET 该资源可能会返回 404 。请求体可以包含指向某种状态检查器或估计资源何时可用的资源。 |
204 No Content | 通常用于响应 DELETE 请求,因为在删除资源后将其返回在主体中通常没有意义!有时用于响应 POST 、 PUT 或 PATCH 请求,如果客户端不需要确认请求已正确处理。 |
405 Method Not Allowed | 当请求使用不支持的方法时返回。例如,设计为只读的网络服务可能明确禁止 PUT 、 DELETE 等。 |
415 Unsupported Media Type | 当请求体中的资源使用了网络服务无法处理的媒体类型时返回。例如,如果请求体包含 XML 格式的资源,但网络服务只能处理 JSON。 |
表 15.2:对其他方法如 POST 和 PUT 的常见 HTTP 状态代码响应
ASP.NET Core 最小化 API 项目
我们将构建一个网络服务,提供一种使用 ASP.NET Core 与 Northwind 数据库中的数据进行交互的方法,以便任何能够发出 HTTP 请求并接收 HTTP 响应的客户端应用程序都可以使用这些数据。
传统上,您使用 ASP.NET Core Web API / dotnet new webapi 项目模板。这允许创建一个使用控制器或较新的最小 API 实现的 Web 服务。
警告!在 .NET 6 和 .NET 7 中, dotnet new webapi 命令创建一个使用控制器实现的服务。在 .NET 6 和 .NET 7 中,要使用最小 API 实现服务,您需要在命令中添加 --use-minimal-apis 开关。使用 .NET 8 或更高版本, dotnet new webapi 命令创建一个使用最小 API 实现的服务。要使用控制器实现服务,您需要添加 --use-controllers 开关。
最小化 API 网络服务和原生 AOT 编译
.NET 8 引入了 ASP.NET Core Web API(原生 AOT)/ dotnet new webapiaot 项目模板,该模板仅使用最小化 API 并支持原生 AOT 发布。随着时间的推移,.NET 的更多组件将支持 AOT,正如您在以下引用中所读到的:
“我们预计在 .NET 9 时间框架内对 MVC 和 Blazor 的原生 AOT 支持进行调查会有所进展,但考虑到涉及的大量工作,我们不期望为 .NET 9 提供生产就绪的原生 AOT 支持。” – Dan Roth
良好实践:最小化 API 特别适合垂直切片架构(VSA)。最小化 API 相对于基于控制器的 Web API 的一个主要好处是,每个最小化 API 端点只需要实例化它所需的依赖注入(DI)服务。使用控制器时,要执行该控制器中的任何操作方法,所有在任何操作方法中使用的 DI 服务必须在每次调用时实例化。这是浪费时间和资源!
创建一个 ASP.NET Core 最小化 API 项目
我们走吧:
- 使用您首选的代码编辑器打开 ModernWeb 解决方案,然后添加一个新项目,如下列表所定义:项目模板:ASP.NET Core Web API / webapi解决方案文件和文件夹: ModernWeb项目文件和文件夹: Northwind.WebApi
- 如果您正在使用 Visual Studio,请确认已选择以下默认设置:认证类型:无配置为 HTTPS:已选择启用容器支持:已清除启用 OpenAPI 支持:已选择请勿使用顶级语句:已清除 使用控制器:已清除
确保清除“使用控制器”复选框,否则您的代码将与本书中看到的非常不同!
如果您正在使用 VS Code 或 Rider,请在 ModernWeb 目录下的命令提示符或终端中输入以下内容:
dotnet new webapi -o Northwind.WebApi
- 构建 Northwind.WebApi 项目。
- 在项目文件中,删除实现 OpenAPI Web 服务文档的包的版本号,因为我们正在使用 CPM,如下所示的标记:
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
在 Program.cs 中,查看代码,如下所示:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
internal record WeatherForecast(DateOnly Date,
int TemperatureC, string? Summary)
{
public int TemperatureF => 32 +
(int)(TemperatureC / 0.5556);
}
在审查前面的代码时,请注意以下几点:
- 该程序的配置与其他任何 ASP.NET Core 项目类似,首先调用 WebApplication.CreateBuilder 。
- 服务集合中添加了一个 OpenAPI 服务。这个服务用于记录 Web 服务。在 .NET 8 及之前版本中,使用了第三方的 Swashbuckle 包来实现这一点,但在 .NET 9 及之后版本中,微软编写了自己的实现。您可以在以下链接中阅读更多信息:https://github.com/dotnet/aspnetcore/issues/54599。默认情况下,OpenAPI 文档生成会创建一个符合 OpenAPI 规范 v3.0 的文档:https://spec.openapis.org/oas/v3.0.0。
- 在开发过程中,OpenAPI 文档被映射为端点,以便其他开发人员可以轻松地使用它来创建客户端。默认情况下,通过调用 MapOpenApi 注册的 OpenAPI 端点在 /openapi/{documentName}.json 端点处公开文档。默认情况下, documentName 是 v1 。在生产环境中,这些端点不再被映射,因为它们不再必要。
- MapGet 调用注册了相对路径 /weatherforecast 以响应 HTTP GET 请求,其实现使用共享 Random 对象返回一个包含随机温度和摘要的 WeatherForecast 对象数组,例如 Bracing 或 Balmy ,用于接下来五天的天气。
现在让我们允许 HTTP 请求指定预测应该提前多少天。同时,我们将通过将天气端点实现放在自己的代码文件中来实施良好的实践:
- 添加一个名为 Program.Weather.cs 的新类文件。
- 在 Program.Weather.cs 中,添加语句以扩展自动生成的 partial Program 类,通过移动(剪切并粘贴语句)来自 Program.cs 的天气相关语句,并进行小的调整,例如定义一个 GetWeather 方法,带有一个 days 参数,以控制生成多少个天气预报,如以下代码所示:
public partial class Program
{
static string[] summaries = { "Freezing", "Bracing",
"Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot",
"Sweltering", "Scorching" };
internal static WeatherForecast[]? GetWeather(int days)
{
WeatherForecast[]? forecast = Enumerable.Range(1, days)
.Select(index => new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
}
internal record WeatherForecast(DateOnly Date,
int TemperatureC, string? Summary)
{
public int TemperatureF => 32 +
(int)(TemperatureC / 0.5556);
}
}
在 Program.cs 中,修改 MapGet 调用,如以下代码中突出显示的内容所示:
app.UseHttpsRedirection();
app.MapGet("/weatherforecast/{days:int?}",
(int days = 5) => GetWeather(days))
.WithName("GetWeatherForecast");
app.Run();
在 MapGet 调用中,请注意路由模板模式 {days:int?} 限制了 days 参数为 int 值。 ? 使 days 参数可选,如果缺失将默认为 5 。
审查网络服务的功能
现在,我们将测试网络服务的功能:
- 在 Properties 文件夹中,在 launchSettings.json ,请注意,如果您使用的是 Visual Studio,默认情况下, https 配置文件将启动浏览器并导航到 /weatherforecast 相对 URL 路径,如以下标记中突出显示的内容所示:
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
对于 https 配置文件,对于其 applicationUrl ,将 HTTPS 的随机端口号更改为 5151 ,将 HTTP 的随机端口号更改为 5150 ,如以下标记中突出显示所示:
"applicationUrl": "https://localhost:5151;http://localhost:5150",
- 保存对所有已修改文件的更改。
- 使用 https 启动配置启动 Northwind.WebApi 网络服务项目。
- 在 Windows 上,如果您看到一个 Windows 安全警报对话框,提示 Windows Defender 防火墙已阻止此应用程序的一些功能,请点击允许访问按钮。
- 启动 Chrome,导航到 https://localhost:5151/ ,请注意您将收到 404 状态代码响应,因为我们尚未启用静态文件,并且没有 index.xhtml 。请记住,该项目并不是为了让人类查看和交互,因此这是网络服务的预期行为。
- 在 Chrome 中,显示开发者工具。
- 导航到 https://localhost:5151/weatherforecast ,并注意网络服务应返回一个包含五个随机天气预报对象的 JSON 文档,格式为数组,如图 15.1 所示:
- 关闭开发者工具。
- 导航到 https://localhost:5151/weatherforecast/14 ,并注意请求两周天气预报时的响应包含 14 个预报。
- 选择“美化打印”复选框,如图 15.1 所示,并注意最近版本的 Chrome 现在可以更好地格式化 JSON 响应,以便人类阅读。
- 关闭 Chrome 并关闭网络服务器。
路线约束
要注册 /weatherforecast 路由端点,我们使用了路由约束来限制 days 参数的可接受值为整数,如以下代码中所示的高亮部分:
app.MapGet("/weatherforecast/{days:int?}", ...
路线约束允许我们根据数据类型和其他验证来控制匹配。它们在表 15.3 中总结:
约束 | 示例 | 描述 |
required | {id:required} | 参数已提供。 |
int 和 long | {id:int} | 任何正确大小的整数。 |
decimal , double , 和 float | {unitprice:decimal} | 任何正确大小的实数。 |
bool | {discontinued:bool} | 对 true 或 false 进行不区分大小写的匹配。 |
datetime | {hired:datetime} | 不变的文化日期/时间。 |
guid | {id:guid} | 一个 GUID 值。 |
minlength(n) , maxlength(n) , length(n) , 和 length(n, m) | {title:minlength(5)}, {title:length(5, 25)} | 文本必须具有定义的最小和/或最大长度。 |
min(n) , max(n) , 和 range(n, m) | {age:range(18, 65)} | 整数必须在定义的最小和/或最大范围内。 |
alpha, regex | {firstname:alpha}, {id:regex(^[A-Z]{{5}}$)} | 参数必须匹配一个或多个字母字符或正则表达式。 |
表 15.3:带有示例和描述的路线约束
使用冒号分隔多个约束,如下例所示:
app.MapGet("/weatherforecast/{days:int:min(5)}", ...
对于正则表达式, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant 会自动添加。正则表达式标记必须转义(将 \ 替换为 \\ ,将 { 替换为 {{ ,将 } 替换为 }} )或使用逐字字符串字面量。
更多信息:您可以通过定义一个实现 IRouteConstraint 的类来创建自定义路由约束。这超出了本书的范围,但您可以在以下链接中阅读相关内容:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#custom-route-constraints。
短路路径
当路由将请求匹配到一个端点时,它会让其余的中间件管道运行,然后再调用端点逻辑。这需要时间,因此在 ASP.NET Core 8 及更高版本中,您可以立即调用端点并返回响应。
您可以通过在映射的端点路由上调用 ShortCircuit 方法来实现此操作,如以下代码所示:
app.MapGet("/", () => "Hello World").ShortCircuit();
或者,您可以调用 MapShortCircuit 方法来响应 404 Missing Resource 或其他状态代码,对于不需要进一步处理的资源,如以下代码所示:
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
相关推荐
- 5分钟搭建公网https网页文件服务器,免费权威TLS证书
-
请关注本头条号,每天坚持更新原创干货技术文章。如需学习视频,请在微信搜索公众号“智传网优”直接开始自助视频学习前言本文主要讲解如何快速搭建一个https网页文件服务器,并免费申请权威机构颁发的tls证...
- nginx负载均衡配置(nginx负载均衡配置两个程序副本)
-
Nginx是什么没有听过Nginx?那么一定听过它的“同行”Apache吧!Nginx同Apache一样都是一种WEB服务器。基于REST架构风格,以统一资源描述符(UniformResources...
- 19《Nginx 入门教程》Nginx综合实践
-
今天我们将基于Nginx完成两个比较有用的场景,但是用到的Nginx的配置非常简单。内部Yum源搭建内部Pip源搭建1.实验环境ceph1centos7.6内网ip:172.16....
- Nginx性能调优与优化指南(nginx优化配置大全)
-
Nginx性能调优需要结合服务器硬件资源、业务场景和负载特征进行针对性优化。以下是一些关键优化方向和具体配置示例:一、Nginx配置优化1.进程与连接数优化nginxworker_process...
- C++后端开发必须彻底搞懂Nginx,从原理到实战(高级篇)
-
本文为Nginx实操高级篇。通过配置Nginx配置文件,实现正向代理、反向代理、负载均衡、Nginx缓存、动静分离和高可用Nginx6种功能,并对Nginx的原理作进一步的解析。当需...
- 【Nginx】史上最全的Nginx配置详解
-
Nginx服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里,http块又包括http全局块和server块。Nginx是非常重要的负载均衡中间件,被广泛应用于大型...
- 【Nginx】Nginx 4种常见配置实例(nginx基本配置与参数说明)
-
本文主要介绍nginx4种常见的配置实例。Nginx实现反向代理;Nginx实现负载均衡;Nginx实现动静分离;Nginx实现高可用集群;Nginx4种常见配置实例如下:一、Nginx反向代理配...
- 使用nginx+allure管理自动化测试报告
-
allure在自动化测试中经常用来生成漂亮的报告,但是网上及官网上给出的例子都仅仅是针对单个测试用例文件的形式介绍的,实际使用中,自动化测试往往需要包含不止一个产品或项目,本文介绍如何使用nginx+...
- nginx配置文件详解(nginx配置文件详解高清版)
-
Nginx是一个强大的免费开源的HTTP服务器和反向代理服务器。在Web开发项目中,nginx常用作为静态文件服务器处理静态文件,并负责将动态请求转发至应用服务器(如Django,Flask,et...
- SpringCloud Eureka-服务注册与发现
-
1.Eureka介绍1.1学习Eureka前的说明目前主流的服务注册&发现的组件是Nacos,但是Eureka作为老牌经典的服务注册&发现技术还是有必要学习一下,原因:(1)一些早期的分布式微服...
- 微服务 Spring Cloud 实战 Eureka+Gateway+Feign+Hystrix
-
前言我所在项目组刚接到一个微服务改造需求,技术选型为SpringCloud,具体需求是把部分项目使用SpringCloud技术进行重构。本篇文章中介绍了Eureka、Gateway、Fe...
- 深度剖析 Spring Cloud Eureka 底层实现原理
-
你作为一名互联网大厂后端技术开发人员,在构建分布式系统时,是不是常常为服务的注册与发现而头疼?你是否好奇,像SpringCloudEureka这样被广泛使用的组件,它的底层实现原理到底是怎样的...
- 热爱生活,喜欢折腾。(很热爱生活)
-
原文是stackoverflow的一则高票回答,原文链接可能之前也有人翻译过,但是刚好自己也有疑惑,所以搬运一下,个人水平有限所以可能翻译存在误差,欢迎指正(如侵删)。尽管classmethod和st...
- GDB调试的高级技巧(详细描述gdb调试程序的全过程)
-
GDB是我们平时调试c/c++程序的利器,查起复杂的bug问题,比打印大法要好得多,但是也不得不说,gdb在默认情况下用起来并不是很好用,最近学习到几个高级点的技巧,分享下:一美化打印先上个例子...
- Arduino 实例(二十三)Arduino 给Python 编译器发送信息
-
1首先Python需要安装Pyserial库,在命令提示符中输入pipintallpyserial若是遇到提示‘pip‘不是内部或外部命令,也不是可运行的程序或批处理文件,则需要设置环境变...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- skip-name-resolve (63)
- linuxlink (65)
- pythonwget (67)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)