ASP.NET Core 最小 API 简介

590 阅读11分钟

在最近的 .NET 版本中,有一种使用 ASP.NET Core 构建基于 JSON 的 API 的新方法:最小 API。受 ASP.NET 生态系统和来自其他社区的元素的先前尝试的启发,最小 API 方法试图简化生成 JSON 的 HTTP API 的开发。

这篇文章探讨了为什么使用 Minimal API 有意义、与 ASP.NET Core MVC 相比的编程模型,以及一些可能会让您考虑使用它的缺点。

为什么选择 ASP.NET Core 最小 API?

在 .NET Core 和 .NET 的多个版本中,性能一直是 .NET 团队关注的焦点。虽然 ASP.NET Core MVC 是构建 Web API 的可靠且生产强化的方法,但 MVC 管道的复杂性导致在处理传入的 HTTP 请求时浪费大量时间和资源。 

标准 ASP.NET Core MVC 请求的步骤包括路由、控制器初始化、使用模型绑定和筛选器执行操作以及结果筛选器。处理一个请求通常是一个 17 步的过程,如果您使用任何视图引擎,还需要更多步骤。在构建基于 JSON 的 API 时,您可能会将这些步骤视为“不必要的”,从而减少获得更高吞吐量的机会。

除了性能影响,一些人可能会发现 ASP.NET Core MVC 的“约定优于配置”方法“太神奇了”。 例如,ASP.NET Core 通过扫描路由属性或匹配用户定义的路由模式来注册控制器端点。此外,ASP.NET Core MVC 方法通常可以将应用程序的结构定义与您编写的实际代码分离。使用全局过滤器、模型绑定器和中间件,这种复杂性会导致开发人员引入微妙但令人沮丧的错误。 

最后,Minimal API 符合过去十年开发的编程范式,强调微服务并限制每个主机公开的功能。实际上,使用 Minimal API 构建的应用程序可以很容易地放入一个文件中,在一个易于阅读的地方表达功能。一些开发人员更喜欢这种明确性,而不是 ASP.NET Core MVC 的控制器、模型和视图的蔓延。

ASP.NET Core 的另一个关键部分是路由、中间件和依赖注入等横切功能的组件化。元素与任何编程模型的分离允许您在单个主机应用程序中混合和匹配 ASP.NET Core MVC、Razor Pages 和最小 API 功能。使用 Minimal API 并不意味着重新开始,而是反思现有的代码库和优化的机会。

您的第一个 Minimal API 应用程序

您可以使用“空”  ASP.NET Core 项目类型创建新的 ASP.NET Core 解决方案,以开始使用 Minimal API。创建解决方案后,您会注意到一个Program.cs包含以下代码的文件。

var builder = WebApplication. 创建生成器(参数);

var app = 建造者。建立();

应用程序。MapGet ( "/" , () = > "Hello World!" ) ;

应用程序。运行();

首先,让我们谈谈WebApplication.CreateBuilder文件顶部的方法调用。此方法注册 Web 应用程序主机的常用元素,例如配置、日志记录、主机服务、路由等。调用后,您可以通过注册服务、读取配置、日志记录等方式扩充您的主机。然后,一旦您的修改完成,您就可以Build托管您的应用程序了。

巩固应用程序的配置后,您可以通过注册中间件或添加 HTTP 端点来修改请求管道。请注意对 的调用MapGet,该调用既获取路径又RequestDelegate返回字符串值。最小 API 与所有基本 HTTP 方法相关,例如GETPOSTPUTPATCHDELETE和其他。

RequestDelegate是一个强大的抽象,允许 ASP.NET Core 有一个通用接口来执行所有 HTTP 请求。虽然是一个强大的抽象,但它是delegate一个带有参数的相对简单的定义HttpContext

// 根据一项或多项协议授权给 .NET 基金会。

// .NET 基金会根据 MIT 许可证向您许可此文件。

命名空间微软。网络核心。网址;

/// <总结>

/// 可以处理 HTTP 请求的函数。

/// </总结>

/// 请求的。

/// 表示请求处理完成的任务。

公共委托任务RequestDelegate (HttpContext 上下文);

虽然 aRequestDelegate有一个参数,正如您稍后将看到的,我们可以使用依赖注入将任意数量的参数传递给我们的端点定义。

在最后一行,您启动应用程序并通过调用Run方法侦听传入请求。在此示例中,Run正在阻塞并将暂停超过此点的进一步执行。其他方法(例如Start)将允许您之后执行更多代码。

从这里开始,让我们开始使用更多 Minimal API 编程模型功能。

模型绑定和返回类型

让我们添加一个POST利用最小 API 模型绑定和Results类的方法。

var builder = WebApplication. 创建生成器(参数);

