【ASP.NET Core】REST与RESTful详解,从理论到实现

发布于:2025-07-05 ⋅ 阅读:(19) ⋅ 点赞:(0)


前言

提起WebAPI的开发,REST风格的API是不得不提及的一个话题。REST风格的WebAPI也是目前最流行的一种WebAPI风格,除了REST风格外,还有通过WebService或者WCF开发的基于SOAP协议的WebAPI,不过这些都在时代的潮流中或被沉淀,或又通过新的面貌重新进入我们的视野。

REST是一种基于 HTTP 协议的软件架构风格,而RESTful则是基于这种风格的实践。本文将详尽的介绍REST,并且通过ASP.NET Core开发RESTful WebAPI。

我曾经也写过通过WCF来开发RESTful WebAPI,有需要的朋友可以去看这这篇文章
链接: 【WCF】基于WCF在WinForms搭建RESTful服务指南


一、REST与RESTful

1.1 背景

REST(Representational State Transfer),这是一种基于HTTP协议的软件架构风格。由计算机科学家Roy Fielding在2000年的博士论文中提出(Roy博士也是 HTTP 协议的主要设计者之一)。

REST的理念的提出也是源自Web本身的设计原则(如URI、HTTP方法),目的是在让Web架构更符合其初衷,通过HTTP的语义来使用HTTP协议。

REST的提出是建立在对早期WebAPI服务协议的一种精简,开发过WebService的朋友应该能明细感觉到,这种基于SOAP协议,利用XML报文的形式传递数据的方式用起来很 “重” 。而且因为API风格大多使用RPC这种面向过程的方式,仅仅把HTTP当成传输数据的通道,并不关心HTTP谓词。而是通过方法实现HTTP语义,比如/Project/GetAll、/Project/GetByld?id=7、/Project/Update、/Project/DeleteByld/7。

RPC这种通过相应的业务驱动的方式,比较自然,但是未充分利用HTTP语义,容易导致API语义不清晰。并且随着业务的更改与扩张,大量的代码需要修改,版本管理复杂扩展性较差。

聊完RPC这种"面向过程的风格”,接下来我们来对比了解下遵循REST风格设计的Web API ——RESTful。

1.2 REST风格

REST是一种基于HTTP协议的软件架构风格,它使用URL表示资源,使用HTTP方法(GET、POST、PUT、DELETE)表示对资源的操作。本质上是一组设计原则。

  • 按照HTTP的语义来使用HTTP协议
  • 将资源作为核心抽象,通过不同HTTP语义实现对资源的操作
  • 无状态通信,每个请求独立
  • 支持多种数据格式

1.3 RESTful

RESTful是遵循REST架构风格的API实现。一个API被称为RESTful,意味着它严格遵守REST原则。我们可以这样简单的总结一下RESTful

  • 使用名词而非动词命名URL(如/users而非/getAllUsers),URL用于资源的定位
  • 服务器端要通过状态码来反映资源获取的结果(如200成功、201新增成功、403没有权限,404 未找到)

1.4 使用REST的优缺点

按照HTTP的语义来使用HTTP协议的这种约定大于配置的方式的,确实能节约不少开发上的工作,但是也变相要求开发人员对REST原则更了解、并且有更多的设计能力。以下分别总结几点优缺点。

  • REST优点
    • 通过URL对资源定位,语义更清晰通过HTTP谓词表示不同的操作,接口自描述。
    • 可以对GET、PUT、DELETE请求进行重试(分布式架构里的中间网关服务器,自动重发)
    • 可以用GET请求做缓存(依据幂等性质,安全的设置缓存)
    • 通过HTTP状态码反映服务器端的处理结果统一错误处理机制
    • 网关等可以分析请求处理结果(网关服务器解析API响应,通过请求结果判断系统是否健壮)
  • REST缺点
    • 真实系统中的资源非常复杂,难以优雅表达。很难清晰地进行资源的划分,对技术人员的业务和技术水平要求高。
    • 安全性依赖外部机制,认证授权需依赖JWT等外部方案,增加复杂性。
    • 状态管理不足,客户端维护更多状态。

