[ASP.NET MVC 小牛之路]18 - Web API 详解与实战

在现代Web开发中,Web API 是构建后端服务的核心组件之一,它允许我们为不同类型的客户端(如浏览器、移动App、第三方系统)提供标准化的数据接口。ASP.NET Web API专注于构建RESTful风格的服务,支持JSON、XML等多种数据格式,是构建前后端分离应用、微服务的核心工具。

本文将从基础概念出发,逐步深入Web API的开发实践,涵盖项目创建、控制器设计、路由配置、CRUD实现、安全认证及最佳实践,帮助你快速掌握ASP.NET Web API的核心技能。

目录#

  1. Web API 基础概念
    1.1 什么是Web API?
    1.2 RESTful 架构风格
    1.3 Web API vs ASP.NET MVC

  2. 创建Web API项目
    2.1 项目模板选择
    2.2 项目结构解析

  3. Web API 控制器与动作
    3.1 ApiController 与控制器设计
    3.2 动作方法的返回类型
    3.3 示例:产品管理控制器

  4. 路由配置
    4.1 默认路由与自定义路由
    4.2 路由属性(RoutePrefix、Route)
    4.3 路由约束与参数传递

  5. CRUD 操作实现
    5.1 GET:查询资源(单条/列表)
    5.2 POST:创建资源
    5.3 PUT:更新资源
    5.4 DELETE:删除资源
    5.5 错误处理与状态码

  6. 内容协商与格式化
    6.1 内容协商原理
    6.2 配置格式化器
    6.3 自定义格式化器

  7. 安全与认证
    7.1 认证与授权基础
    7.2 基于Token的认证(JWT)
    7.3 [Authorize] 属性的使用

  8. 最佳实践
    8.1 路由设计规范
    8.2 错误处理最佳实践
    8.3 API版本控制
    8.4 性能优化与缓存

  9. 实战:待办事项API开发
    9.1 项目搭建与模型设计
    9.2 控制器实现(CRUD)
    9.3 测试与部署

  10. 参考资料

1. Web API 基础概念#

1.1 什么是Web API?#

Web API是一种通过HTTP协议对外提供数据服务的接口,客户端(如浏览器、移动App、其他服务器)通过发送HTTP请求(GET/POST/PUT/DELETE等)获取或操作数据。ASP.NET Web API专注于构建RESTful风格的服务,支持JSON、XML等多种数据格式,是构建前后端分离应用、微服务的核心工具。

1.2 RESTful 架构风格#

REST(Representational State Transfer)是一种软件架构风格,核心原则包括:

  • 资源(Resource):以URI标识(如 /api/products 表示产品资源集合)。
  • 统一接口:使用HTTP方法(GET/POST/PUT/DELETE)操作资源。
  • 无状态:每个请求包含足够信息,服务器不保留客户端状态。
  • 表现层:资源的表现形式(如JSON/XML)与资源本身分离。

示例:

  • GET /api/products:获取所有产品(资源列表)。
  • GET /api/products/1:获取ID为1的产品(单个资源)。
  • POST /api/products:创建新产品(资源)。
  • PUT /api/products/1:更新ID为1的产品。
  • DELETE /api/products/1:删除ID为1的产品。

1.3 Web API vs ASP.NET MVC#

对比项ASP.NET MVCASP.NET Web API
核心目标渲染页面(View)提供数据服务(JSON/XML等)
路由设计面向页面(如 /Home/Index)面向资源(如 /api/products)
返回类型ActionResult(视图/数据)IHttpActionResult/HttpResponseMessage/直接返回模型
客户端场景浏览器为主多客户端(移动、前端、第三方)

2. 创建Web API项目#

2.1 项目模板选择#

  1. 打开Visual Studio,新建项目 → 选择 ASP.NET Web Application。
  2. 命名项目(如 WebApiDemo),选择框架版本(建议.NET Framework 4.6+或.NET Core)。
  3. 模板选择:Web API(若使用.NET Core,选择“API”模板)。

注意:.NET Core中,ASP.NET Core Web API是更现代的选择,本文以.NET Framework的Web API 2为例,核心概念与.NET Core版本兼容。

2.2 项目结构解析#

创建后的项目结构包含:

  • Controllers 文件夹:存放API控制器(继承 ApiController),默认包含 ValuesController.cs(示例控制器)。
  • Models 文件夹:存放数据模型(如实体类、DTO)。
  • App_Start 文件夹:
    • WebApiConfig.cs:配置Web API路由、格式化器等。
    • RouteConfig.cs:MVC路由(若项目同时包含MVC)。
  • Global.asax.cs:应用程序启动入口,注册路由等。

