GraphQL query in .NET Core

使用 HotChocolate 在 ASP.NET Core Web API 项目中提供 GraphQL 查询服务


添加依赖

.NET 平台上的 GraphQL 服务有 GraphQL.net 和 HotChocolate

HotChocolate 提供了更多的特性和扩展,比如过滤、排序、分页、订阅、批量操作等,并且可以自动将 .NET 类型转换为 GraphQL 类型
如果想要更多的控制和灵活性,可以选择 GraphQL.net;如果想要更多的便利和功能,则可以选择 HotChocolate

配置 HotChocolate 会更简单一些

dotnet add package HotChocolate.Data
dotnet add package HotChocolate.AspNetCore
dotnet add package HotChocolate.Abstractions

实现查询

  • 在 Persistence 层创建 TodoListQuery.cs,在这里实现需要的查询
public class TodoListQuery
{
    [UsePaging(IncludeTotalCount = true, DefaultPageSize = 10)]
    public IQueryable<TodoList> GetTodoLists([Service] ApplicationDbContext dbContext)
        => dbContext.TodoLists.AsQueryable();
    
    public IQueryable<TodoList> GetTodoList(int id, [Service] ApplicationDbContext dbContext)
        => dbContext.TodoLists.Include(x => x.Items).Where(x => x.Id == id);

    // ...
}

Use IncludeTotalCount = true to show the total count of the output result.

Use int id as input param.

  • 若使用 MediatR,则在 API 层传递 command,并实现相应的 handler
public class TodoListQuery
{
    [UsePaging(IncludeTotalCount = true, DefaultPageSize = 10)]
    [UseFiltering]
    public async Task<List<TodoListDto>> GetTodoLists([Service] IMediator mediator)
        => await mediator.Send(new GetTodosQueryGraph(null));
    
    [UsePaging(IncludeTotalCount = true, DefaultPageSize = 10)]
    public async Task<List<TodoListDto>> GetTodoLists(GetTodosQueryGraph query, [Service] IMediator mediator)
        => await mediator.Send(query);

    // ...
}
public record GetTodosQueryGraph(string? Title) : IRequest<List<TodoListDto>>;

public class GetTodosQueryGraphHandler : IRequestHandler<GetTodosQueryGraph, List<TodoListDto>>
{
    private readonly IApplicationDbContext _context;
    private readonly IMapper _mapper;

    public GetTodosQueryGraphHandler(IApplicationDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<List<TodoListDto>> Handle(GetTodosQueryGraph request, CancellationToken cancellationToken)
    {
        return await _context.TodoLists
            .AsNoTracking()
            .Where(x => x.Title.Contains(request.Title!))
            .ProjectTo<TodoListDto>(_mapper.ConfigurationProvider)
            .OrderBy(t => t.Title)
            .ToListAsync(cancellationToken);
    }
}

注入配置

  • 注入 GraphQL 服务配置
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGraphQLServer()
    .AddQueryType<TodoListQuery>()
    .AddFiltering();

仅在开发环境下启用 Banana Cake Pop GraphQL IDE

app.MapGraphQL().WithOptions(new GraphQLServerOptions()
{
    Tool = { Enable = builder.Environment.IsDevelopment() }
});

app.Run();

测试查询

访问 /graphql 进行测试

  • request

    query TodoLists {
      todoLists(first: 3, where: {title: {contains: "occaecat"}}){
        totalCount
        nodes{
          id
          title
        }
        pageInfo{
          hasPreviousPage
          startCursor
          endCursor
          hasNextPage
        }
      }  
    }
  • repsonse

    {
      "data": {
        "todoLists": {
          "totalCount": 18,
          "nodes": [
            {
              "id": 3,
              "title": "occaecat m"
            },
            {
              "id": 4,
              "title": "occaecat rm"
            },
            {
              "id": 5,
              "title": "occaecat; rm"
            }
          ],
          "pageInfo": {
            "hasPreviousPage": false,
            "startCursor": "MA==",
            "endCursor": "Mg==",
            "hasNextPage": true
          }
        }
      }
    }

Getting started with GraphQL and HotChocolate | Microsoft Learn

HotChocolate - An Introduction to GraphQL for ASP.NET Core

Introduction - Hot Chocolate - ChilliCream GraphQL Platform