1.5 小结

REST总体而言是一种偏学术化的概念,但实际工作生产中,面对复杂的情况,切记不要抱着学术化的概念固步自封。灵活的选择和裁剪REST(比方说URL定位结合QueryString查询数据),更有利于我们在实际工作中的落地REST。

二、ASP.NET Core中的RESTful落地指南

2.1项目构建

  1. 打开VS2022,选取ASP.NET Core Web API,其余配置项可默认。
    在这里插入图片描述
  2. 建议开启对OpenAPI的支持(默认项),这样会引入Swagger,方便直观的了解开发的Web API。
    在这里插入图片描述
  3. .NET9需要手动引入Swagger服务
dotnet add package Swashbuckle.AspNetCore --version 9.0.1

program.cs引入服务

#region 注册Swagger服务
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
#endregion

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();

    #region 启用Swagger 
    app.UseSwagger();
    app.UseSwaggerUI();
    #endregion
}

调试的时候观察控制台启动的地址,在浏览器里复制后后面加上/swagger/index.html就可以像以前版本的.net一样访问swagger了。

  1. Controller文件夹引入名为StudentRESTfulController的API Controller
    在这里插入图片描述

2.2 RESTful服务搭建

2.2.1 资源路由定义

[Route("api/[controller]")]
[ApiController]
public class StudentRESTfulController : ControllerBase

新建的控制器,默认会用Route和ApiController特性修饰,并且Route特性里包括(“api/[controller]”)的指定。也就是说该控制器下面的Action方法都是通过api/StudentRESTful定位,并不会关注每个Action的名称。
如果想直接根据Action的名称请求,我们可以通过修改Route参数。这样的话就有点偏向RPC的风格了,失去了根据HTTP语义定位资源的

[Route("api/[controller]/[action]")]
[ApiController]
public class StudentRESTfulController : ControllerBase

2.2.2 Action的HTTP语义定义

在ASP.NET Core中,通过对Action方法前添加诸如【HttpGet】,【HttpPost】,【HttpPut】,【HttpPatch】,【HttpDelete】。特性内部也可以定义路由参数,比如下面示例里的"{id}"是路由模板中的占位符,表示该方法需要接收一个名为id的参数,且该参数会从URL中提取


[HttpGet]
public ActionResult<List<Student>> GetStudents()

[HttpGet("{id}")]
public ActionResult<Student> GetStudent(int id)

[HttpPut("{id}")]
public IActionResult UpdateStudent(int id, [FromBody] Student updatedStudent)

HTTP方法

HTTP 操作类型 幂等性 典型场景
GET 读取 获取单个资源(/api/studentRESTful/1)或资源列表(/api/studentRESTful)
POST 创建 创建新资源
PUT 全量更新 通过提供全部字段,完整替换资源
PATCH 部分更新 增量更新资源,修改部分属性
DELETE 删除 删除资源

2.2.3 状态码的返回

标注的REST风格是完全按照HTTP的语义来使用HTTP协议,也就是说返回结果里也要按照HTTP协议返回相应的状态码。

ASP.NET Core提供了ActionResult,像常见的调用Return ok()返回的ObjectResult对象都是继承自ActionResult,让我们开发能够更加方便的返回状态码。

成功状态码

状态码 对应方法 含义 典型场景
200 OK return Ok() 请求成功,返回数据 GET 请求返回资源列表或单个资源
201 Created return Created() 资源创建成功 POST 请求创建新资源
202 Accepted return Accepted() 请求已接受但未完成处理 异步操作(如后台任务处理),需在响应头中包含 Location 指向状态查询 URI
204 No Content return NoContent() 请求成功但无返回内容 PUT/PATCH/DELETE 操作成功后(无需返回数据)

客户端错误状态码