var app = 建造者。建立();

应用程序。MapGet ( "/" , () = > "Hello World!" ) ;

应用程序。MapPost ( "/hugs" , (抱抱抱抱) = >

结果。好的(新的拥抱(拥抱。名称,“侧面拥抱” ))

) ;

应用程序。运行();

公共记录拥抱(字符串名称);

公共记录拥抱(字符串名称,字符串种类);

Minimal API 应用程序中的模型绑定不同于 ASP.NET Core MVC。ASP.NET Core MVC 使用该IModelBinder接口,允许常用的共享方法和堆叠模型绑定器来处理传入请求。使用 Minimal API,您只有一次机会将请求中的数据绑定到模型。默认情况下,它是将请求主体(假定为 JSON)直接反序列化为您的请求类型。

对我们新端点的请求将在 HTTP 请求正文中传递 JSON 负载。

发布 http: //localhost:5272/hugs

内容类型:application/json

{ “姓名” :“哈立德·阿布哈克迈” }

收到后,您可以处理请求并使用该类返回结果Result。该类Result是一种辅助类型,其中包含用于降低典型状态代码结果和有效负载的复杂性的方法。在编写独特的响应类型之前,您应该探索类中的方法列表Result。您可能会找到所需的结果类型,例如内容、文件和 JSON。

信息:如果您有兴趣使用 JetBrains Rider 的内置 HTTP 客户端和端点窗口测试端点,请查看我们的博客文章。

依赖注入和服务

足够复杂的应用程序总是对服务有一定的依赖性。最小 API 构建在 ASP.NET Core 的依赖项注入之上,这意味着您可以注入创建适当响应所需的任何已注册服务。让我们用 的依赖重构我们的服务HuggingService

使用静态系统。保安。密码学。随机数生成器;

var builder = WebApplication. 创建生成器(参数);

建设者。服务。AddSingleton ( new HuggingService ()) ;

var app = 建造者。建立();

应用程序。MapGet ( "/" , () = > "Hello World!" ) ;

应用程序。MapPost ( "/hugs" , ( Hug hug, HuggingService hugger ) = >

结果。好的(拥抱者。拥抱(拥抱))

) ;

应用程序。运行();

公共记录拥抱(字符串名称);

公共记录拥抱(字符串名称,字符串种类);

公开课HuggingService

{

私有只读字符串[] _hugKinds = {

“侧抱” 、“熊抱” 、

“礼貌的拥抱” 、“回头拥抱” 、

《自我拥抱》

} ;

私有字符串RandomKind = >

_hugKinds [ GetInt32 ( 0 , _hugKinds.Length ) ] ;

public Hugged Hug (抱抱)

= >新的(拥抱。名字,RandomKind );

}

如果您查看我们的方法调用的定义MapPost,您会注意到一个HuggingService作为委托参数的实例。您可以获得注入的服务,因为 ASP.NET Core 将IServiceProvider在耗尽路由值、查询字符串、HTTP 标头和 HTTP 正文中发生值解析的可能性后解析类型。有几个属性可以为 ASP.NET Core 提供有关参数绑定首选位置的提示,但在大多数情况下,这是不必要的。关于服务,您必须在应用程序的构建阶段注册任何依赖项,否则会出错。

运行与之前相同的 HTTP 请求,我们得到相同的结果,但现在有随机的拥抱。

用于请求和响应处理的过滤器

过滤器是 ASP.NET Core MVC 的中流砥柱,可以帮助减少通常重复的代码。最近在 .NET 7 中引入了 Minimal API 筛选器,旨在实现与其前身相同的功能。让我们修改我们的代码以添加时间戳过滤器以将当前日期和时间添加到Hugged响应对象中。

使用Microsoft.AspNetCore.Http.HttpResults;

使用静态系统。保安。密码学。随机数生成器;

var builder = WebApplication. 创建生成器(参数);

建设者。服务。AddSingleton ( new HuggingService ()) ;

var app = 建造者。建立();

应用程序。MapGet ( "/" , () = > "Hello World!" ) ;

异步ValueTask <对象? >时间戳(

EndpointFilterInvocationContext ctx,

EndpointFilterDelegate 接下来

)

{

var result = await next ( ctx ) ;

如果(结果是Ok < Hugged > { Value : { } } hugged )

拥抱。价值。时间戳= 日期时间。UtcNow ;

返回结果;

}

应用程序。MapPost ( "/hugs" , ( Hug hug, HuggingService hugger ) = >

结果。好的(拥抱者。拥抱(拥抱))

) 。添加端点过滤器(时间戳);

应用程序。运行();

公共记录拥抱(字符串名称);

公共记录拥抱(字符串名称,字符串种类)

