REST(Representational State Transfer) 是一种基于 HTTP 协议的软件架构风格,用于设计网络应用的接口(API)。它的核心思想是通过资源(Resource)的表(Representation)来实现客户端与服务器之间的状态交互。
通过 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) { ... }
通过 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));
}
通过 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。
处理 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 类型。
从 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
结合路径、查询参数和请求体,适用于复杂场景
[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"
}
使用 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 });
}
注意:动态类型需谨慎处理,避免安全漏洞
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);
});
});
使用 [BindNever] 或 DTO 模式过滤不需要的字段
public class UserUpdateDto
{
public string Name { get; set; }
public string Email { get; set; }
[BindNever] // 阻止绑定 IsAdmin 字段
public bool IsAdmin { get; set; }
}
在 Program.cs 中强制验证所有请求
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Title = "参数验证失败",
Status = StatusCodes.Status400BadRequest
};
return new BadRequestObjectResult(problemDetails);
};
});
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("文件内容与扩展名不匹配");
// 保存文件...
}
使用 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() { ... }
}
集成 Swagger 文档生成:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
app.UseSwagger();
app.UseSwaggerUI();
使用 Postman 测试不同参数组合:
启用详细模型绑定日志
// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Mvc.ModelBinding": "Debug"
}
}
}
通过合理选择参数传递方式并遵循上述实践,可以构建出高效、安全且符合 RESTful 规范的 ASP.NET Core API。