3. Web API 控制器与动作#

3.1 ApiController 与控制器设计#

Web API控制器需继承 System.Web.Http.ApiController(.NET Framework)或 Microsoft.AspNetCore.Mvc.ControllerBase(.NET Core)。

示例:创建ProductController管理产品资源:

using System.Web.Http;
using WebApiDemo.Models;
 
public class ProductController : ApiController
{
    // 模拟数据存储
    private static List<Product> products = new List<Product>
    {
        new Product { Id = 1, Name = "手机", Price = 3999 },
        new Product { Id = 2, Name = "电脑", Price = 5999 }
    };
 
    // 动作方法...
}
 
// 模型类
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

3.2 动作方法的返回类型#

Web API动作方法支持三种返回类型:

  1. 直接返回模型:如 List<Product>,框架自动序列化为JSON/XML。
  2. HttpResponseMessage:手动控制HTTP响应(状态码、头部、内容)。
  3. IHttpActionResult:推荐!封装常见响应(如Ok()/NotFound()/BadRequest()),更简洁。

示例(IHttpActionResult):

// 获取所有产品
public IHttpActionResult Get()
{
    return Ok(products); // 返回200 OK,包含产品列表
}
 
// 获取单个产品
public IHttpActionResult Get(int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);
    if (product == null)
    {
        return NotFound(); // 返回404 Not Found
    }
    return Ok(product);
}

3.3 示例:产品管理控制器#

完整的ProductController示例(包含CRUD):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using WebApiDemo.Models;
 
public class ProductController : ApiController
{
    private List<Product> products = new List<Product>
    {
        new Product { Id = 1, Name = "手机", Price = 3999 },
        new Product { Id = 2, Name = "电脑", Price = 5999 }
    };
 
    // GET: api/Product
    public IHttpActionResult Get()
    {
        return Ok(products);
    }
 
    // GET: api/Product/5
    public IHttpActionResult Get(int id)
    {
        var product = products.FirstOrDefault(p => p.Id == id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }
 
    // POST: api/Product
    public IHttpActionResult Post([FromBody]Product product)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState); // 返回400,包含验证错误
        }
        // 模拟新增(实际应使用数据库)
        product.Id = products.Max(p => p.Id) + 1;
        products.Add(product);
        return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
    }
 
    // PUT: api/Product/5
    public IHttpActionResult Put(int id, [FromBody]Product product)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        if (id != product.Id)
        {
            return BadRequest("ID不匹配");
        }
        var existing = products.FirstOrDefault(p => p.Id == id);
        if (existing == null)
        {
            return NotFound();
        }
        // 更新属性
        existing.Name = product.Name;
        existing.Price = product.Price;
        return Ok(existing);
    }
 
    // DELETE: api/Product/5
    public IHttpActionResult Delete(int id)
    {
        var product = products.FirstOrDefault(p => p.Id == id);
        if (product == null)
        {
            return NotFound();
        }
        products.Remove(product);
        return Ok(product);
    }
}

4. 路由配置#

4.1 默认路由与自定义路由#

Web API的默认路由在 WebApiConfig.cs 中配置:

using System.Web.Http;
 
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 默认路由:api/{controller}/{id}
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

默认路由规则:

  • GET /api/Product → 调用 ProductController.Get()(无参)。
  • GET /api/Product/1 → 调用 ProductController.Get(1)。
  • POST /api/Product → 调用 ProductController.Post(...)。

4.2 路由属性(RoutePrefix、Route)#

若需更灵活的路由(如嵌套资源、自定义URL),可使用路由属性:

示例:为ProductController设置路由前缀,并为动作方法自定义路由:

using System.Web.Http;
 
[RoutePrefix("api/v1/products")] // 路由前缀
public class ProductController : ApiController
{
    // GET api/v1/products
    [Route("")]
    public IHttpActionResult GetAll()
    {
        // ...
    }
 
    // GET api/v1/products/5
    [Route("{id:int}")] // 约束id为整数
    public IHttpActionResult GetById(int id)
    {
        // ...
    }
 
    // POST api/v1/products
    [Route("")]
    public IHttpActionResult Create([FromBody]Product product)
    {
        // ...
    }
}

4.3 路由约束与参数传递#

