C# RESTful服务开发权威指南


一、RESTful服务基础概念

1.1 REST架构核心原则

  • 资源导向:一切皆资源,使用URI标识
  • 统一接口:标准HTTP方法操作资源
  • 无状态性:每个请求包含完整上下文
  • 可缓存性:响应应明确是否可缓存
  • 分层系统:客户端无需了解直接连接的服务端

1.2 HTTP方法语义规范

方法幂等性安全典型应用场景
GET获取资源表示
POST创建新资源或执行操作
PUT全量更新已知资源
PATCH部分更新资源
DELETE删除指定资源

二、ASP.NET Core RESTful服务搭建

2.1 项目初始化与配置

# 创建WebAPI项目
dotnet new webapi -n ProductService
cd ProductService

# 添加必要NuGet包
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

2.2 基础项目结构

ProductService/
├── Controllers/
│   └── ProductsController.cs
├── Models/
│   ├── Entities/
│   │   └── Product.cs
│   └── DTOs/
│       ├── ProductCreateDto.cs
│       └── ProductResponseDto.cs
├── Services/
│   ├── IProductService.cs
│   └── ProductService.cs
├── Mappings/
│   └── ProductProfile.cs
└── Program.cs

三、核心实现技术

3.1 控制器设计规范

[ApiController]
[Route("api/products")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _service;
    private readonly IMapper _mapper;

    public ProductsController(IProductService service, IMapper mapper)
    {
        _service = service;
        _mapper = mapper;
    }

    /// <summary>
    /// 获取所有产品
    /// </summary>
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<ProductResponseDto>>> GetAll()
    {
        var products = await _service.GetAllAsync();
        return Ok(_mapper.Map<List<ProductResponseDto>>(products));
    }
}

3.2 高级路由配置

// 复合资源路由
[HttpGet("categories/{categoryId}/products")]
public IActionResult GetProductsByCategory(int categoryId)
{
    // ...
}

// 自定义路由约束
[HttpGet("{id:int:min(1)}")]
public IActionResult GetById(int id)
{
    // ...
}

// 条件路由
[HttpGet("search", Name = "SearchProducts")]
public IActionResult Search([FromQuery] ProductSearchCriteria criteria)
{
    // ...
}

四、数据序列化与内容协商

4.1 响应格式控制

// 配置系统文本JSON序列化
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.WriteIndented = true;
    });

// 支持XML格式
builder.Services.AddControllers()
    .AddXmlSerializerFormatters();

4.2 自定义格式化器

public class CsvOutputFormatter : TextOutputFormatter
{
    public CsvOutputFormatter()
    {
        SupportedMediaTypes.Add("text/csv");
        SupportedEncodings.Add(Encoding.UTF8);
    }

    protected override bool CanWriteType(Type type)
    {
        return typeof(IEnumerable).IsAssignableFrom(type);
    }

    public override async Task WriteResponseBodyAsync(
        OutputFormatterWriteContext context, 
        Encoding selectedEncoding)
    {
        // CSV转换逻辑
    }
}

// 注册格式化器
builder.Services.AddControllers(options =>
{
    options.OutputFormatters.Add(new CsvOutputFormatter());
});

五、高级功能实现

5.1 HATEOAS支持

public class ProductResource
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public List<Link> Links { get; set; } = new List<Link>();
}

[HttpGet("{id}", Name = "GetProduct")]
public IActionResult GetById(int id)
{
    var product = _service.GetById(id);
    if (product == null) return NotFound();

    var resource = _mapper.Map<ProductResource>(product);
    resource.Links.Add(new Link(
        href: Url.Link("GetProduct", new { id }),
        rel: "self",
        method: "GET"));

    resource.Links.Add(new Link(
        href: Url.Link("UpdateProduct", new { id }),
        rel: "update-product",
        method: "PUT"));

    return Ok(resource);
}

5.2 并发控制

// 使用ETag实现乐观并发
[HttpPut("{id}")]
public IActionResult UpdateProduct(int id, 
    [FromBody] ProductUpdateDto dto,
    [FromHeader(Name = "If-Match")] string etag)
{
    var product = _service.GetById(id);
    if (product == null) return NotFound();

    // 验证ETag
    var currentEtag = _service.GetProductVersion(id);
    if (etag != currentEtag)
    {
        return StatusCode(StatusCodes.Status412PreconditionFailed);
    }

    // 更新逻辑
    _service.UpdateProduct(id, dto);

    // 返回新ETag
    Response.Headers["ETag"] = _service.GetProductVersion(id);
    return NoContent();
}

六、安全防护机制

6.1 认证与授权

// JWT配置
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidateAudience = true,
            ValidAudience = builder.Configuration["Jwt:Audience"],
            ValidateLifetime = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
            ValidateIssuerSigningKey = true
        };
    });

// 策略授权
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireAdmin", policy => 
        policy.RequireRole("Administrator"));

    options.AddPolicy("MinimumAge", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(18)));
});

6.2 输入验证与防护

// 数据注解验证
public class ProductCreateDto
{
    [Required(ErrorMessage = "产品名称是必填项")]
    [StringLength(100, MinimumLength = 2)]
    public string Name { get; set; }

