本手册旨在为初学者提供一份全面、系统的.NET Web API学习指南。无论您是刚接触Web开发的新手,还是想要深入了解.NET Web API的开发者,本手册都将引导您循序渐进地掌握所需的知识和技能。
本手册的内容从环境搭建开始,逐步深入到项目创建、控制器开发、路由系统、数据访问等核心概念,并涵盖了身份验证、文档生成、测试和部署等高级主题。每一章节都包含详细的概念解释、代码示例、操作步骤和最佳实践,使您能够在理论与实践的结合中真正掌握.NET Web API开发。
- .NET Web API概述和环境搭建
- 创建第一个Web API项目
- 控制器和动作方法
- 路由系统详解
- 模型绑定和数据验证
- 依赖注入和服务配置
- 中间件和请求管道
- 数据访问和Entity Framework
- 身份验证和授权
- 异常处理和日志记录
- API文档和Swagger
- 测试和调试
- 部署和性能优化
.NET Web API是一个用于构建HTTP服务的框架,它使用ASP.NET Core技术栈来创建RESTful API。这些API能够被任何能够发送HTTP请求的客户端(如Web浏览器、移动应用、桌面应用等)所访问。
.NET Web API专为构建面向服务的架构(SOA)和微服务架构而设计,它提供了一种简单而高效的方式来开发和发布与平台无关的Web服务。
传统Web应用(如MVC)主要关注HTML页面的生成和呈现,而Web API则专注于数据交换:
- 传统Web应用:服务器端渲染HTML,并将完整的网页发送给客户端
- Web API:服务器只发送数据(通常是JSON或XML格式),由客户端决定如何处理和呈现这些数据
- 平台无关性:API可以被任何支持HTTP的平台或语言消费
- 支持多种数据格式:默认支持JSON,但也可以轻松配置XML或其他格式
- RESTful设计:支持REST架构风格,使用标准的HTTP方法(GET, POST, PUT, DELETE等)
- 模型绑定和验证:自动将HTTP请求数据映射到强类型模型,并提供内置的验证机制
- 依赖注入:内置的依赖注入容器,简化应用程序的设计和测试
- 跨平台:可以在Windows、macOS或Linux上运行
- 为Single Page Applications (SPA)提供后端服务
- 为移动应用提供API
- 构建微服务架构
- 系统集成与B2B通信
- 为物联网(IoT)设备提供接口
.NET SDK (Software Development Kit)是开发.NET应用程序所需的核心工具集。
安装步骤:
- 访问Microsoft官方下载页面
- 选择最新的.NET SDK版本(本手册基于.NET 8.0或更高版本)
- 下载适合您操作系统的安装程序
- 运行安装程序并按照向导完成安装
- 安装完成后,打开命令行工具验证安装:
dotnet --version如果显示版本号,则表示安装成功。
对于Windows用户,Visual Studio是功能最完整的IDE选择:
- 访问Visual Studio下载页面
- 选择合适的版本(Community版本对个人开发者、学生和小型团队免费)
- 下载并运行安装程序
- 在安装向导中选择"ASP.NET和Web开发"工作负载
- 完成安装过程
对于跨平台开发或轻量级IDE的需求:
- 访问VS Code下载页面
- 下载适合您操作系统的版本
- 安装后,添加以下扩展以增强.NET开发体验:
- C# Dev Kit
- .NET Core Tools
- REST Client(用于测试API)
安装开发证书:
首次开发HTTPS应用时,需要安装ASP.NET Core HTTPS开发证书:
dotnet dev-certs https --trust全局工具安装:
安装常用的.NET全局工具,如Entity Framework Core工具:
dotnet tool install --global dotnet-efREST (Representational State Transfer) 是一种架构风格,定义了Web服务应该如何设计和实现:
- 资源识别:使用URI唯一标识资源
- 统一接口:使用标准HTTP方法表达操作意图
- 无状态:服务器不存储客户端状态
- 资源表示:资源可以有多种表示形式(如JSON、XML)
- 自描述消息:消息包含足够信息,描述如何处理
- 超媒体驱动:通过超链接指引可能的下一步操作
| HTTP方法 | CRUD操作 | 描述 |
|---|---|---|
| GET | Read | 获取资源 |
| POST | Create | 创建新资源 |
| PUT | Update | 完全替换资源 |
| PATCH | Update | 部分更新资源 |
| DELETE | Delete | 删除资源 |
| 状态码范围 | 类别 | 常见使用场景 |
|---|---|---|
| 200-299 | 成功 | 200 OK, 201 Created, 204 No Content |
| 300-399 | 重定向 | 301 Moved Permanently, 304 Not Modified |
| 400-499 | 客户端错误 | 400 Bad Request, 401 Unauthorized, 404 Not Found |
| 500-599 | 服务器错误 | 500 Internal Server Error, 503 Service Unavailable |
ASP.NET Core提供了两种主要的Web API开发方式:
-
控制器型Web API (Controller-based Web API):
- 基于控制器类和动作方法
- 提供更多开箱即用的功能和结构
- 适合复杂或大型API项目
- 有丰富的特性支持,如模型绑定、验证、筛选器等
-
最小API (Minimal API):
- 简化的API开发方法,减少样板代码
- 直接在Program.cs中定义端点
- 适合小型项目或微服务
- 从.NET 6开始引入,并在后续版本不断增强
本手册主要聚焦于控制器型Web API,它更适合学习完整的ASP.NET Core Web API功能集。
步骤详解:
- 启动Visual Studio 2022
- 点击"创建新项目"
- 在搜索框中输入"Web API"
- 选择"ASP.NET Core Web API"模板,点击"下一步"
- 在"配置新项目"对话框中:
- 输入项目名称(如"TodoApi")
- 选择合适的位置保存项目
- 可选:修改解决方案名称
- 点击"下一步"
- 在"附加信息"对话框中:
- 确认使用.NET 8.0(或最新版本)
- 确保"使用控制器"选项被勾选
- 确保"启用OpenAPI支持"被勾选(这将添加Swagger文档)
- 根据需要配置"启用Docker"和"配置HTTPS"选项
- 点击"创建"
Visual Studio将创建项目并打开解决方案,包含基本的项目结构和示例控制器。
如果您使用Visual Studio Code或其他编辑器,可以通过.NET CLI创建项目:
# 创建新的Web API项目
dotnet new webapi -n TodoApi
# 进入项目目录
cd TodoApi
# 打开VS Code(如果使用)
code .创建的Web API项目包含以下关键文件和文件夹:
- Program.cs:应用程序入口点,包含应用配置和启动代码
- appsettings.json:应用程序配置文件
- Controllers/:控制器类所在的文件夹
- WeatherForecastController.cs:示例API控制器
- Properties/:包含启动和项目配置
- launchSettings.json:定义如何启动应用程序
- WeatherForecast.cs:示例数据模型
- [项目名].csproj:项目定义文件,包含项目依赖和配置
.NET 6+使用顶级语句(Top-level statements)简化了Program.cs文件。以下是一个典型的Program.cs文件及其关键部分:
var builder = WebApplication.CreateBuilder(args);
// 添加服务到依赖注入容器
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// 配置HTTP请求管道(中间件)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();关键部分解释:
WebApplication.CreateBuilder(args):创建应用程序构建器- 服务注册:将服务添加到依赖注入容器
builder.Build():构建应用程序实例- 中间件配置:设置请求处理管道
app.Run():启动应用程序
使用Visual Studio:
- 按F5启动调试,或Ctrl+F5启动不调试
- 项目将在浏览器中打开(通常指向Swagger UI)
使用.NET CLI:
dotnet run运行后,终端会显示应用程序的URL,通常是https://localhost:7xxx和http://localhost:5xxx(端口可能不同)。
- 在浏览器中打开显示的URL
- 如果启用了Swagger,将显示Swagger UI界面
- 展开"GET /WeatherForecast"端点
- 点击"Try it out"按钮,然后点击"Execute"
- 查看响应结果
控制器(Controller)是ASP.NET Core Web API的核心组件,负责处理HTTP请求并生成HTTP响应。在Web API中,控制器通常:
- 继承自
ControllerBase类(而非MVC应用中的Controller类) - 使用
[ApiController]特性标记 - 包含一个或多个动作方法(Action Methods)
- 负责协调模型和视图(在API中是指响应数据)
以下是创建控制器的基本步骤:
- 在
Controllers文件夹中创建新文件(如TodoItemsController.cs) - 添加以下代码:
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;
// 通过依赖注入获取数据上下文
public TodoItemsController(TodoContext context)
{
_context = context;
}
// 动作方法将在后续添加
}
}关键组件:
- [Route("api/[controller]")]:定义路由模板,
[controller]会被替换为控制器名称(不含"Controller"后缀) - [ApiController]:启用API特定行为
- ControllerBase:包含API控制器所需的基本功能
- 依赖注入:通过构造函数注入
TodoContext
动作方法(Action Methods)是控制器中处理特定HTTP请求的公共方法。它们:
- 映射到特定的HTTP方法(GET, POST, PUT, DELETE等)
- 返回数据或IActionResult派生类型
- 可以接收来自请求的参数
- 执行业务逻辑或调用服务
ASP.NET Core使用特性(Attributes)将HTTP动词映射到动作方法:
| HTTP动词特性 | 对应HTTP方法 | 典型用途 |
|---|---|---|
| [HttpGet] | GET | 获取资源 |
| [HttpPost] | POST | 创建资源 |
| [HttpPut] | PUT | 更新资源 |
| [HttpDelete] | DELETE | 删除资源 |
| [HttpPatch] | PATCH | 部分更新 |
例如,添加获取所有待办事项的GET方法:
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}以下是待办事项API的完整CRUD实现:
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
todoItem);
}// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}
_context.Entry(todoItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
private bool TodoItemExists(long id)
{
return _context.TodoItems.Any(e => e.Id == id);
}// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}路由是将HTTP请求映射到控制器动作方法的机制。它决定了哪个控制器和动作方法将处理特定的HTTP请求。ASP.NET Core的路由系统具有以下作用:
- URL与代码的解耦:URL不直接反映物理文件结构
- 可读性和SEO友好:创建简洁、描述性的URL
- 版本控制:支持API版本化
- 灵活性:允许自定义URL模式和参数
使用[Route]特性可以定义控制器和动作方法的路由模板:
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
// 路由: GET api/products
[HttpGet]
public IActionResult GetAll() { ... }
// 路由: GET api/products/5
[HttpGet("{id}")]
public IActionResult GetById(int id) { ... }
}在上面的例子中:
[controller]是一个令牌,会被控制器名称(不含"Controller"后缀)替换{id}是路由参数,会被URL中的值捕获并传递给方法参数
路由约束限制路由参数可以匹配的值:
// 仅匹配id为整数的路由
[HttpGet("{id:int}")]
public IActionResult GetById(int id) { ... }
// 仅匹配符合正则表达式的路由
[HttpGet("{sku:regex(^[a-zA-Z0-9]{6}$)}")]
public IActionResult GetBySku(string sku) { ... }常用的路由约束:
| 约束 | 示例 | 匹配 | 不匹配 |
|---|---|---|---|
| int | {id:int} | 123 | abc, 123.45 |
| bool | {flag:bool} | true, false | yes, 1 |
| datetime | {date:datetime} | 2023-07-15 | july 15 |
| decimal | {price:decimal} | 123.45 | abc |
| min(value) | {id:min(1)} | 1, 2, 3 | 0, -1 |
| max(value) | {id:max(10)} | 1, 10 | 11, 20 |
| minlength(value) | {name:minlength(2)} | ab, abc | a |
| regex(expression) | {sku:regex(^[a-z]{3}$)} | abc | ab, abcd |
模型绑定是ASP.NET Core的一个核心功能,它自动将HTTP请求数据(路由参数、查询字符串、表单数据、JSON等)映射到动作方法的参数或模型对象。模型绑定简化了从HTTP请求中提取数据的过程,让开发者能够直接使用强类型对象而非手动解析请求。
ASP.NET Core提供了特性来明确指定数据应该从哪个源绑定:
| 特性 | 数据源 | 示例 |
|---|---|---|
| [FromRoute] | 路由参数 | /api/products/{id} |
| [FromQuery] | 查询字符串 | /api/products?category=electronics |
| [FromBody] | 请求正文 | HTTP POST/PUT/PATCH的JSON或XML数据 |
| [FromForm] | 表单数据 | 多部分表单数据或x-www-form-urlencoded |
| [FromHeader] | HTTP头部 | Authorization: Bearer token |
| [FromServices] | 服务容器(依赖注入) | 注入的服务 |
ASP.NET Core提供了一组内置的验证特性,可用于验证模型:
| 特性 | 描述 | 示例 |
|---|---|---|
| [Required] | 属性不能为null | [Required] |
| [StringLength] | 字符串长度限制 | [StringLength(50, MinimumLength = 3)] |
| [Range] | 数值范围限制 | [Range(1, 100)] |
| [RegularExpression] | 正则表达式匹配 | [RegularExpression(@"^[A-Z]{2}\d{4}$")] |
| [EmailAddress] | 电子邮件格式 | [EmailAddress] |
| [Phone] | 电话号码格式 | [Phone] |
| [Url] | URL格式 | [Url] |
例如,为产品模型添加验证:
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
[Required]
[StringLength(500)]
public string Description { get; set; }
[Range(0.01, 10000)]
public decimal Price { get; set; }
[Range(0, 1000)]
public int StockQuantity { get; set; }
[Url]
public string ImageUrl { get; set; }
}依赖注入(Dependency Injection,简称DI)是一种设计模式,它允许对象及其依赖项之间的松散耦合。在依赖注入模式中,对象不直接创建其依赖项,而是从外部接收已创建的依赖项实例。
依赖注入的核心原则:
- 依赖反转原则:高层模块不应依赖低层模块,两者都应依赖抽象
- 控制反转:控制流的反转,对象的创建和生命周期由外部容器管理
- 显式依赖:类应明确声明其所有依赖项
ASP.NET Core DI容器支持三种主要的服务生命周期:
- 定义:每次请求服务时创建新实例
- 注册方式:
services.AddTransient<IService, Service>() - 适用场景:轻量级、无状态服务
- 定义:每个请求(HTTP请求)内共享同一实例
- 注册方式:
services.AddScoped<IService, Service>() - 适用场景:需要保持请求内状态的服务,如数据库上下文
- 定义:应用程序生命周期内只创建一个实例
- 注册方式:
services.AddSingleton<IService, Service>() - 适用场景:无状态服务、共享缓存、配置服务
使用选项模式的基本步骤:
- 创建选项类:
public class SmtpSettings
{
public const string SectionName = "Smtp";
public string Server { get; set; } = string.Empty;
public int Port { get; set; } = 25;
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool EnableSsl { get; set; } = true;
public string FromEmail { get; set; } = string.Empty;
public string FromName { get; set; } = string.Empty;
}- 配置选项:
// 在Program.cs中
builder.Services.Configure<SmtpSettings>(
builder.Configuration.GetSection(SmtpSettings.SectionName));- 在appsettings.json中添加配置:
{
"Smtp": {
"Server": "smtp.example.com",
"Port": 587,
"Username": "user@example.com",
"Password": "SecurePassword123",
"EnableSsl": true,
"FromEmail": "no-reply@example.com",
"FromName": "My Application"
}
}- 使用选项:
public class EmailService : IEmailService
{
private readonly SmtpSettings _smtpSettings;
public EmailService(IOptions<SmtpSettings> smtpOptions)
{
_smtpSettings = smtpOptions.Value;
}
// 使用配置进行邮件发送
}中间件是ASP.NET Core应用程序请求处理管道中的软件组件。每个中间件执行特定任务,如路由、认证、异常处理等,并决定是否将请求传递给管道中的下一个中间件。
中间件的关键特点:
- 可以处理传入的HTTP请求
- 可以处理传出的HTTP响应
- 可以选择是否将请求传递给下一个中间件
- 可以在下一个中间件前后执行逻辑
- 可以短路请求管道
ASP.NET Core提供了许多内置中间件组件:
var app = builder.Build();
// 配置HTTP请求管道
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation($"Processing request: {context.Request.Method} {context.Request.Path}");
await _next(context);
_logger.LogInformation($"Finished processing request: {context.Response.StatusCode}");
}
}
// 注册中间件
app.UseMiddleware<RequestLoggingMiddleware>();Entity Framework Core (EF Core) 是一个现代的对象关系映射(ORM)框架,允许.NET开发者使用.NET对象处理数据库。它支持多种数据库提供程序,包括SQL Server、PostgreSQL、SQLite、MySQL等。
- DbContext:表示数据库会话,负责查询和保存数据
- 实体(Entities):表示数据库表的.NET类
- DbSet:表示数据库中特定类型的实体集合
- 迁移(Migrations):用于创建和修改数据库架构的方式
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Toolspublic class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public DateTime CreatedDate { get; set; }
}
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TodoItem>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.CreatedDate).HasDefaultValueSql("GETDATE()");
});
}
}// 在Program.cs中
builder.Services.AddDbContext<TodoContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));# 创建初始迁移
dotnet ef migrations add InitialCreate
# 更新数据库
dotnet ef database update
# 添加新迁移
dotnet ef migrations add AddCreatedDate
# 回滚迁移
dotnet ef database update PreviousMigrationNamepublic interface ITodoRepository
{
Task<IEnumerable<TodoItem>> GetAllAsync();
Task<TodoItem> GetByIdAsync(long id);
Task<TodoItem> AddAsync(TodoItem todoItem);
Task UpdateAsync(TodoItem todoItem);
Task DeleteAsync(long id);
}
public class TodoRepository : ITodoRepository
{
private readonly TodoContext _context;
public TodoRepository(TodoContext context)
{
_context = context;
}
public async Task<IEnumerable<TodoItem>> GetAllAsync()
{
return await _context.TodoItems.ToListAsync();
}
public async Task<TodoItem> GetByIdAsync(long id)
{
return await _context.TodoItems.FindAsync(id);
}
public async Task<TodoItem> AddAsync(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return todoItem;
}
public async Task UpdateAsync(TodoItem todoItem)
{
_context.Entry(todoItem).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem != null)
{
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
}
}
}- 身份验证(Authentication):验证用户是谁
- 授权(Authorization):确定用户可以做什么
JWT (JSON Web Token) 是一种无状态的身份验证方法,适用于API。
// 安装包
// dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
// 配置服务
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
builder.Configuration["JWT:Secret"])),
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JWT:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["JWT:Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
// 添加授权
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("UserOnly", policy => policy.RequireRole("User"));
});
// 启用中间件
app.UseAuthentication();
app.UseAuthorization();public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
public string Role { get; set; } = "User";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
public class LoginRequest
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
public class RegisterRequest
{
[Required]
public string Username { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[MinLength(6)]
public string Password { get; set; }
}[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IUserService _userService;
private readonly ITokenService _tokenService;
public AuthController(IUserService userService, ITokenService tokenService)
{
_userService = userService;
_tokenService = tokenService;
}
[HttpPost("register")]
public async Task<IActionResult> Register(RegisterRequest request)
{
if (await _userService.ExistsAsync(request.Username))
{
return BadRequest("用户名已存在");
}
var user = await _userService.CreateAsync(request);
var token = _tokenService.GenerateToken(user);
return Ok(new { Token = token, User = new { user.Id, user.Username, user.Email } });
}
[HttpPost("login")]
public async Task<IActionResult> Login(LoginRequest request)
{
var user = await _userService.ValidateAsync(request.Username, request.Password);
if (user == null)
{
return Unauthorized("用户名或密码错误");
}
var token = _tokenService.GenerateToken(user);
return Ok(new { Token = token, User = new { user.Id, user.Username, user.Email } });
}
}[ApiController]
[Route("api/[controller]")]
[Authorize] // 所有方法都需要认证
public class TodoItemsController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetTodos()
{
// 需要认证的用户才能访问
}
[HttpPost]
[Authorize(Roles = "Admin")] // 仅管理员可以创建
public async Task<IActionResult> CreateTodo(TodoItem item)
{
// 仅管理员可以访问
}
[HttpGet("public")]
[AllowAnonymous] // 允许匿名访问
public async Task<IActionResult> GetPublicTodos()
{
// 任何人都可以访问
}
}public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unexpected error occurred");
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
var response = context.Response;
var errorResponse = new ErrorResponse();
switch (exception)
{
case NotFoundException:
response.StatusCode = 404;
errorResponse.Message = exception.Message;
break;
case ValidationException:
response.StatusCode = 400;
errorResponse.Message = exception.Message;
break;
case UnauthorizedAccessException:
response.StatusCode = 401;
errorResponse.Message = "Unauthorized";
break;
default:
response.StatusCode = 500;
errorResponse.Message = "An unexpected error occurred";
break;
}
var jsonResponse = JsonSerializer.Serialize(errorResponse);
await context.Response.WriteAsync(jsonResponse);
}
}
public class ErrorResponse
{
public string Message { get; set; }
public string Detail { get; set; }
}// 安装包
// dotnet add package Serilog.AspNetCore
// dotnet add package Serilog.Sinks.Console
// dotnet add package Serilog.Sinks.File
// 在Program.cs中配置
using Serilog;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();
// 其他配置...
var app = builder.Build();
app.UseSerilogRequestLogging();
// 其他中间件...
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly ITodoRepository _repository;
private readonly ILogger<TodoItemsController> _logger;
public TodoItemsController(ITodoRepository repository, ILogger<TodoItemsController> logger)
{
_repository = repository;
_logger = logger;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
_logger.LogInformation("Getting all todo items");
try
{
var items = await _repository.GetAllAsync();
_logger.LogInformation("Successfully retrieved {Count} todo items", items.Count());
return Ok(items);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while getting todo items");
throw;
}
}
[HttpPost]
public async Task<ActionResult<TodoItem>> CreateTodoItem(TodoItem item)
{
_logger.LogInformation("Creating new todo item: {Name}", item.Name);
try
{
var createdItem = await _repository.AddAsync(item);
_logger.LogInformation("Successfully created todo item with ID: {Id}", createdItem.Id);
return CreatedAtAction(nameof(GetTodoItem),
new { id = createdItem.Id }, createdItem);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while creating todo item: {Name}", item.Name);
throw;
}
}
}Swagger(现在称为OpenAPI)是一套用于描述REST API的规范。它提供了一种标准化的方式来记录API,包括端点、请求/响应模型、认证方法等。
// 在Program.cs中
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Todo API",
Version = "v1",
Description = "一个简单的待办事项API",
Contact = new OpenApiContact
{
Name = "Your Name",
Email = "your.email@example.com",
Url = new Uri("https://yourwebsite.com")
}
});
// 包含XML注释
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// JWT认证配置
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
// 启用Swagger中间件
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Todo API V1");
c.RoutePrefix = string.Empty; // 设置Swagger UI在应用根目录
});
}首先在项目文件中启用XML文档生成:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
</Project>然后在控制器方法上添加XML注释:
/// <summary>
/// 获取所有待办事项
/// </summary>
/// <returns>待办事项列表</returns>
/// <response code="200">成功返回待办事项列表</response>
/// <response code="500">内部服务器错误</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<TodoItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _repository.GetAllAsync();
}
/// <summary>
/// 根据ID获取特定的待办事项
/// </summary>
/// <param name="id">待办事项的唯一标识符</param>
/// <returns>指定的待办事项</returns>
/// <response code="200">成功返回待办事项</response>
/// <response code="404">未找到指定的待办事项</response>
[HttpGet("{id}")]
[ProducesResponseType(typeof(TodoItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _repository.GetByIdAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
/// <summary>
/// 创建新的待办事项
/// </summary>
/// <param name="todoItem">要创建的待办事项</param>
/// <returns>创建的待办事项</returns>
/// <response code="201">成功创建待办事项</response>
/// <response code="400">请求数据无效</response>
[HttpPost]
[ProducesResponseType(typeof(TodoItem), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<TodoItem>> CreateTodoItem(TodoItem todoItem)
{
var createdItem = await _repository.AddAsync(todoItem);
return CreatedAtAction(nameof(GetTodoItem),
new { id = createdItem.Id }, createdItem);
}# 创建测试项目
dotnet new xunit -n TodoApi.Tests
# 添加引用
cd TodoApi.Tests
dotnet add reference ../TodoApi/TodoApi.csproj
# 添加测试包
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Moqpublic class TodoItemsControllerTests
{
private readonly Mock<ITodoRepository> _mockRepository;
private readonly Mock<ILogger<TodoItemsController>> _mockLogger;
private readonly TodoItemsController _controller;
public TodoItemsControllerTests()
{
_mockRepository = new Mock<ITodoRepository>();
_mockLogger = new Mock<ILogger<TodoItemsController>>();
_controller = new TodoItemsController(_mockRepository.Object, _mockLogger.Object);
}
[Fact]
public async Task GetTodoItems_ReturnsOkResult_WithListOfTodoItems()
{
// Arrange
var todoItems = new List<TodoItem>
{
new TodoItem { Id = 1, Name = "Test Item 1", IsComplete = false },
new TodoItem { Id = 2, Name = "Test Item 2", IsComplete = true }
};
_mockRepository.Setup(repo => repo.GetAllAsync()).ReturnsAsync(todoItems);
// Act
var result = await _controller.GetTodoItems();
// Assert
var okResult = Assert.IsType<ActionResult<IEnumerable<TodoItem>>>(result);
var returnValue = Assert.IsType<List<TodoItem>>(okResult.Value);
Assert.Equal(2, returnValue.Count);
}
[Fact]
public async Task GetTodoItem_ReturnsNotFound_WhenItemDoesNotExist()
{
// Arrange
_mockRepository.Setup(repo => repo.GetByIdAsync(1)).ReturnsAsync((TodoItem)null);
// Act
var result = await _controller.GetTodoItem(1);
// Assert
Assert.IsType<NotFoundResult>(result.Result);
}
[Fact]
public async Task CreateTodoItem_ReturnsCreatedAtAction_WithCreatedItem()
{
// Arrange
var newItem = new TodoItem { Name = "New Item", IsComplete = false };
var createdItem = new TodoItem { Id = 1, Name = "New Item", IsComplete = false };
_mockRepository.Setup(repo => repo.AddAsync(newItem)).ReturnsAsync(createdItem);
// Act
var result = await _controller.CreateTodoItem(newItem);
// Assert
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(result.Result);
Assert.Equal("GetTodoItem", createdAtActionResult.ActionName);
Assert.Equal(createdItem, createdAtActionResult.Value);
}
}public class TodoApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public TodoApiIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
_client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 移除真实的数据库上下文
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<TodoContext>));
if (descriptor != null)
services.Remove(descriptor);
// 添加内存数据库
services.AddDbContext<TodoContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
});
}).CreateClient();
}
[Fact]
public async Task Get_TodoItems_ReturnsSuccessStatusCode()
{
// Act
var response = await _client.GetAsync("/api/todoitems");
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal("application/json; charset=utf-8",
response.Content.Headers.ContentType?.ToString());
}
[Fact]
public async Task Post_TodoItem_ReturnsCreatedStatusCode()
{
// Arrange
var newItem = new { Name = "Test Item", IsComplete = false };
var json = JsonSerializer.Serialize(newItem);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/api/todoitems", content);
// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
}
}# 安装HTTP REPL
dotnet tool install -g Microsoft.dotnet-httprepl
# 连接到API
httprepl https://localhost:7xxx
# 浏览API
ls
# 导航到端点
cd api/todoitems
# 发送GET请求
get
# 发送POST请求
post -c "{"name": "Test Item", "isComplete": false}"创建Postman集合来测试API:
{
"info": {
"name": "Todo API",
"description": "API测试集合"
},
"item": [
{
"name": "Get All Todos",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/todoitems"
}
},
{
"name": "Create Todo",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/todoitems",
"body": {
"mode": "raw",
"raw": "{\"name\": \"Test Item\", \"isComplete\": false}",
"options": {
"raw": {
"language": "json"
}
}
}
}
}
],
"variable": [
{
"key": "baseUrl",
"value": "https://localhost:7xxx"
}
]
}在不同环境中使用不同的配置文件:
appsettings.json:基础配置appsettings.Development.json:开发环境配置appsettings.Production.json:生产环境配置
// appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=prod-server;Database=TodoDB;Trusted_Connection=true;"
},
"JWT": {
"Secret": "your-super-secret-key-for-production",
"Issuer": "https://yourdomain.com",
"Audience": "https://yourdomain.com"
}
}// 添加健康检查
builder.Services.AddHealthChecks()
.AddDbContext<TodoContext>()
.AddCheck("Database", () =>
{
// 自定义数据库健康检查
return HealthCheckResult.Healthy();
});
// 配置健康检查端点
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});// 添加响应缓存
builder.Services.AddResponseCaching();
// 使用响应缓存中间件
app.UseResponseCaching();
// 在控制器中设置缓存
[HttpGet]
[ResponseCache(Duration = 300)] // 缓存5分钟
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _repository.GetAllAsync();
}// 使用异步方法
public async Task<IEnumerable<TodoItem>> GetCompletedItemsAsync()
{
return await _context.TodoItems
.Where(t => t.IsComplete)
.AsNoTracking() // 只读查询时禁用跟踪
.ToListAsync();
}
// 分页查询
public async Task<PagedResult<TodoItem>> GetPagedItemsAsync(int page, int pageSize)
{
var totalCount = await _context.TodoItems.CountAsync();
var items = await _context.TodoItems
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return new PagedResult<TodoItem>
{
Items = items,
TotalCount = totalCount,
Page = page,
PageSize = pageSize
};
}# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
steps:
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build project'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Publish application'
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
displayName: 'Publish artifacts'
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: 'drop'# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["TodoApi/TodoApi.csproj", "TodoApi/"]
RUN dotnet restore "TodoApi/TodoApi.csproj"
COPY . .
WORKDIR "/src/TodoApi"
RUN dotnet build "TodoApi.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "TodoApi.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TodoApi.dll"]# docker-compose.yml
version: '3.8'
services:
todoapi:
build: .
ports:
- "8080:80"
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Server=db;Database=TodoDB;User=sa;Password=YourPassword123;
depends_on:
- db
db:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=YourPassword123
ports:
- "1433:1433"
volumes:
- sqldata:/var/opt/mssql
volumes:
sqldata:// 添加Application Insights
builder.Services.AddApplicationInsightsTelemetry();
// 自定义遥测
public class TodoItemsController : ControllerBase
{
private readonly TelemetryClient _telemetryClient;
public TodoItemsController(TelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}
[HttpPost]
public async Task<ActionResult<TodoItem>> CreateTodoItem(TodoItem item)
{
_telemetryClient.TrackEvent("TodoItemCreated",
new Dictionary<string, string> { { "ItemName", item.Name } });
// 业务逻辑...
}
}// 自定义性能指标
public class MetricsService
{
private readonly IMetrics _metrics;
private readonly Counter<int> _todoItemsCreated;
public MetricsService(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("TodoApi");
_todoItemsCreated = meter.CreateCounter<int>("todo_items_created");
}
public void IncrementTodoItemsCreated()
{
_todoItemsCreated.Add(1);
}
}通过本手册的学习,您应该能够:
- 理解.NET Web API的基本概念和应用场景
- 搭建完整的开发环境并创建第一个Web API项目
- 掌握控制器和动作方法的开发技巧
- 设计RESTful的路由系统
- 实现模型绑定和数据验证
- 应用依赖注入和服务配置最佳实践
- 创建和使用中间件构建请求管道
- 集成Entity Framework Core进行数据访问
- 实现身份验证和授权机制
- 处理异常和记录日志
- 生成API文档并使用Swagger
- 编写单元测试和集成测试
- 部署应用程序到生产环境并进行性能优化
- 深入学习架构模式:研究Clean Architecture、DDD、CQRS等高级架构模式
- 微服务开发:了解如何将单体应用拆分为微服务
- API网关:学习使用Ocelot、YARP等API网关技术
- 消息队列:集成RabbitMQ、Azure Service Bus等消息系统
- 缓存策略:深入学习Redis、分布式缓存等技术
- 容器化:掌握Docker和Kubernetes的使用
- 云原生开发:学习云平台特定的服务和最佳实践
- 遵循REST原则设计API
- 使用异步编程提高性能
- 实施适当的错误处理和日志记录
- 编写全面的测试确保代码质量
- 应用安全最佳实践保护API
- 使用版本控制管理API变更
- 监控和度量应用程序性能
- 持续学习保持技术更新