掌握 .NET Web API:构建强大 API 的最佳实践

作者:微信公众号:【架构师老卢】
5-28 16:5
187

目录:

  • 对 API 进行版本控制
  • 使用正确的 HTTP 方法
  • 保护 API
  • 缓存响应
  • 用户输入验证
  • 异常处理
  • 记录 API

先决条件:

介绍

在开发 API 时,程序员除了实现基本功能外,还需要考虑各种因素。他们必须解决版本控制、安全性、性能、错误处理和文档等问题。通过结合这些领域的最佳实践,程序员可以确保其 API 的可靠性、可扩展性和可用性。

本文旨在全面概述程序员在创建 API 时应遵循的最佳实践。无论是使用 .NET 6 Web API 还是任何其他框架,这些做法对于提供设计良好且可靠的 API 都是必不可少的。

对 API 进行版本控制

什么是 API 版本控制?

API 版本控制是一种用于管理 API 中的更改,同时保持与现有客户端的向后兼容性。它允许开发人员引入新功能、修复错误或对 API 进行其他修改,而不会影响现有集成的功能。

API 是软件。软件的每个部分都需要在某个时候进行更新。在 API 上创建一些更新时,需要确保这些更改不会影响 API 使用者(用户)。为确保这一点,您需要引入 API 版本控制

为什么需要 API 版本控制?

您需要 API 版本控制,因为它可以确保兼容性,支持灵活地采用更改,提供受控和记录的修改,支持多个版本共存,并授予对 API 生命周期的精细控制,从而实现成功和高效的软件开发。

如果您的目标是开发强大且可扩展的 Web 应用程序,请考虑从 Toptal 聘请 ASP.NET 开发人员。凭借他们在创建高性能解决方案方面的深厚专业知识,Toptal 的 ASP.NET 专业人员有能力提供满足复杂业务需求的尖端应用程序,确保顶级公司和初创公司有效地实现其技术目标。

最重要的 3 个原因:

  1. 向后兼容性:它确保客户端可以继续使用旧版本,而新客户端可以利用更新的功能。
  2. API 演进:随着 API 的演进,版本控制允许您引入新功能、弃用过时的功能并进行改进,而不会中断现有客户端。
  3. 客户端灵活性:不同的客户端可能有不同的要求,或者可能需要特定版本的 API 中提供的特定功能。

ASP.NET Web API 版本控制

.NET 6 中有多种 API 版本控制方法。以下是两种常用的方法:

  • 基于 URL 的版本控制:版本号包含在 URL:
    https://api.example.com/v1/products
    https://api.example.com/v2/products 中
  • 基于标头的版本控制:版本号在 HTTP 请求的自定义标头中指定。例如:
GET /products HTTP/1.1  
Host: api.example.com  
Accept: application/json  
X-API-Version: 2

让我们以基于 URL 为例。首先,您需要安装一个软件包:

Microsoft.AspNetCore.Mvc.Versioning

然后,您需要在 Program.cs C# class:builder 中配置 API 版本控制。Services.AddApiVersioning(选项 =>

//Program.cs C# class:

builder.Services.AddApiVersioning(options =>  
 {  
      options.ReportApiVersions = true;  
      options.DefaultApiVersion = new ApiVersion(1, 0);  
      options.AssumeDefaultVersionWhenUnspecified = true;  
      options.ApiVersionReader = new UrlSegmentApiVersionReader();  
});

- ReportApiVersions -> API 响应将包含有关可用版本和用于当前请求
的版本的信息 - DefaultApiVersion -> 此选项设置默认 API 版本。
- AssumeDefaultVersionWhenUnspecified -> 设置为 true 时,如果客户端在请求中不包含版本,则 API 将使用 DefaultApiVersion 中指定的默认版本。
- ApiVersionReader -> 它决定如何从请求中提取 API 版本。在这种情况下,将从 URL 段读取 API 版本。

在 ProductsController 中,使用 MapToApiVersion 属性修饰操作方法,以指定每个方法的版本。

客户端可以通过将版本号附加到 URL 来访问不同版本的 API,例如 /v1/products/v2/products

[ApiController]
 [Route("v{version:apiVersion}/products")]
 public class ProductsController : ControllerBase
 {
// GET v1/products
 [HttpGet]
 [MapToApiVersion("1.0")]
 public IActionResult GetV1()
 {
      	// Implementation for version 1
 }
// GET v2/products
 [HttpGet]
 [MapToApiVersion("2.0")]
 public IActionResult GetV2()
 {
    	// Implementation for version 2
 }
}

根据我的经验,使用基于属性的路由来简化配置和组织,坚持一致的版本控制策略,彻底记录每个版本以让用户了解情况,并优先考虑向后兼容性以避免对现有用户造成干扰。

相信我,遵循这些提示将使您在 .NET 6 中的 API 开发变得轻而易举!:D