    [Range(0.01, double.MaxValue)]
    public decimal Price { get; set; }

    [Url]
    public string ProductUrl { get; set; }
}

// 防跨站请求伪造
builder.Services.AddAntiforgery(options =>
{
    options.HeaderName = "X-CSRF-TOKEN";
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

七、性能优化策略

7.1 缓存实现

// 响应缓存
[HttpGet("{id}")]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
public IActionResult GetById(int id)
{
    // ...
}

// 分布式缓存
[HttpGet("featured")]
public async Task<IActionResult> GetFeaturedProducts()
{
    const string cacheKey = "featured_products";

    if (!_cache.TryGetValue(cacheKey, out List<Product> products))
    {
        products = await _service.GetFeaturedProductsAsync();
        var cacheOptions = new MemoryCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromMinutes(5));

        _cache.Set(cacheKey, products, cacheOptions);
    }

    return Ok(products);
}

7.2 分页与数据压缩

[HttpGet]
[ResponseCompression]
public async Task<ActionResult<PagedResponse<ProductDto>>> GetPaged(
    [FromQuery] PaginationParams parameters)
{
    var pagedData = await _service.GetPagedProductsAsync(parameters);

    Response.Headers.Add("X-Pagination", 
        JsonSerializer.Serialize(pagedData.PaginationMetadata));

    return Ok(pagedData);
}

// 响应压缩配置
builder.Services.AddResponseCompression(options =>
{
    options.Providers.Add<GzipCompressionProvider>();
    options.EnableForHttps = true;
});

八、测试与文档

8.1 单元测试示例

public class ProductsControllerTests
{
    private readonly ProductsController _controller;
    private readonly Mock<IProductService> _mockService = new();

    public ProductsControllerTests()
    {
        var mapper = new MapperConfiguration(cfg => 
            cfg.AddProfile(new ProductProfile())).CreateMapper();

        _controller = new ProductsController(_mockService.Object, mapper);
    }

    [Fact]
    public async Task Create_ReturnsCreated_WhenModelValid()
    {
        // Arrange
        var dto = new ProductCreateDto { /* 初始化 */ };
        _mockService.Setup(x => x.CreateAsync(It.IsAny<Product>()))
            .ReturnsAsync(new Product { Id = 1 });

        // Act
        var result = await _controller.Create(dto);

        // Assert
        var createdAtResult = Assert.IsType<CreatedAtActionResult>(result);
        Assert.Equal(201, createdAtResult.StatusCode);
    }
}

8.2 Swagger文档增强

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo 
    { 
        Title = "Product Service API", 
        Version = "v1",
        Contact = new OpenApiContact
        {
            Name = "API Support",
            Email = "support@productservice.com"
        }
    });

    // 添加JWT支持
    var securityScheme = new OpenApiSecurityScheme
    {
        Name = "JWT Authentication",
        Description = "Enter JWT Bearer token",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT",
        Reference = new OpenApiReference
        {
            Id = JwtBearerDefaults.AuthenticationScheme,
            Type = ReferenceType.SecurityScheme
        }
    };

    c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        { securityScheme, Array.Empty<string>() }
    });
});

九、部署与监控

9.1 容器化部署

# 多阶段构建Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore "ProductService.csproj"
RUN dotnet publish "ProductService.csproj" -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "ProductService.dll"]

9.2 健康检查与监控

// 健康检查配置
builder.Services.AddHealthChecks()
    .AddDbContextCheck<ProductContext>()
    .AddRedis(Configuration["Redis:ConnectionString"])
    .AddUrlGroup(new Uri("http://external-service"), "External API");

// Prometheus监控
builder.Services.AddOpenTelemetryMetrics(builder =>
{
    builder.AddAspNetCoreInstrumentation()
           .AddHttpClientInstrumentation()
           .AddPrometheusExporter();
});

app.UseOpenTelemetryPrometheusScrapingEndpoint();

十、RESTful设计最佳实践

  1. 资源命名规范
  • 使用名词复数形式(如/products
  • 避免动词出现在URI中
  • 层级关系表达(如/stores/{id}/products
  1. 状态码正确使用
  • 200 OK – 成功GET请求
  • 201 Created – 资源创建成功
  • 204 No Content – 成功但无返回内容
  • 400 Bad Request – 客户端错误
  • 404 Not Found – 资源不存在
  • 429 Too Many Requests – 请求限流
  1. 版本控制策略
  • URL路径版本(/api/v1/products
  • 查询参数版本(/api/products?version=1
  • 请求头版本(Accept: application/vnd.company.api.v1+json
  1. 错误处理统一格式
   {
     "error": {
       "code": "invalid-request",
       "message": "价格不能为负数",
       "target": "price",
       "details": [
         {
           "code": "validation-error",
           "message": "必须大于0"
         }
       ]
     }
   }

通过遵循这些原则和实践,您将能够构建出符合行业标准、易于维护且高性能的C# RESTful服务。随着.NET平台的持续演进,这些服务将能够充分利用最新的云原生特性和性能优化技术。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注