前言
REST(Representational State Transfer) 是一种基于 HTTP 协议的软件架构风格,用于设计网络应用的接口(API)。它的核心思想是通过资源(Resource)的表(Representation)来实现客户端与服务器之间的状态交互。
一、REST 遵循核心原则
- 资源导向:所有事物抽象为资源(如用户、订单),通过唯一标识符(URI/URL)定位。
- 统一接口:使用标准的 HTTP 方法(GET、POST、PUT、DELETE 等)操作资源。
- 无状态(Stateless):服务器不保存客户端状态,每次请求必须包含所有必要信息。
- 可缓存:响应需明确标注是否可缓存,以提高性能。
- 分层系统:客户端无需关心服务器架构(如负载均衡、代理)。
- 按需代码(可选):服务器可返回可执行代码(如 JavaScript)扩展客户端功能。
二、REST 的优点和缺点
1.优点
- 简单易用
基于 HTTP 标准方法,学习成本低。
使用 URL 和 JSON/XML 等通用格式,开发调试方便。 - 可扩展性强
无状态设计允许服务器轻松横向扩展。
客户端与服务器解耦,支持独立迭代。 - 高性能与缓存
利用 HTTP 缓存机制(如 Cache-Control),减少重复请求。 - 跨平台兼容
支持多种数据格式(JSON、XML、HTML等),适配不同客户端(Web、移动端、IoT设备)。 - 松耦合
客户端只需关注资源 URI 和接口规范,无需了解服务端实现细节。
2.缺点
- 过度请求(Over-fetching/Under-fetching)
返回固定数据结构,可能导致客户端获取冗余数据或需要多次请求(如嵌套资源)。 - 无状态的双刃剑
每次请求需携带完整信息(如身份验证 Token),增加网络开销。
不适合需保持会话状态的应用(如实时游戏)。 - 缺乏严格规范
REST 是架构风格而非标准,不同开发者对“RESTful”的理解可能不同(如 URI 设计、HTTP 方法使用)。 - 复杂操作支持有限
对非 CRUD(增删改查)操作(如批量更新、事务处理)需设计绕行方案(如自定义端点)。 - 版本管理问题
API 升级时需谨慎处理版本兼容性(如通过 URL 路径 v1/resource 或请求头)。
三、常见参数传递方式
1)路径参数(Path Parameters)
-
通过 URL 路径中的占位符传递参数,常用于标识唯一资源。
[HttpGet("users/{id}")] public IActionResult GetUserById(int id) { // 直接通过方法参数接收路径参数 var user = _userService.GetUser(id); return Ok(user); }
路由模板:[HttpGet(“users/{id}”)] 定义占位符 {id},参数名需与方法参数名一致,支持类型自动转换(如 int、Guid)。
-
高级场景(自定义路由约束(如正则表达式))
[HttpGet("users/{id:guid}")] public IActionResult GetUserById(Guid id) { ... } [HttpGet("posts/{slug:regex(^[a-z0-9-]+$)}")] public IActionResult GetPostBySlug(string slug) { ... }
2)查询参数(Query Parameters)
-
通过 URL 末尾的 ?key=value 形式传递,适用于过滤、分页、排序等场景。
[HttpGet("users")] public IActionResult SearchUsers([FromQuery] string name, [FromQuery] int? age) { // 使用 [FromQuery] 显式绑定查询参数 var users = _userService.Search(name, age); return Ok(users); }
[HttpGet("users")] public IActionResult SearchUsers( [FromQuery] string name, [FromQuery] int page = 1, [FromQuery] int pageSize = 10) { var users = _userService.Search(name, page, pageSize); return Ok(users); }
-
调用示例:
GET /api/users?name=John&age=30 GET /api/users?name=Alice&page=2&pageSize=20
-
对象绑定(将多个查询参数封装到对象)
public class UserSearchParams { public string Name { get; set; } public int Page { get; set; } = 1; public int PageSize { get; set; } = 10; } [HttpGet("users/search")] public IActionResult SearchUsers([FromQuery] UserSearchParams parameters) { // 直接使用 parameters.Name, parameters.Page 等 return Ok(_userService.Search(parameters)); }
3)请求体参数(Request Body)
-
通过 HTTP 请求体传递复杂数据(如 JSON/XML),适用于 POST/PUT/PATCH 请求。
JSON 绑定示例[HttpPost("users")] public IActionResult CreateUser([FromBody] UserCreateDto dto) { if (!ModelState.IsValid) return BadRequest(ModelState); var user = _mapper.Map<User>(dto); _db.Users.Add(user); _db.SaveChanges(); return CreatedAtAction(nameof(GetProduct), new { id = user.Id }, user); }
DTO 类
public class UserCreateDto { [Required] [StringLength(100)] public string Name { get; set; } public int Age { get; set; } }
配置 JSON 序列化(Program.cs)
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.WriteIndented = true; });
注意:确保请求头包含 Content-Type: application/json。
4)表单参数(Form Data)
-
处理 HTML 表单提交或文件上传,支持 multipart/form-data 或 x-www-form-urlencoded。
-
文件上传示例
[HttpPost("upload")] public async Task<IActionResult> UploadFile( [FromForm] IFormFile file, [FromForm] string description) { if (file == null || file.Length == 0) return BadRequest("No file uploaded."); var uploadsPath = Path.Combine(_env.WebRootPath, "uploads"); Directory.CreateDirectory(uploadsPath); var filePath = Path.Combine(uploadsPath, file.FileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } return Ok(new { FileName = file.FileName, Size = file.Length, Description = description }); }
配置上传限制(Program.cs)
// 修改默认 28.6MB 限制 builder.Services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 1024 * 1024 * 100; // 100MB });
调用方式:
使用 Postman 或前端表单提交,选择 form-data 类型。
5)请求头参数(Headers)
-
从 HTTP 请求头(Headers )中获取参数,常用于身份验证(如 JWT Token)。
[HttpGet("profile")] public IActionResult GetUserProfile([FromHeader(Name = "Authorization")] string authHeader) { if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) return Unauthorized(); var token = authHeader["Bearer ".Length..]; var principal = _tokenService.ValidateToken(token); // 获取用户信息... return Ok(principal.Claims.ToDictionary(c => c.Type, c => c.Value)); }
调用示例:
请求头添加:Authorization: Bearer your_token_here
6)混合参数传递
-
结合路径、查询参数和请求体,适用于复杂场景
[HttpPut("orders/{orderId}/items/{itemId}")] public IActionResult UpdateOrderItem( int orderId, // 路径参数 Guid itemId, // 路径参数 [FromQuery] bool trackChanges, // 查询参数 [FromBody] OrderItemUpdateDto dto) // 请求体 { var item = _orderService.UpdateItem(orderId, itemId, dto, trackChanges); return Ok(item); }
调用示例:
PUT /api/orders/123/items/abcde?trackChanges=true Content-Type: application/json { "quantity": 5, "notes": "Urgent delivery" }
7)动态参数(Dynamic Object)
-
使用 dynamic 或字典接收未定义参数
[HttpPost("dynamic")] public IActionResult DynamicParams([FromBody] dynamic data) { JObject json = JObject.FromObject(data); string name = json["name"]?.ToString(); int? age = json["age"]?.ToObject<int?>(); return Ok(new { name, age }); }
注意:动态类型需谨慎处理,避免安全漏洞
四、关键技术与调试技巧
模型绑定控制
- 显式绑定来源
使用 [FromRoute]、[FromQuery]、[FromBody] 等特性明确参数来源。 - 禁用自动绑定
在 Program.cs 中关闭全局模型绑定:builder.Services.Configure<ApiBehaviorOptions>(options => { options.SuppressInferBindingSourcesForParameters = true; });
复杂模型验证
-
示例
public class ProductCreateDto { [Required(ErrorMessage = "产品名称必填")] [StringLength(100, ErrorMessage = "名称不能超过100字符")] public string Name { get; set; } [Range(0.01, double.MaxValue, ErrorMessage = "价格必须大于0")] public decimal Price { get; set; } [Url(ErrorMessage = "图片链接格式不正确")] public string ImageUrl { get; set; } } [HttpPost] public IActionResult CreateProduct([FromBody] ProductCreateDto dto) { if (!ModelState.IsValid) { // 返回详细错误信息 return ValidationProblem(ModelState); } // 处理逻辑... }
处理数组和集合
-
查询参数中的数组
// GET /api/products?ids=1&ids=2&ids=3 [HttpGet("products")] public IActionResult GetProductsByIds([FromQuery] List<int> ids) { var products = _db.Products.Where(p => ids.Contains(p.Id)).ToList(); return Ok(products); }
-
JSON 请求体中的数组
[HttpPost("bulk")] public IActionResult BulkCreate([FromBody] List<ProductCreateDto> dtos) { // 批量处理逻辑... }
全局异常处理
-
在 Program.cs 中配置统一错误响应
builder.Services.AddProblemDetails(); app.UseExceptionHandler(exceptionHandlerApp => { exceptionHandlerApp.Run(async context => { var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error; var problemDetails = new ProblemDetails { Title = "服务器错误", Status = StatusCodes.Status500InternalServerError, Detail = exception?.Message }; await context.Response.WriteAsJsonAsync(problemDetails); }); });
五、性能与安全最佳实践
1)防过度提交攻击(Overposting)
-
使用 [BindNever] 或 DTO 模式过滤不需要的字段
public class UserUpdateDto { public string Name { get; set; } public string Email { get; set; } [BindNever] // 阻止绑定 IsAdmin 字段 public bool IsAdmin { get; set; } }
2)启用请求验证
-
在 Program.cs 中强制验证所有请求
builder.Services.Configure<ApiBehaviorOptions>(options => { options.InvalidModelStateResponseFactory = context => { var problemDetails = new ValidationProblemDetails(context.ModelState) { Title = "参数验证失败", Status = StatusCodes.Status400BadRequest }; return new BadRequestObjectResult(problemDetails); }; });
3)文件上传安全
- 验证文件扩展名和签名
- 限制文件大小
- 存储到非 Web 根目录
private static readonly Dictionary<string, List<byte[]>> _fileSignatures = new() { { ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47 } } }, { ".jpg", new List<byte[]> { new byte[] { 0xFF, 0xD8, 0xFF } } }; [HttpPost("safe-upload")] public async Task<IActionResult> SafeUpload([FromForm] IFormFile file) { using var memoryStream = new MemoryStream(); await file.CopyToAsync(memoryStream); var fileData = memoryStream.ToArray(); var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); if (!_fileSignatures.ContainsKey(ext)) return BadRequest("不支持的文件类型"); bool valid = _fileSignatures[ext].Any(signature => fileData.Take(signature.Length).SequenceEqual(signature)); if (!valid) return BadRequest("文件内容与扩展名不匹配"); // 保存文件... }
4)API 版本控制
-
使用 Microsoft.AspNetCore.Mvc.Versioning 包
builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ReportApiVersions = true; }); // 控制器中指定版本 [ApiVersion("1.0")] [ApiVersion("2.0")] [Route("api/v{version:apiVersion}/products")] public class ProductsController : ControllerBase { [HttpGet] [MapToApiVersion("1.0")] public IActionResult GetV1() { ... } [HttpGet] [MapToApiVersion("2.0")] public IActionResult GetV2() { ... } }
5)缓存控制
- 使用 ResponseCache 特性或 ETag 实现缓存。
- 示例:
[HttpGet("{id}")] [ResponseCache(Duration = 60)] // 缓存60秒 public IActionResult GetProduct(int id) { ... }
6)启用 HTTPS
- 强制使用 HTTPS 确保通信安全。
- 配置方法:
services.AddHttpsRedirection(options => { options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect; });
7)认证与授权
- 集成 JWT、OAuth2 等方案,使用 [Authorize] 特性保护端点。
- 示例:
[Authorize(Roles = "Admin")] [HttpDelete("{id}")] public IActionResult DeleteProduct(int id) { ... }
六、调试工具
1)Swagger/OpenAPI
-
集成 Swagger 文档生成:
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); }); app.UseSwagger(); app.UseSwaggerUI();
2)Postman
使用 Postman 测试不同参数组合:
- 路径参数
- 查询字符串
- 多部分表单
- 请求头
3)ASP.NET Core 日志
-
启用详细模型绑定日志
// appsettings.Development.json { "Logging": { "LogLevel": { "Microsoft.AspNetCore.Mvc.ModelBinding": "Debug" } } }
总结
通过合理选择参数传递方式并遵循上述实践,可以构建出高效、安全且符合 RESTful 规范的 ASP.NET Core API。