{

公共DateTime 时间戳{得到; 设置;} = 日期时间。Unix时代;

}

公开课HuggingService

{

私有只读字符串[] _hugKinds = {

“侧抱” 、“熊抱” 、

“礼貌的拥抱” 、“回头拥抱” 、

《自我拥抱》

} ;

私有字符串RandomKind = >

_hugKinds [ GetInt32 ( 0 , _hugKinds.Length ) ] ;

public Hugged Hug (抱抱)

= >新的(拥抱。名字,RandomKind );

}

您可以将过滤器实现为本地函数、委托或接口IEndpointFilter。为简单起见,我选择使用本地函数并将其传递到AddEndpointFilter. 过滤器负责选择何时何地执行它正在过滤的端点。决定何时运行next参数为您提供了与中间件相同的灵活性,因为过滤器的行为非常相似。

在下一节中,我们将了解如何利用组来减少代码噪音。

路由组和常见行为

最近引入的另一个功能是 的概念RouteGroupBuilder,它允许您在逻辑上将路由分组到基本路径前缀。进一步修改我们的应用程序,让我们创建一个新的“拥抱” 路由组,它现在将成为所有基于拥抱的端点的基础。该组允许我们在一个方便的位置添加组元数据、过滤器等。

使用微软。网络核心。网址。HTTP结果;

使用静态系统。保安。密码学。随机数生成器;

var builder = WebApplication. 创建生成器(参数);

建设者。服务。AddSingleton ( new HuggingService ()) ;

var app = 建造者。建立();

应用程序。MapGet ( "/" , () = > "Hello World!" ) ;

异步 ValueTask <对象?>时间戳(

EndpointFilterInvocationContext ctx,

EndpointFilterDelegate 接下来

)

{

var result = await next ( ctx ) ;

如果(结果是 Ok < Hugged > { Value : { } } hugged )

拥抱。价值。时间戳= 日期时间。UtcNow ;

返回结果;

}

变种拥抱=应用程序。地图组(“拥抱” )

. 添加端点过滤器(时间戳);

拥抱。MapPost ( "" , (抱抱抱抱, HuggingService hugger ) = >

结果。好的(拥抱者。拥抱(拥抱))

) ;

拥抱。MapGet ( "" , ( HuggingService hugger ) = >

结果。好的(拥抱者。拥抱(新拥抱(“测试” )))

) ;

应用程序。运行();

公共记录拥抱(字符串名称);

公共记录拥抱(字符串名称,字符串种类)

{

公共 DateTime 时间戳{得到; 放; } = 日期时间。Unix时代;

}

公开课HuggingService

{

私有只读字符串[] _hugKinds = {

“侧抱” 、“熊抱” 、

“礼貌的拥抱” 、“回头拥抱” 、

《自我拥抱》

} ;

私有字符串 RandomKind = >

_hugKinds [ GetInt32 ( 0 , _hugKinds.Length ) ] ;

public Hugged Hug (抱抱)

= >新的(拥抱。名字,RandomKind );

}

现在您可以在一个位置增强我们的“拥抱” 组,并让这些更改影响组内的所有路线。当您的 API 变得越来越复杂时,这是一个极好的优化。

最小的 API 缺点

该技术非常强大,能够处理 ASP.NET Core 开发人员想要的大部分(如果不是全部的话)工作负载。Minimal API 方法的一些缺点更多地与仍在增长的社区有关。ASP.NET Core MVC 拥有丰富的开源解决方案历史,可增强 MVC 管道和许多解释常见陷阱的博客文章。使用 Minimal API,用户社区仍处于起步阶段。因此,您可能会在基础架构代码的细节上花费比您希望的更多的时间。

如果您从最小的 API 开始,您将做出许多您可能不需要使用 ASP.NET Core MVC 的决定。有选择的自由,但有时感觉像是一种负担。您将模型和服务放在哪里?你如何重构过滤器?你应该在哪里定义路线?

令人眼花缭乱的选择可能意味着您会看到许多 Minimal API 应用程序看起来彼此截然不同,而 MVC 是一种标准且可识别的方法。这些当然不是采用 Minimal API 的障碍,但您应该注意它们。

结论和最后的想法

ASP.NET Core 生态系统中最小 API 的故事仍在撰写中,您可能会在 .NET 的每次迭代中看到新功能。但是,如果您正在考虑从 MVC 方法切换,这篇文章介绍了 Minimal API 的公共部分。此外,您还了解了一些概念,例如应用程序设置、端点定义、模型绑定、依赖项注入、过滤器和路由组。我还指出了乐高式框架的新兴社区支持和决策超载的潜在缺点。 

总而言之,最终的选择是你的,我希望这篇文章对你有所帮助。