所有文章 > API开发 > ASP.NET Core Web API GET 请求进阶(三)|Owner、Review、Reviewer 接口实战
ASP.NET Core Web API GET 请求进阶(三)|Owner、Review、Reviewer 接口实战

ASP.NET Core Web API GET 请求进阶(三)|Owner、Review、Reviewer 接口实战

1. 简介

在本系列的前两篇文章中,我们已经为 PokemonCategoryCountry 资源构建了丰富的 GET 接口。本文将继续介绍如何为 OwnerReviewReviewer 三个模型设计和实现多样的 GET 请求,涵盖列表查询、关联查询、存在性校验等常见场景,并额外讲解如何解决二对二/多对多关联导致的 JSON 循环引用问题。


2. 环境与项目结构回顾

  • 技术栈:.NET 6+、C#、Entity Framework Core、AutoMapper
  • 依赖注入:在 Program.cs 中已注册 DbContext、AutoMapper 及各仓储 AddScoped < …, … >()
  • 项目结构

    /Interfaces
    IOwnerRepository.cs
    IReviewRepository.cs
    IReviewerRepository.cs

    /Repositories
    OwnerRepository.cs
    ReviewRepository.cs
    ReviewerRepository.cs

    /DTOs
    OwnerDto.cs
    ReviewDto.cs
    ReviewerDto.cs
    /Helpers
    MappingProfiles.cs
    /Controllers
    OwnerController.cs
    ReviewController.cs
    ReviewerController.cs
    /Data
    AppDbContext.cs

3. Owner GET 接口实现

3.1 接口层(IOwnerRepository)

定义三种方法,满足关联查询与存在性校验:

public interface IOwnerRepository
{
    IReadOnlyCollection < Pokemon > GetPokemonsByOwner(int ownerId); // 获取某 Owner 的所有 Pokemon
    bool OwnerExists(int ownerId);                                // 判断 Owner 是否存在
}

3.2 仓储层(OwnerRepository)

Repositories/OwnerRepository.cs 中实现:

public class OwnerRepository : IOwnerRepository
{
    private readonly AppDbContext _context;
    public OwnerRepository(AppDbContext context) = > _context = context;

    public IReadOnlyCollection < Pokemon > GetPokemonsByOwner(int ownerId) = >
        _context.PokemonOwners
                .Where(po = > po.Owner.Id == ownerId)      // 过滤关联表
                .Select(po = > po.Pokemon)                  // 投影到 Pokemon
                .ToList();

    public bool OwnerExists(int ownerId) = >
        _context.Owners.Any(o = > o.Id == ownerId);
}

3.3 DTO 与映射(OwnerDto)

  • DTOs/OwnerDto.cs

    public class OwnerDto
    {
      public int Id { get; set; }
      public string FirstName { get; set; }
      public string LastName { get; set; }
    }
  • Helpers/MappingProfiles.cs 中添加:

    CreateMap < Pokemon, PokemonDto > ();

3.4 控制器层(OwnerController)

[ApiController]
[Route("api/[controller]")]
public class OwnerController : ControllerBase
{
    private readonly IOwnerRepository _repo;
    private readonly IMapper _mapper;

    public OwnerController(IOwnerRepository repo, IMapper mapper)
    {
        _repo = repo;
        _mapper = mapper;
    }

    // GET api/owner/{id}/pokemons
    [HttpGet("{id}/pokemons")]
    public ActionResult < IReadOnlyCollection < PokemonDto >  >  GetPokemonsByOwner(int id)
    {
        if (!_repo.OwnerExists(id))
            return NotFound($"Owner {id} 未找到。");

        var pokemons = _repo.GetPokemonsByOwner(id);
        return Ok(_mapper.Map < IReadOnlyCollection < PokemonDto >  > (pokemons));
    }
}

4. Review GET 接口实现

4.1 接口层(IReviewRepository)

public interface IReviewRepository
{
    IReadOnlyCollection < Review >  GetReviews();                  // 全部 Review
    Review GetReview(int reviewId);                            // 单条 Review
    IReadOnlyCollection < Review > GetReviewsByPokemon(int pokemonId); // 某 Pokemon 的所有 Review
    bool ReviewExists(int reviewId);                           // Review 存在性校验
}

4.2 仓储层(ReviewRepository)

public class ReviewRepository : IReviewRepository
{
    private readonly AppDbContext _context;
    public ReviewRepository(AppDbContext context) = >  _context = context;

    public IReadOnlyCollection < Review > GetReviews() = >
        _context.Reviews.ToList();

    public Review GetReview(int reviewId) = >
        _context.Reviews.FirstOrDefault(r = > r.Id == reviewId);

    public IReadOnlyCollection < Review > GetReviewsByPokemon(int pokemonId) = >
        _context.Reviews
                .Where(r = > r.Pokemon.Id == pokemonId)
                .ToList();

    public bool ReviewExists(int reviewId) = >
        _context.Reviews.Any(r = > r.Id == reviewId);
}

4.3 DTO 与映射(ReviewDto)

  • DTOs/ReviewDto.cs

    public class ReviewDto
    {
      public int Id { get; set; }
      public string Title { get; set; }
      public string Text { get; set; }
      public int Rating { get; set; }
    }
  • MappingProfiles 中:

    CreateMap < Review, ReviewDto > ();

4.4 控制器层(ReviewController)

[ApiController]
[Route("api/[controller]")]
public class ReviewController : ControllerBase
{
    private readonly IReviewRepository _repo;
    private readonly IMapper _mapper;

    public ReviewController(IReviewRepository repo, IMapper mapper)
    {
        _repo = repo;
        _mapper = mapper;
    }