我建议您阅读Microsoft关于RESTful API版本控制的官方文章

使用正确的 HTTP 方法

什么是 HTTP 方法?

HTTP 方法(也称为 HTTP 谓词)是可以使用超文本传输协议 (HTTP) 对资源执行的标准化操作或操作。这些方法定义要对客户端请求的特定资源执行的操作类型。

最常用的方法是:

  • GET:检索资源的表示形式。
  • POST:创建新资源。
  • PUT:更新现有资源或创建新资源(如果不存在)。
  • PATCH:部分更新现有资源。
  • DELETE:删除资源。

为什么使用正确的 HTTP 方法很重要?

使用正确的 HTTP 方法很重要,因为它可以确保遵守 REST 架构风格的原则,改进 API 设计和可维护性,通过实施适当的访问控制来增强安全性,促进可伸缩性和缓存机制,并实现与 Web 上各种客户端和服务器的互操作性和兼容性。

从我的角度来看,这 2 个是最重要的:

  1. RESTful 原则:正确使用 HTTP 方法符合具象状态传输 (REST) 的原则。RESTful API 利用这些方法的语义为客户端创建统一且可预测的接口。
  2. **语义清晰:**每个 HTTP 方法都具有特定的含义,使您的 API 端点更加直观和不言自明。开发人员可以根据所使用的 HTTP 方法轻松了解端点的用途和功能。

何时使用每种 HTTP 方法?

  • GET:使用 GET 从资源中检索数据。它应该不会对服务器产生任何副作用。
  • POST:使用 POST 创建新资源。请求有效负载通常包含新资源的数据,服务器为其分配唯一标识符。
  • PUT:使用 PUT 更新现有资源或创建新资源(如果不存在)。请求负载包含要更新的资源的完整表示形式。
  • PATCH:使用 PATCH 对现有资源执行部分更新。请求有效负载仅包括需要应用于资源的更改。
  • DELETE:使用 DELETE 删除由其唯一标识符标识的资源。

故事时间:

让我分享一下我在开发 .NET API 时使用 HTTP 方法的有趣体验。这一切都始于我错误地使用“GET”方法而不是“POST”来提交用户数据。这导致了滑稽的混淆和信息混淆。

好像这还不够,当我无意中设置“PUT”方法而不是“DELETE”来删除资源时,我遇到了另一个错误,导致数据持久且顽固。

经验教训:始终使用正确的 HTTP 方法,否则您会发现自己陷入了一个充满搞笑 API 事故的世界!

.NET 6 示例

[ApiController]
[Route("products")]
public class ProductsController : ControllerBase
{
     // GET /products
    [HttpGet]
    public IActionResult Get()
    {
         // Implementation to retrieve all products
    }
    // GET /products/{id}
    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        // Implementation to retrieve a specific product by ID
    }
    // POST /products
    [HttpPost]
    public IActionResult Create(ProductDto product)
    {
        // Implementation to create a new product using the provided data
    }
    // PUT /products/{id}
    [HttpPut("{id}")]
    public IActionResult Update(int id, ProductDto product)
    {
        // Implementation to update an existing product identified by the ID
    }
    // PATCH /products/{id}
    [HttpPatch("{id}")]
    public IActionResult PartialUpdate(int id, JsonPatchDocument<ProductDto> patchDocument)
    {
        // Implementation to apply partial updates to an existing product
    }
    // DELETE /products/{id}
    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        // Implementation to delete a specific product by ID
    }
}

请记住,根据要对资源执行的操作选择适当的 HTTP 方法,并遵循每种方法的准则和最佳做法。

保护 API

保护 API 对于保护敏感数据、防止未经授权的访问和确保应用程序的完整性至关重要。它是指实施措施和实践的过程,以保护应用程序编程接口 (API) 免受未经授权的访问、数据泄露和其他安全风险。它涉及实施身份验证、授权、加密和其他安全机制,以确保 API 及其处理的数据的机密性、完整性和可用性。

开发 .NET 6 Web API 时,API 安全性需要考虑几个重要方面:

认证:

实施身份验证对于防止未经授权的访问和保护敏感资源至关重要。在 .NET 6 Web API 中,可以使用各种身份验证机制,例如 JSON Web 令牌 (JWT)、OAuth 或自定义身份验证方案。

.NET 6 中的 JWT 持有者令牌示例

// Program.cs  
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)  
            .AddJwtBearer(options =>  
            {  
                options.TokenValidationParameters = new TokenValidationParameters  
                {  
                    ValidateIssuer = true,  
                    ValidateAudience = true,  
                    ValidateIssuerSigningKey = true,  
                    ValidIssuer = "your_issuer",  
                    ValidAudience = "your_audience",  
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_security_key"))  
                };  
            });  
}

授权

