2. 第一个ASP.NET Core微服务
上一节我们明确了微服务的边界与拆分思路,这一节要把概念落到代码上:用 ASP.NET Core Web API 模板创建一个真正能跑起来的最小服务。所谓“最小可运行”,指的是它具备一个微服务最基本的骨架——独立进程、独立端口、可被 HTTP 调用的接口、可观测的接口文档,以及可配置的启动行为。我们以“商品目录服务(Catalog Service)”为例,它只负责商品信息的查询与新增,边界清晰,适合作为第一个微服务。
创建项目与理解项目结构
在命令行中执行以下命令即可生成一个 Web API 项目:
dotnet new webapi -n Catalog.Service -o src/Catalog.Service
cd src/Catalog.Service
dotnet run
模板生成的项目结构并不复杂,但每一部分都对应微服务的运行要素。Program.cs 是应用入口,负责构建主机、注册服务、配置中间件与路由;appsettings.json 与 appsettings.Development.json 承载配置,前者是基础配置,后者在开发环境下覆盖;Properties/launchSettings.json 定义本地启动配置,包括应用 URL、环境变量与启动配置文件;Controllers 目录存放控制器,是路由与业务入口的落点。理解这套结构的关键在于:ASP.NET Core 采用“主机 + 中间件管道 + 依赖注入”的统一模型,微服务只是这个模型的一个独立实例。
需要特别说明的是,模板默认可能生成最小 API(在 Program.cs 中直接写 app.MapGet),也可能生成控制器风格。本课程统一采用控制器风格,因为它更利于按资源组织接口,也便于后续引入过滤器、模型验证与版本控制。若模板生成的是最小 API,可手动添加 Controllers 目录并注册控制器服务。
启动配置:端口、环境与配置文件
微服务必须拥有独立且稳定的监听地址。launchSettings.json 中的 applicationUrl 决定本地调试端口,例如设置为 http://localhost:5101。生产环境则通过环境变量 ASPNETCORE_URLS 或反向代理统一入口。环境变量 ASPNETCORE_ENVIRONMENT 控制加载哪个配置文件,开发环境为 Development,生产为 Production。这种“配置分层 + 环境覆盖”的机制,使同一个镜像可以在不同环境以不同参数运行,是微服务可移植性的基础。
在 Program.cs 中,服务注册与中间件顺序决定了请求如何被处理。典型的最小配置如下:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
这里有几个关键点:AddControllers 注册控制器相关服务;AddEndpointsApiExplorer 与 AddSwaggerGen 为接口文档提供元数据;UseSwagger 与 UseSwaggerUI 仅在开发环境启用,避免生产环境暴露接口细节;MapControllers 将控制器动作映射为路由端点。中间件顺序不能随意调整,例如授权必须在路由之前,否则无法正确拦截请求。
路由与控制器:把资源暴露为 HTTP 接口
控制器是路由的载体。我们创建一个 ProductsController,用特性路由声明资源路径:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private static readonly List<Product> _products = new()
{
new Product { Id = 1, Name = "机械键盘", Price = 399.00m },
new Product { Id = 2, Name = "无线鼠标", Price = 129.00m }
};
[HttpGet]
public ActionResult<IEnumerable<Product>> GetAll() => _products;
[HttpGet("{id:int}")]
public ActionResult<Product> GetById(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
return product is null ? NotFound() : product;
}
[HttpPost]
public ActionResult<Product> Create(Product product)
{
product.Id = _products.Max(p => p.Id) + 1;
_products.Add(product);
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
}
这段代码体现了 REST 风格的核心约定:GET /api/products 返回集合,GET /api/products/1 返回单个资源,POST /api/products 创建资源并返回 201 与 Location 头。[ApiController] 启用自动模型验证与参数绑定推断,[Route("api/[controller]")] 中的 [controller] 会被替换为控制器名去掉 Controller 后缀,即 products。路由参数 {id:int} 中的约束确保只有整数才能匹配,避免无效请求进入动作方法。
这里使用静态列表模拟数据,是为了让第一个服务不依赖数据库即可运行。真实微服务中,这一层会替换为仓储或数据库访问,但接口契约与路由结构保持不变,这正是微服务“边界稳定、实现可替换”的体现。
Swagger 集成:让接口可发现、可调试
Swagger(OpenAPI)为微服务提供了自描述能力。启动服务后访问 http://localhost:5101/swagger,可以看到自动生成的接口列表、请求参数、响应模型与状态码。点击 “Try it out” 可以直接发起请求,验证 GET /api/products 是否返回两条商品数据,以及 POST 是否返回 201。对于微服务架构,Swagger 不仅是调试工具,更是服务契约的可视化表达,后续网关聚合、客户端生成都依赖这份 OpenAPI 文档。
最小服务的运行闭环
把上述内容串起来,一个最小微服务的运行闭环是:dotnet run 启动独立进程,Kestrel 监听 5101 端口;请求进入中间件管道,经过 HTTPS 重定向与授权后到达路由;路由根据 api/products 匹配到控制器动作;动作返回数据或状态码;Swagger 从元数据生成文档供调试。判断服务是否达标,可以检查三点:进程是否独立监听端口、/swagger 是否可访问、GET /api/products 是否返回预期 JSON。
完成这一节后,你已经拥有一个可运行、可调试、可扩展的微服务雏形。它的价值不在于功能多少,而在于结构正确:独立部署单元、清晰的资源路由、可发现的接口契约。后续引入数据库、服务注册、网关与容器化时,都是在这个骨架上叠加能力,而不是推倒重来。