RESTful


前言

REST(Representational State Transfer) 是一种基于 HTTP 协议的软件架构风格,用于设计网络应用的接口(API)。它的核心思想是通过资源(Resource)的表(Representation)来实现客户端与服务器之间的状态交互。

一、REST 遵循核心原则

  1. 资源导向:所有事物抽象为资源(如用户、订单),通过唯一标识符(URI/URL)定位。
  2. 统一接口:使用标准的 HTTP 方法(GET、POST、PUT、DELETE 等)操作资源。
  3. 无状态(Stateless):服务器不保存客户端状态,每次请求必须包含所有必要信息。
  4. 可缓存:响应需明确标注是否可缓存,以提高性能。
  5. 分层系统:客户端无需关心服务器架构(如负载均衡、代理)。
  6. 按需代码(可选):服务器可返回可执行代码(如 JavaScript)扩展客户端功能。

二、REST 的优点和缺点

1.优点

  1. 简单易用
    基于 HTTP 标准方法,学习成本低。
    使用 URL 和 JSON/XML 等通用格式,开发调试方便。
  2. 可扩展性强
    无状态设计允许服务器轻松横向扩展。
    客户端与服务器解耦,支持独立迭代。
  3. 高性能与缓存
    利用 HTTP 缓存机制(如 Cache-Control),减少重复请求。
  4. 跨平台兼容
    支持多种数据格式(JSON、XML、HTML等),适配不同客户端(Web、移动端、IoT设备)。
  5. 松耦合
    客户端只需关注资源 URI 和接口规范,无需了解服务端实现细节。

2.缺点

  1. 过度请求(Over-fetching/Under-fetching)
    返回固定数据结构,可能导致客户端获取冗余数据或需要多次请求(如嵌套资源)。
  2. 无状态的双刃剑
    每次请求需携带完整信息(如身份验证 Token),增加网络开销。
    不适合需保持会话状态的应用(如实时游戏)。
  3. 缺乏严格规范
    REST 是架构风格而非标准,不同开发者对“RESTful”的理解可能不同(如 URI 设计、HTTP 方法使用)。
  4. 复杂操作支持有限
    对非 CRUD(增删改查)操作(如批量更新、事务处理)需设计绕行方案(如自定义端点)。
  5. 版本管理问题
    API 升级时需谨慎处理版本兼容性(如通过 URL 路径 v1/resource 或请求头)。

三、常见参数传递方式

1)路径参数(Path Parameters)

  1. 通过 URL 路径中的占位符传递参数,常用于标识唯一资源

    [HttpGet("users/{id}")]
    public IActionResult GetUserById(int id) 
    {
        // 直接通过方法参数接收路径参数
        var user = _userService.GetUser(id);
        return Ok(user);
    }
    

    路由模板[HttpGet(“users/{id}”)] 定义占位符 {id},参数名需与方法参数名一致,支持类型自动转换(如 int、Guid)。

  2. 高级场景(自定义路由约束(如正则表达式))

    [HttpGet("users/{id:guid}")]
    public IActionResult GetUserById(Guid id) { ... }
    
    [HttpGet("posts/{slug:regex(^[a-z0-9-]+$)}")]
    public IActionResult GetPostBySlug(string slug) { ... }
    

2)查询参数(Query Parameters)

  1. 通过 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);
    }
    
  2. 调用示例

    GET /api/users?name=John&age=30
    GET /api/users?name=Alice&page=2&pageSize=20
    
  3. 对象绑定(将多个查询参数封装到对象)

    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)

  1. 通过 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)

  1. 处理 HTML 表单提交或文件上传,支持 multipart/form-datax-www-form-urlencoded

  2. 文件上传示例

    [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)

  1. 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)混合参数传递

  1. 结合路径、查询参数和请求体,适用于复杂场景

    [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)

  1. 使用 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 });
    }
    

    注意:动态类型需谨慎处理,避免安全漏洞


四、关键技术与调试技巧

模型绑定控制

  1. 显式绑定来源
    使用 [FromRoute]、[FromQuery]、[FromBody] 等特性明确参数来源。
  2. 禁用自动绑定
    Program.cs 中关闭全局模型绑定:
    builder.Services.Configure<ApiBehaviorOptions>(options =>
    {
        options.SuppressInferBindingSourcesForParameters = true;
    });
    

复杂模型验证

  1. 示例

    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);
        }
        // 处理逻辑...
    }
    

处理数组和集合

  1. 查询参数中的数组

    // 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);
    }
    
  2. JSON 请求体中的数组

    [HttpPost("bulk")]
    public IActionResult BulkCreate([FromBody] List<ProductCreateDto> dtos)
    {
        // 批量处理逻辑...
    }
    

全局异常处理

  1. 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)

  1. 使用 [BindNever]DTO 模式过滤不需要的字段

    public class UserUpdateDto
    {
        public string Name { get; set; }
        public string Email { get; set; }
    
        [BindNever] // 阻止绑定 IsAdmin 字段
        public bool IsAdmin { get; set; }
    }
    

2)启用请求验证

  1. 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)文件上传安全

  1. 验证文件扩展名和签名
  2. 限制文件大小
  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 版本控制

  1. 使用 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)缓存控制

  1. 使用 ResponseCache 特性或 ETag 实现缓存
  2. 示例
    [HttpGet("{id}")]
    [ResponseCache(Duration = 60)] // 缓存60秒
    public IActionResult GetProduct(int id) { ... }
    

6)启用 HTTPS

  1. 强制使用 HTTPS 确保通信安全。
  2. 配置方法
    services.AddHttpsRedirection(options => 
    {
        options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
    });
    

7)认证与授权

  1. 集成 JWT、OAuth2 等方案,使用 [Authorize] 特性保护端点。
  2. 示例
    [Authorize(Roles = "Admin")]
    [HttpDelete("{id}")]
    public IActionResult DeleteProduct(int id) { ... }
    

六、调试工具

1)Swagger/OpenAPI

  1. 集成 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 测试不同参数组合:

  1. 路径参数
  2. 查询字符串
  3. 多部分表单
  4. 请求头

3)ASP.NET Core 日志

  1. 启用详细模型绑定日志

    // appsettings.Development.json
    {
      "Logging": {
        "LogLevel": {
          "Microsoft.AspNetCore.Mvc.ModelBinding": "Debug"
        }
      }
    }
    

总结

通过合理选择参数传递方式并遵循上述实践,可以构建出高效、安全且符合 RESTful 规范的 ASP.NET Core API

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值