状态码 对应方法 含义 典型场景
400 Bad Request return BadRequest() 请求参数无效或格式错误 模型验证失败或请求体格式错误
401 Unauthorized Unauthorized 未认证 未登录
403 Forbidden return Forbid()/return StatusCode(403) 已认证但权限不足 比如JWT Token过期失效
404 Not Found return NotFound() 资源不存在 请求的 ID 对应的资源不存在(如 /api/users/999)
405 Method Not Allowed 一般框架返回 /return StatusCode(405 ) 指定 URI 不支持该 HTTP 方法 对 /api/users 用 DELETE(通常 DELETE 应针对单个资源)
409 Conflict return Conflict() 请求冲突(如唯一约束冲突) 创建或更新资源时版本冲突(乐观锁)
415Unsupported Media Type 一般框架返回/return StatusCode(415 ) 不支持的请求格式(如 Content-Type 错误) 发送 application/xml 但 API 仅支持 application/json

服务器错误状态码

状态码 对应方法 含义 典型场景
500 Internal Server Error return StatusCode(500) 服务器内部错误 服务器内部执行出现重大错误
501 Not Implemented return StatusCode(501) 方法未实现 API 端点尚未实现(如占位方法)
503 Service Unavailable return StatusCode(503) 服务不可用(临时过载或维护) 服务器暂时无法处理请求(如限流、数据库维护)

2.2.4 返回状态码的裁剪

HTTP的状态码并不适合用来表示业务层面的错误码,它是一个用来表示技术层面信息的状态码。工作中我们也能经常发现仅仅是凭借状态码,很难详细的将一个错误完整的描述清楚。

就比如404 Not Found这个状态码。请求一个不存在的URL和对于一个已存在的URL查询出一个不存在的结果都会返回404 Not Found。这其实就有一个业务和技术错误耦合在一起,导致仅仅凭借状态码难以描述清楚。

一种建议是业务方面的错误使用200返回,但是在报文内部定义指定的错误码和错误信息进行补充。

2.3 RESTful服务示例

2.3.1 HttpGet

HttpGet特性的内部可以指定参数,比如下面示例的"{id}" 是路由模板中的占位,表示该方法需要接收一个名为id的参数,且该参数会从URL中提取。

[HttpGet]
public ActionResult<List<Student>> GetStudents()
{
    return Ok(_students);
}
[HttpGet("{id}")]
public ActionResult<Student> GetStudent(int id)
{
    var student = _students.FirstOrDefault(e => e.Id == id);

    if (student == null)
        return NotFound("");

    return Ok(student);
}

2.3.2 HttpPost

[HttpPost]
 public ActionResult<Student> CreateStudent(Student newStudent)
 {
     // 确保ID是唯一的
     if (_students.Any(s => s.Id == newStudent.Id))
         return BadRequest("学生ID已存在");

     _students.Add(newStudent);

     return CreatedAtAction(nameof(GetStudent), new { id = newStudent.Id }, newStudent);
 }

2.3.3 HttpPut

HttpPut是全量更新,需要传入完整对象。这里用[FromBody]的特性修饰参数,表明该数据从请求body中获取。

[HttpPut("{id}")]
public IActionResult UpdateStudent(int id, [FromBody] Student updatedStudent)
{
    if (id != updatedStudent.Id)
        return BadRequest();

    var student = _students.FirstOrDefault(e => e.Id == id);

    if (student == null)
        return NotFound();

    student.Name = updatedStudent.Name;

    return NoContent();

}

2.3.4 HttpPatch

增量更新,不一定是幂等。比方说累加器。

[HttpPatch("{id}")]
public IActionResult PartialUpdateStudent(int id, string name)
{
    var student = _students.FirstOrDefault(e => e.Id == id);

    if (student == null)
        return NotFound("");
    student.Name = name;
    return NoContent();
}

2.3.5 HttpDelete

[HttpDelete("{id}")]
public IActionResult DeleteStudent(int id)
{
    var student = _students.FirstOrDefault(e => e.Id == id);

    if (student == null)
        return NotFound();

    _students.Remove(student);

    return NoContent();
}

总结

本文介绍了REST架构风格及RESTful API的概念,阐述了其基于HTTP语义的设计原则,并结合ASP.NET Core演示了RESTful API的具体实现,包括路由设计、HTTP方法映射及状态码规范等。