如果你刚接触ASP.NET Core开发,可能会好奇为什么要使用Web API。简单来说,Web API就像餐厅的服务员 - 它负责在前端(顾客)和后端(厨房)之间传递请求和响应。在现代应用开发中,这种前后端分离的架构已经成为主流。
我刚开始做项目时,经常把业务逻辑和界面混在一起,结果每次改需求都像拆炸弹一样小心翼翼。后来用了Web API,前端可以随便换(网页、手机App、小程序),后端逻辑完全不受影响。这种灵活性在团队协作时尤其重要,前端和后端开发可以完全并行工作。
举个例子,我们团队最近开发的一个电商系统,Web API负责处理商品数据,而三个不同的前端团队分别开发了官网、移动App和微信小程序,都调用同一套API。如果没有Web API,这种多端适配的工作量会大得可怕。
我更喜欢用.NET CLI创建项目,因为更快捷,而且适合自动化部署。打开终端(Windows的CMD/PowerShell,Mac/Linux的Terminal),按顺序执行以下命令:
bash复制# 创建项目文件夹并进入
mkdir MyFirstApi
cd MyFirstApi
# 创建Web API项目
dotnet new webapi
# 添加必要包(比如Swagger用于API文档)
dotnet add package Swashbuckle.AspNetCore
# 运行项目
dotnet run
第一次运行时可能会提示信任HTTPS证书,按Y确认即可。项目启动后,默认会监听5000和5001端口。试试在浏览器打开https://localhost:5001/weatherforecast,你应该能看到一个返回JSON数据的示例API。
注意:如果遇到端口冲突,可以在Properties/launchSettings.json中修改应用端口号。
如果你习惯使用Visual Studio,创建过程更直观:
创建完成后,直接按F5运行。VS会自动打开Swagger页面,你可以在这里测试默认的WeatherForecast API。
ASP.NET Core提供了两种路由配置方式,我在实际项目中都经常用到:
特性路由(Attribute Routing):直接在Controller和Action上使用[Route]特性标注。这种方式更直观,适合RESTful风格的API。
csharp复制[ApiController]
[Route("api/products")] // 控制器级别路由
public class ProductsController : ControllerBase
{
[HttpGet("{id}")] // 方法级别路由
public IActionResult GetProduct(int id)
{
// 实现代码
}
}
传统路由(Conventional Routing):在Program.cs中统一配置。这种方式适合有固定路由规则的项目。
csharp复制app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
我个人的经验法则是:新项目优先使用特性路由,因为它更灵活;维护老项目时可能需要兼容传统路由。
经过多个项目的实践,我总结了一些实用的路由技巧:
路由约束:可以限制参数类型
csharp复制[HttpGet("{id:int}")] // 只匹配整数id
public IActionResult GetById(int id)
路由版本控制:通过路由实现API版本管理
csharp复制[Route("api/v1/[controller]")]
自定义路由参数转换器:比如自动将路由中的slug转换为ID
csharp复制[HttpGet("{slug}")]
public IActionResult GetBySlug([SlugToId] int id)
路由命名:方便生成URL
csharp复制[HttpGet("{id}", Name = "GetProduct")]
踩过的坑:曾经因为路由顺序问题调试了半天,后来发现ASP.NET Core会按照以下优先级匹配路由:
在Controllers文件夹右键添加->新建项->API控制器类。我建议的控制器基础结构如下:
csharp复制[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
// 依赖注入
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}
}
几个关键点:
ControllerBase而不是Controller(后者包含视图支持)[ApiController]特性启用API特定行为async/await模式下面是一个完整的商品API示例:
csharp复制[HttpPost]
public async Task<IActionResult> Create([FromBody] ProductCreateDto dto)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var product = await _productService.CreateAsync(dto);
return CreatedAtAction(nameof(GetById),
new { id = product.Id }, product);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
return NotFound();
return Ok(product);
}
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, [FromBody] ProductUpdateDto dto)
{
if (id != dto.Id)
return BadRequest("ID不匹配");
var result = await _productService.UpdateAsync(dto);
if (!result)
return NotFound();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var result = await _productService.DeleteAsync(id);
if (!result)
return NotFound();
return NoContent();
}
每个方法的最佳实践:
创建项目时如果启用了OpenAPI支持,就已经集成了Swagger。访问/swagger端点可以看到所有API的交互式文档。我习惯在开发过程中始终保持Swagger页面打开,方便测试。
如果需要更详细的Swagger配置:
csharp复制builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo {
Title = "My API",
Version = "v1",
Description = "我的第一个Web API项目"
});
// 添加XML注释
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
记得在项目属性中勾选"生成XML文档文件"。
虽然Swagger很方便,但Postman更适合复杂的测试场景。我通常会创建这些测试集合:
一个专业的技巧:使用Postman的环境变量和测试脚本自动化测试流程,比如先获取Token再测试需要认证的API。
在开发阶段,我推荐这样配置日志:
csharp复制builder.Logging.AddConsole()
.AddDebug()
.SetMinimumLevel(LogLevel.Debug);
然后在控制器中注入ILogger:
csharp复制private readonly ILogger<ProductsController> _logger;
public ProductsController(..., ILogger<ProductsController> logger)
{
_logger = logger;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
_logger.LogDebug("正在获取商品 {ProductId}", id);
// ...
}
遇到奇怪的问题时,我通常会按这个顺序排查:
经过多个项目的迭代,我发现这样的结构最合理:
code复制MyApi/
├── Controllers/ # API端点
├── Services/ # 业务逻辑
│ ├── Interfaces/ # 服务接口
│ └── Implementations/ # 服务实现
├── Models/ # 数据模型
│ ├── Entities/ # 数据库实体
│ └── DTOs/ # 数据传输对象
├── Data/ # 数据访问
├── Middlewares/ # 自定义中间件
├── Extensions/ # 扩展方法
└── Program.cs # 入口文件
csharp复制app.UseExceptionHandler(a => a.Run(async context =>
{
var exceptionHandler = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandler.Error;
await context.Response.WriteAsJsonAsync(new {
error = exception.Message
});
}));
[ApiController]的自动验证csharp复制[HttpPost]
public IActionResult Create(ProductCreateDto dto)
{
// 不需要手动检查ModelState.IsValid
// 因为[ApiController]会自动返回400错误
}
csharp复制// 错误示范
public IActionResult Get()
{
var products = _productService.GetAll(); // 同步调用
return Ok(products);
}
// 正确示范
public async Task<IActionResult> Get()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}
csharp复制// 错误示范 - 会查询整个表
var products = await _context.Products.ToListAsync();
return products.Where(p => p.Price > 100);
// 正确示范 - 在数据库层面过滤
var products = await _context.Products
.Where(p => p.Price > 100)
.ToListAsync();
我习惯使用这样的appsettings结构:
json复制{
"ConnectionStrings": {
"Default": "Server=.;Database=MyApi;Trusted_Connection=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"JwtSettings": {
"Secret": "your-secret-key",
"ExpiryMinutes": 60
}
}
然后通过环境变量覆盖生产环境配置:
bash复制# Linux/macOS
export ConnectionStrings__Default="Server=prod-db;Database=MyApi;User=api;Password=123456"
# Windows
setx ConnectionStrings__Default "Server=prod-db;Database=MyApi;User=api;Password=123456"
bash复制dotnet publish -c Release -o ./publish
在IIS中:
常见问题解决:
创建Dockerfile:
dockerfile复制FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["MyApi.csproj", "."]
RUN dotnet restore "MyApi.csproj"
COPY . .
RUN dotnet build "MyApi.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "MyApi.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
构建并运行:
bash复制docker build -t myapi .
docker run -d -p 8080:80 --name myapi-container myapi