实施授权可确保用户对特定 API 端点或资源具有适当的访问权限。在 .NET 6 Web API 中,可以使用授权属性(如 [Authorize])或基于策略的授权来控制访问。

// ProductsController.cs
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [Authorize(Roles = "admin")]
    public IActionResult GetProducts()
    {
        // Implementation for retrieving products
    }
    [HttpPost]
    [Authorize]
    public IActionResult CreateProduct(ProductDto product)
    {
        // Implementation for creating a new product
    }
}

这是用于身份验证和授权的 YouTube 视频的绝佳播放列表。

速率限制

速率限制有助于防止滥用、流量过大和拒绝服务 (DoS) 攻击。它确保了 API 资源的公平使用,并防止其不堪重负。在 API 中实施速率限制技术,例如为每个客户端、IP 地址或全局设置最大请求限制。

在 .NET 6 中,可以使用 AspNetCoreRateLimit 库实现此目的。

在Program.cs文件中添加依赖项:

services.AddMemoryCache();
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

并设置中间件:

app.UseIpRateLimiting();

您可以在 appsettings.json 文件中配置的速率限制规则:

{  
  "IpRateLimiting": {  
    "EnableEndpointRateLimiting": true,  
    "StackBlockedRequests": false,  
    "RealIpHeader": "X-Forwarded-For",  
    "ClientIdHeader": "X-ClientId",  
    "HttpStatusCode": 429,  
    "GeneralRules": [  
      {  
        "Endpoint": "*",  
        "Period": "1s",  
        "Limit": 5  
      }  
    ]  
  }  
}

缓存响应

什么是响应缓存?

响应缓存涉及存储 API 请求的响应,并在再次发出相同请求时直接从缓存中提供该响应。通过缓存响应,您可以减少多次重新处理同一请求的需要,从而缩短响应时间并提高可伸缩性。

为什么要使用响应缓存?

您应该使用响应缓存来最大限度地提高 API 的性能和可伸缩性,通过缩短响应时间来增强用户体验,提高整体响应能力,减轻服务器负载,优化网络带宽消耗并确保可用性,最终实现高效且高性能的 API 系统。

我将根据我的经验向您解释 3 个最有力的原因:

  1. 改进的性能:无需重复执行相同的昂贵计算或数据库查询,而是从内存或缓存存储中快速提供缓存响应。缓存响应可缩短响应时间并提高 API 的整体性能。
  2. 节省带宽:后续请求可以获取缓存的响应,从而节省带宽,而不是每次都传输整个响应。缓存响应可减少通过网络发送的数据量。
  3. 减少服务器负载:通过提供缓存响应,服务器处理的请求更少,从而减少服务器负载并提高可伸缩性。这在处理高流量或资源密集型 API 时尤其有用。

.NET 实现

若要在 .NET 6 Web API 中启用响应缓存,可以使用内置的 ResponseCaching 中间件和 [ResponseCache] 属性。

添加依赖项:

services.AddResponseCaching()
//Add Middleware:

app.UseResponseCaching();

在控制器中使用:

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [ResponseCache(Duration = 60, VaryByQueryKeys = new[] { "category" })]
    public IActionResult GetProducts(string category)
    {
        // Implementation to retrieve and return products
    }
}

在此示例中,ResponseCache 属性应用于 ProductsController 中的 GetProducts 操作。Duration 属性指定缓存持续时间(以秒为单位)(在本例中为 60 秒),而 VaryByQueryKeys 属性指示应针对“category”查询参数的不同值单独缓存响应。

Microsoft Learn 在响应缓存方面有很好的指南

用户输入验证

什么是用户输入验证?

用户输入验证是在 API 中接受和处理用户输入的数据之前检查和验证数据的过程。它涉及验证输入的格式、类型和完整性,以防止常见的安全漏洞。您可以保护应用程序免受 SQL 注入、XSS(跨站点脚本)或命令注入攻击等漏洞的侵害。

为什么要使用用户输入验证?

您可能应该使用用户输入验证来确保数据完整性和准确性,通过防止潜在漏洞来增强应用程序的安全性,通过提供及时反馈和防止错误来改善用户体验,并最终创建可靠、安全和用户友好的应用程序。

您应该了解的 3 大理由(第 3 个是我的最爱):

  1. 数据完整性:通过验证用户输入,可以强制执行业务规则、数据约束和特定格式要求。这有助于保持数据的完整性和一致性。
  2. 安全漏洞防护:用户输入验证通过检测和拒绝恶意或格式错误的输入,帮助保护 API 免受常见安全风险的影响。它确保您的 API 处理的数据值得信赖且可以安全处理。
  3. 错误处理:通过验证用户输入,您可以在检测到无效输入时提供有意义的错误消息和响应。这有助于用户理解和纠正他们的输入,从而带来更好的用户体验。