路由约束可限制参数类型(如int/string/datetime),示例:

// 匹配 /api/products/1(id为整数)
[Route("{id:int}")]
 
// 匹配 /api/products/abc(id为字符串)
[Route("{id:alpha}")]

5. CRUD 操作实现#

5.1 GET:查询资源#

  • 列表查询:返回所有资源,使用GET /api/resource。
  • 单条查询:返回指定ID的资源,使用GET /api/resource/{id}。

示例(续上ProductController的Get方法):

// 列表查询
public IHttpActionResult Get()
{
    return Ok(products);
}
 
// 单条查询
public IHttpActionResult Get(int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);
    return product == null ? (IHttpActionResult)NotFound() : Ok(product);
}

5.2 POST:创建资源#

使用POST /api/resource,请求体包含资源数据(JSON/XML)。需注意:

  • 模型验证(ModelState.IsValid)。
  • 创建成功后返回201 Created,并包含资源位置(CreatedAtRoute)。

示例:

public IHttpActionResult Post([FromBody]Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    // 模拟新增
    product.Id = products.Max(p => p.Id) + 1;
    products.Add(product);
    // 返回201,并指定资源位置(如 /api/Product/3)
    return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}

5.3 PUT:更新资源#

使用PUT /api/resource/{id},请求体包含更新后的资源数据。需注意:

  • ID匹配(URL中的ID与请求体中的ID一致)。
  • 资源存在性检查。

示例:

public IHttpActionResult Put(int id, [FromBody]Product product)
{
    if (id != product.Id)
    {
        return BadRequest("ID不匹配");
    }
    var existing = products.FirstOrDefault(p => p.Id == id);
    if (existing == null)
    {
        return NotFound();
    }
    // 更新属性
    existing.Name = product.Name;
    existing.Price = product.Price;
    return Ok(existing);
}

5.4 DELETE:删除资源#

使用DELETE /api/resource/{id},删除指定ID的资源。需注意:

  • 资源存在性检查。
  • 删除后返回200 OK或204 No Content(无响应体)。

示例:

public IHttpActionResult Delete(int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);
    if (product == null)
    {
        return NotFound();
    }
    products.Remove(product);
    return Ok(product); // 或 return StatusCode(HttpStatusCode.NoContent);
}

5.5 错误处理与状态码#

Web API通过IHttpActionResult的方法返回标准HTTP状态码:

  • NotFound() → 404。
  • BadRequest() → 400。
  • Unauthorized() → 401。
  • InternalServerError() → 500。

示例:处理无效请求:

public IHttpActionResult Post([FromBody]Product product)
{
    if (string.IsNullOrEmpty(product.Name))
    {
        ModelState.AddModelError("Name", "产品名称不能为空");
    }
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState); // 返回400,包含错误信息
    }
    // ...
}

6. 内容协商与格式化#

6.1 内容协商原理#

Web API根据请求头的Accept字段决定返回格式(如Accept: application/json则返回JSON)。框架默认支持JSON和XML格式化器。

6.2 配置格式化器#

若需只返回JSON(移除XML格式化器),在WebApiConfig.cs中配置:

public static void Register(HttpConfiguration config)
{
    // 移除XML格式化器
    config.Formatters.Remove(config.Formatters.XmlFormatter);
 
    // 配置JSON格式化器(可选,如缩进、日期格式)
    config.Formatters.JsonFormatter.SerializerSettings.Formatting = 
        Newtonsoft.Json.Formatting.Indented;
    config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = 
        new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(); // 小驼峰命名
}

6.3 自定义格式化器#

若需支持自定义格式(如CSV),可实现MediaTypeFormatter,但通常JSON/XML足够。

7. 安全与认证#

7.1 认证与授权基础#

Web API的安全包括认证(用户身份验证)和授权(用户权限控制)。常见方案:

  • 基本认证:在请求头中传递Base64编码的用户名/密码(不推荐明文传输,需HTTPS)。
  • Bearer Token:基于OAuth2,使用Authorization: Bearer {token}传递令牌。
  • ASP.NET Identity:结合Cookie或Token认证。

7.2 基于Token的认证(JWT)#

JSON Web Token(JWT)是轻量级的认证方式,步骤:

  1. 客户端登录,服务端生成JWT(包含用户信息、过期时间)。
  2. 客户端后续请求在Authorization头中携带JWT。
  3. 服务端验证JWT有效性,授权访问。

示例:使用[Authorize]属性保护API:

