
FastAPI是什么?快速上手指南
在上一节《GET 请求全攻略》中,我们为 Pokemon 资源构建了完整的 GET 接口。作为系列的第二篇,本教程将继续深入,面向 Category 与 Country 两种新模型,快速搭建四类常见的 GET Endpoint,并配合 Repository 模式、DTO 限制、AutoMapper 映射和 Swagger 测试,全方位提升你的 API 设计水平。
Program.cs
中注入 DbContext
、AutoMapper、各资源的仓储(AddScoped)项目结构:
/Interfaces
IPokemonRepository.cs
ICategoryRepository.cs
ICountryRepository.cs
…
/Repositories
PokemonRepository.cs
CategoryRepository.cs
CountryRepository.cs
…
/DTOs
PokemonDto.cs
CategoryDto.cs
CountryDto.cs
…
/Helpers
MappingProfiles.cs
/Controllers
PokemonController.cs
CategoryController.cs
CountryController.cs
/Data
AppDbContext.cs
在 Interfaces/ICategoryRepository.cs
,定义三种查询方法:
public interface ICategoryRepository
{
IReadOnlyCollection < Category > GetCategories(); // 列表
Category GetCategory(int id); // 详情
IReadOnlyCollection < Pokemon > GetPokemonsByCategory(int id); // 关联查询
bool CategoryExists(int id); // 存在性校验
}
在 Repositories/CategoryRepository.cs
:
public class CategoryRepository : ICategoryRepository
{
private readonly AppDbContext _context;
public CategoryRepository(AppDbContext context) = > _context = context;
public IReadOnlyCollection < Category > GetCategories() = >
_context.Categories.ToList();
public Category GetCategory(int id) = >
_context.Categories.FirstOrDefault(c = > c.Id == id);
public IReadOnlyCollection < Pokemon > GetPokemonsByCategory(int id) = >
_context.PokemonCategories
.Where(pc = > pc.Category.Id == id)
.Select(pc = > pc.Pokemon)
.ToList();
public bool CategoryExists(int id) = >
_context.Categories.Any(c = > c.Id == id);
}
在 DTOs/CategoryDto.cs
:
public class CategoryDto
{
public int Id { get; set; }
public string Name { get; set; }
}
在 Helpers/MappingProfiles.cs
中添加映射:
CreateMap < Category, CategoryDto > ();
CreateMap < Pokemon, PokemonDto > (); // 关联查询时使用
在 Controllers/CategoryController.cs
中注入仓储与映射器,并实现三条 GET:
[ApiController]
[Route("api/[controller]")]
public class CategoryController : ControllerBase
{
private readonly ICategoryRepository _repo;
private readonly IMapper _mapper;
public CategoryController(ICategoryRepository repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
// GET api/category
[HttpGet]
public ActionResult < IReadOnlyCollection < CategoryDto > > GetCategories()
{
var categories = _repo.GetCategories();
return Ok(_mapper.Map < IReadOnlyCollection < CategoryDto > > (categories));
}
// GET api/category/{id}
[HttpGet("{id}")]
public ActionResult < CategoryDto > GetCategory(int id)
{
if (!_repo.CategoryExists(id))
return NotFound($"Category {id} 未找到。");
var category = _repo.GetCategory(id);
return Ok(_mapper.Map < CategoryDto > (category));
}
// GET api/category/{id}/pokemons
[HttpGet("{id}/pokemons")]
public ActionResult < IReadOnlyCollection < PokemonDto > > GetPokemonsByCategory(int id)
{
if (!_repo.CategoryExists(id))
return NotFound($"Category {id} 未找到。");
var pokemons = _repo.GetPokemonsByCategory(id);
return Ok(_mapper.Map < IReadOnlyCollection < PokemonDto > > (pokemons));
}
}
在 Interfaces/ICountryRepository.cs
:
public interface ICountryRepository
{
IReadOnlyCollection < Country > GetCountries(); // 列表
Country GetCountry(int id); // 详情
IReadOnlyCollection < Owner > GetOwnersByCountry(int id); // 关联查询
bool CountryExists(int id); // 存在性校验
}
在 Repositories/CountryRepository.cs
:
public class CountryRepository : ICountryRepository
{
private readonly AppDbContext _context;
public CountryRepository(AppDbContext context) = > _context = context;
public IReadOnlyCollection < Country > GetCountries() = >
_context.Countries.ToList();
public Country GetCountry(int id) = >
_context.Countries.FirstOrDefault(c = > c.Id == id);
public IReadOnlyCollection < Owner > GetOwnersByCountry(int id) = >
_context.Owners
.Where(o = > o.Country.Id == id)
.ToList();
public bool CountryExists(int id) = >
_context.Countries.Any(c = > c.Id == id);
}
在 DTOs/CountryDto.cs
:
public class CountryDto
{
public int Id { get; set; }
public string Name { get; set; }
}
MappingProfiles
中添加:
CreateMap < Country, CountryDto > ();
CreateMap < Owner, OwnerDto > (); // 后续 Owner GET 关联时使用
[ApiController]
[Route("api/[controller]")]
public class CountryController : ControllerBase
{
private readonly ICountryRepository _repo;
private readonly IMapper _mapper;
public CountryController(ICountryRepository repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
// GET api/country
[HttpGet]
public ActionResult < IReadOnlyCollection<CountryDto > > GetCountries() = >
Ok(_mapper.Map < IReadOnlyCollection<CountryDto > > (_repo.GetCountries()));
// GET api/country/{id}
[HttpGet("{id}")]
public ActionResult < CountryDto > GetCountry(int id)
{
if (!_repo.CountryExists(id))
return NotFound($"Country {id} 未找到。");
return Ok(_mapper.Map < CountryDto > (_repo.GetCountry(id)));
}
// GET api/country/{id}/owners
[HttpGet("{id}/owners")]
public ActionResult < IReadOnlyCollection < OwnerDto > > GetOwnersByCountry(int id)
{
if (!_repo.CountryExists(id))
return NotFound($"Country {id} 未找到。");
var owners = _repo.GetOwnersByCountry(id);
return Ok(_mapper.Map < IReadOnlyCollection < OwnerDto > > (owners));
}
}
/swagger
)。Category 测试:
GET /api/category
→ 全部分类GET /api/category/1
→ 单个分类详情GET /api/category/1/pokemons
→ 该分类下所有 PokemonCountry 测试:
GET /api/country
→ 全部国家GET /api/country/2
→ 单个国家详情GET /api/country/2/owners
→ 该国家下所有 Owner确认返回状态码与 DTO 格式符合预期,确保数据准确且不泄露多余字段。
IRepository
接口,再实现,保证松耦合与可测性。Select
或 Include
,避免默认未加载导致 Null。/resource
、详情 /resource/{id}
、子资源 /resource/{id}/sub}
。通过本篇示例,你已掌握为多种模型批量构建 GET 接口的完整流程,进一步夯实了 API 开发实战能力。下一篇,我们将继续完成剩余 Controller,并切入 POST/PUT/DELETE 操作,敬请期待!
原文引自YouTube视频:https://www.youtube.com/watch?v=bSvYErXVRtQ