.NET Web API 示例

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    [HttpPost]
    public IActionResult CreateProduct([FromBody] ProductDto product)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        // Implementation to create a new product
    }
}
// ProductDto.cs
public class ProductDto
{
    [Required]
    public string Name { get; set; }
    [Range(0, 100)]
    public int Quantity { get; set; }
    // Other properties and validation attributes
}

异常处理

什么是异常处理?

由于各种原因,例如编程错误、无效输入或外部依赖关系,可能会出现异常。通过适当的异常处理,您可以处理错误、从故障中恢复并向客户端提供响应。异常处理是管理和响应 API 代码执行期间发生的意外情况的过程。

为什么要使用异常处理?

首先,异常处理允许适当的错误管理和优雅地处理程序执行过程中可能出现的意外情况。通过捕获和处理异常,可以防止应用程序崩溃,并向用户提供有意义的错误消息或记录它们以进行故障排除。

其次,异常处理有助于将正常程序流与错误处理逻辑分离,使代码更有条理、更可读、更易于维护。它还通过将错误处理逻辑封装在可重用的异常处理块中来促进代码重用。

总结为3个原因:

  1. 防止未处理的异常:可以在异常向上传播到调用堆栈并导致未处理的异常之前捕获和处理异常,从而导致不可预知的行为或崩溃。
  2. **“优雅”错误处理:**它有助于向客户提供有意义的错误响应,从而改善用户体验。
  3. 调试和日志记录:正确记录的异常提供了对错误原因的宝贵见解,并有助于进行故障排除。

如何在 .NET 6 中实现全局异常处理中间件?

在此示例中,全局异常处理中间件被添加到Program.cs文件中。它将未处理的异常重定向到 ErrorController 的 HandleError 操作,您可以在其中记录错误并将标准化的错误响应返回给客户端。

中间件

[ApiController]
[Route("/error")]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorController : ControllerBase
{
    [Route("{statusCode}")]
    public IActionResult HandleError(int statusCode)
    {
        // Log the error and provide a meaningful error response
        var errorResponse = new ErrorResponse
        {
            StatusCode = statusCode,
            Message = "An error occurred."
        };
        return StatusCode(statusCode, errorResponse);
    }
    [Route("")]
    public IActionResult HandleError()
    {
        // Log the error and provide a meaningful error response
        var errorResponse = new ErrorResponse
        {
            StatusCode = 500,
            Message = "An error occurred."
        };
        return StatusCode(500, errorResponse);
    }
}

在 Microsoft 的 Learn 平台上查找有关异常和最佳实践的更多异常处理最佳实践。

记录 API

“Documenting API”是什么意思?

“记录 API”是指创建全面且信息丰富的文档的过程,这些文档描述了应用程序编程接口 (API) 的功能、用法和规范。当您创建 API 时,有人将使用该 API。使用 API 的人应该对将要使用的端点、请求和响应有直观的了解。良好的文档使开发人员能够轻松使用您的 API,减少集成时间,并提高开发人员的满意度。

为什么要记录 API?

您应该记录 API 的主要原因之一是为开发人员提供明确的说明和指南。

我还可以给你两个理由:

  1. 第三方集成:当您希望其他开发人员或组织将其应用程序或服务与您的 API 集成时,文档至关重要。它是了解集成要求、数据格式和身份验证机制的基础。
  2. 改进的可用性:有据可查的 API 提供了有关如何发出请求、处理响应和利用可用功能的清晰说明和示例。这样可以更有效地使用 API,并减少错误或误解的机会。

使用 Swagger 在 .NET 6 中记录 API

有一些工具和库可以帮助你记录你的 API。归根结底,你可以自己做(如果你没有很多时间,我不建议这样做)。

Swagger 是一个广泛用于 ASP.NET Web API 项目的库。VIdeces 几乎存在于您遇到的每个 API 项目中。了解更多: https://swagger.io/solutions/api-development/

配置 Swagger 非常简单。

将 Swagger 添加到依赖项注入中:

builder.Services.AddSwaggerGen(c =>{  
  c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });  
});  
Add Middleware for Swagger and for Swagger UI to display and interact with generated documentation:   
app.UseSwagger();  
app.UseSwaggerUI(c =>  
{  
  c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");  
});

API 文档变得简单

嫁给 Swagger 的样子?不用担心。Treblle 的实时自动生成 API 的功能可以选择打开它 Swagger,因为 Treblle 是 Open APISpec 计划的一部分

自动生成的 API 文档 — Treblle

使用 .NET 6 开发 Web API 项目的最佳实践。通过遵循这些做法,您可以确保安全、可扩展且可维护的 API,以满足用户和客户端的需求。虽然这些实践是专门为 .NET 6 量身定制的,但其中许多实践也可以应用于其他框架,从而为任何 API 开发工作提供宝贵的知识。

相关留言评论
昵称:
邮箱:
阅读排行