[Authorize] // 需认证才能访问
public IHttpActionResult GetUserInfo()
{
    var user = User.Identity.Name;
    return Ok(new { UserName = user });
}

7.3 [Authorize] 属性的使用#

可在控制器或动作方法上添加[Authorize],也可指定角色/用户:

// 只允许Admin角色访问
[Authorize(Roles = "Admin")]
public IHttpActionResult Delete(int id)
{
    // ...
}
 
// 只允许特定用户访问
[Authorize(Users = "Alice,Bob")]
public IHttpActionResult GetSecret()
{
    // ...
}

8. 最佳实践#

8.1 路由设计规范#

  • 资源为中心:URI以资源命名(复数,如/api/products)。
  • 层级清晰:嵌套资源(如/api/orders/1/items表示订单1的商品)。
  • 避免动词:用HTTP方法代替(如不用/api/products/getAll,而用GET /api/products)。

8.2 错误处理最佳实践#

  • 返回适当的HTTP状态码(如404表示资源不存在,400表示请求错误)。
  • 错误信息包含可读的描述(如{"message": "产品名称不能为空"})。
  • 使用IHttpActionResult的方法(NotFound()/BadRequest())而非手动设置状态码。

8.3 API版本控制#

随着业务迭代,API需版本控制,方案:

  • URI版本:/api/v1/products、/api/v2/products。
  • 请求头版本:通过X-API-Version头指定版本。
  • 媒体类型版本:通过Accept头的version参数(如Accept: application/json;version=2)。

8.4 性能优化与缓存#

  • 异步操作:控制器方法使用async/await,避免阻塞线程(如public async Task<IHttpActionResult> Get())。
  • 缓存:使用OutputCache或自定义缓存逻辑(如[CacheOutput(ClientTimeSpan = 60)])。
  • 数据过滤:返回必要字段(如用DTO代替全量模型)。

9. 实战:待办事项API开发#

9.1 项目搭建与模型设计#

  1. 新建Web API项目,命名TodoApi。
  2. 添加TodoItem模型:
    public class TodoItem
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public bool IsCompleted { get; set; }
        public DateTime CreatedAt { get; set; } = DateTime.Now;
    }

9.2 控制器实现(CRUD)#

创建TodoController,实现CRUD:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
 
public class TodoController : ApiController
{
    private List<TodoItem> todos = new List<TodoItem>();
 
    // GET: api/Todo
    public IHttpActionResult Get()
    {
        return Ok(todos);
    }
 
    // GET: api/Todo/5
    public IHttpActionResult Get(int id)
    {
        var todo = todos.FirstOrDefault(t => t.Id == id);
        return todo == null ? (IHttpActionResult)NotFound() : Ok(todo);
    }
 
    // POST: api/Todo
    public IHttpActionResult Post([FromBody]TodoItem todo)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        todo.Id = todos.Count > 0 ? todos.Max(t => t.Id) + 1 : 1;
        todos.Add(todo);
        return CreatedAtRoute("DefaultApi", new { id = todo.Id }, todo);
    }
 
    // PUT: api/Todo/5
    public IHttpActionResult Put(int id, [FromBody]TodoItem todo)
    {
        if (id != todo.Id)
        {
            return BadRequest("ID不匹配");
        }
        var existing = todos.FirstOrDefault(t => t.Id == id);
        if (existing == null)
        {
            return NotFound();
        }
        existing.Title = todo.Title;
        existing.IsCompleted = todo.IsCompleted;
        return Ok(existing);
    }
 
    // DELETE: api/Todo/5
    public IHttpActionResult Delete(int id)
    {
        var todo = todos.FirstOrDefault(t => t.Id == id);
        if (todo == null)
        {
            return NotFound();
        }
        todos.Remove(todo);
        return Ok(todo);
    }
}

9.3 测试与部署#

  • 测试:使用Postman发送HTTP请求,验证各端点(如GET http://localhost:5000/api/Todo)。
  • 部署:发布到IIS或云服务器(如Azure App Service)。

参考资料#


通过本文,你已掌握ASP.NET Web API的核心开发流程:从项目创建、控制器设计、路由配置到CRUD实现、安全认证及最佳实践。实践是掌握的关键,建议结合示例项目(如待办事项API)反复调试,深入理解RESTful风格与Web API的设计思想。

希望这篇“小牛之路”能助你在Web API开发的道路上更进一步!如有疑问,欢迎留言交流~