    // GET api/review
    [HttpGet]
    public ActionResult < IReadOnlyCollection < ReviewDto > > GetReviews()
        = > Ok(_mapper.Map < IReadOnlyCollection < ReviewDto > >(_repo.GetReviews()));

    // GET api/review/{id}
    [HttpGet("{id}")]
    public ActionResult < ReviewDto > GetReview(int id)
    {
        if (!_repo.ReviewExists(id))
            return NotFound($"Review {id} 未找到。");
        return Ok(_mapper.Map < ReviewDto > (_repo.GetReview(id)));
    }

    // GET api/review/pokemon/{pokemonId}
    [HttpGet("pokemon/{pokemonId}")]
    public ActionResult < IReadOnlyCollection < ReviewDto > >  GetReviewsByPokemon(int pokemonId)
    {
        var reviews = _repo.GetReviewsByPokemon(pokemonId);
        return Ok(_mapper.Map < IReadOnlyCollection < ReviewDto > > (reviews));
    }
}

5. Reviewer GET 接口实现

5.1 接口层(IReviewerRepository)

public interface IReviewerRepository
{
    IReadOnlyCollection < Reviewer > GetReviewers();               // 全部 Reviewer
    Reviewer GetReviewer(int reviewerId);                       // 单条 Reviewer
    IReadOnlyCollection < Review > GetReviewsByReviewer(int reviewerId); // 某 Reviewer 的所有 Review
    bool ReviewerExists(int reviewerId);                        // Reviewer 存在性校验
}

5.2 仓储层(ReviewerRepository)

public class ReviewerRepository : IReviewerRepository
{
    private readonly AppDbContext _context;
    public ReviewerRepository(AppDbContext context) = > _context = context;

    public IReadOnlyCollection < Reviewer > GetReviewers() = >
        _context.Reviewers.Include(r = > r.Reviews).ToList();    // Include 加载导航属性

    public Reviewer GetReviewer(int id) = >
        _context.Reviewers.FirstOrDefault(r = > r.Id == id);

    public IReadOnlyCollection < Review > GetReviewsByReviewer(int id) = >
        _context.Reviews.Where(r = > r.Reviewer.Id == id).ToList();

    public bool ReviewerExists(int id) = >
        _context.Reviewers.Any(r = > r.Id == id);
}

5.3 DTO 与映射(ReviewerDto)

  • DTOs/ReviewerDto.cs

    public class ReviewerDto
    {
      public int Id { get; set; }
      public string FirstName { get; set; }
      public string LastName { get; set; }
    }
  • MappingProfiles 中:

    CreateMap < Reviewer, ReviewerDto > ();

5.4 控制器层(ReviewerController)

[ApiController]
[Route("api/[controller]")]
public class ReviewerController : ControllerBase
{
    private readonly IReviewerRepository _repo;
    private readonly IMapper _mapper;

    public ReviewerController(IReviewerRepository repo, IMapper mapper)
    {
        _repo = repo;
        _mapper = mapper;
    }

    // GET api/reviewer
    [HttpGet]
    public ActionResult < IReadOnlyCollection < ReviewerDto > >  GetReviewers()
        = > Ok(_mapper.Map < IReadOnlyCollection < ReviewerDto >  >(_repo.GetReviewers()));

    // GET api/reviewer/{id}
    [HttpGet("{id}")]
    public ActionResult < ReviewerDto > GetReviewer(int id)
    {
        if (!_repo.ReviewerExists(id))
            return NotFound($"Reviewer {id} 未找到。");
        return Ok(_mapper.Map < ReviewerDto > (_repo.GetReviewer(id)));
    }

    // GET api/reviewer/{id}/reviews
    [HttpGet("{id}/reviews")]
    public ActionResult < IReadOnlyCollection < ReviewDto > >  GetReviewsByReviewer(int id)
    {
        var reviews = _repo.GetReviewsByReviewer(id);
        return Ok(_mapper.Map < IReadOnlyCollection < ReviewDto > > (reviews));
    }
}

6. JSON 循环引用问题处理

对于多对多或双向导航属性,默认序列化会出现循环依赖。解决方法:在 Program.cs 中替换默认 JSON 序列化设置为 ReferenceHandler.IgnoreCycles

builder.Services.AddControllers()
    .AddJsonOptions(options = >
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);

7. 小结与最佳实践

  • 接口优先:先定义 IRepository,再分层实现,保证松耦合与可测试性。
  • DTO 限制:只暴露前端所需字段,杜绝敏感信息泄露。
  • Include/Select:对导航属性显式加载,避免默认未加载导致 Null。
  • 循环依赖:通过 ReferenceHandler.IgnoreCycles 消除 JSON 循环引用。
  • REST 设计:列表 /resource、详情 /resource/{id}、子资源 /resource/{id}/sub}
  • Swagger 测试:及时调试所有 GET 接口,验证状态码与返回数据。

通过本篇,你已完成 Owner、Review、Reviewer 三个模型的 GET 接口搭建,下一步将进入 POST/PUT/DELETE,实现完整的 CRUD 能力。期待与您下次再见!

原文引自YouTube视频:https://www.youtube.com/watch?v=FEanWuYq7us

#你可能也喜欢这些API文章!

我们有何不同?

API服务商零注册

多API并行试用

数据驱动选型,提升决策效率

查看全部API→
🔥

热门场景实测,选对API

#AI文本生成大模型API

对比大模型API的内容创意新颖性、情感共鸣力、商业转化潜力

25个渠道
一键对比试用API 限时免费

#AI深度推理大模型API

对比大模型API的逻辑推理准确性、分析深度、可视化建议合理性

10个渠道
一键对比试用API 限时免费