Введение: Почему GraphQL и C# — идеальная пара
Современная веб-разработка требует гибких и производительных API. REST API, несмотря на свою популярность, часто страдает от over-fetching (получение лишних данных) и under-fetching (необходимость нескольких запросов для получения полной информации). GraphQL решает эти проблемы, позволяя клиенту запрашивать только то, что ему нужно, за один запрос.
В этом туториале мы создадим полноценный GraphQL API на C# с использованием библиотеки Hot Chocolate — самой популярной реализации GraphQL для .NET. Вы узнаете, как настроить сервер, определить схему, реализовать запросы (queries), мутации (mutations) и подписки (subscriptions). Все примеры кода будут практическими и готовыми к использованию.
Для кого этот материал: для .NET-разработчиков, которые хотят освоить GraphQL, и для тех, кто ищет альтернативу REST. Базовые знания C# и ASP.NET Core будут полезны, но не обязательны.
1. Настройка проекта и установка Hot Chocolate
Создание проекта ASP.NET Core
Начнем с создания нового проекта. Откройте терминал или командную строку и выполните:
dotnet new web -n GraphQLDemo
cd GraphQLDemo
Теперь установим пакет Hot Chocolate:
dotnet add package HotChocolate.AspNetCore
dotnet add package HotChocolate.AspNetCore.Playground
Пакет HotChocolate.AspNetCore добавляет middleware для обработки GraphQL-запросов, а HotChocolate.AspNetCore.Playground предоставляет встроенную IDE (GraphQL Playground) для тестирования.
Конфигурация Startup
Отредактируйте файл Program.cs (или Startup.cs для .NET 5 и ниже). Добавим базовую конфигурацию:
var builder = WebApplication.CreateBuilder(args);
// Добавляем сервисы GraphQL
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>(); // добавим позже
var app = builder.Build();
// Добавляем middleware
app.UseRouting();
app.MapGraphQL(); // эндпоинт /graphql
app.MapGraphQLPlayground(); // Playground доступен по /playground
app.Run();
Обратите внимание: Query и Mutation — это классы, которые мы создадим далее. Пока оставим только Query.
2. Создание модели данных и первого запроса (Query)
Модель Book
Создадим простую модель книги. Добавьте файл Models/Book.cs:
namespace GraphQLDemo.Models;
public class Book
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public int Year { get; set; }
public decimal Price { get; set; }
}
Класс Query
Теперь создадим класс Query, который будет содержать методы для получения данных. Добавьте файл GraphQL/Query.cs:
using GraphQLDemo.Models;
namespace GraphQLDemo.GraphQL;
public class Query
{
// Временное хранилище данных (в реальном проекте — БД)
private static readonly List<Book> Books = new()
{
new Book { Id = 1, Title = "Война и мир", Author = "Лев Толстой", Year = 1869, Price = 29.99m },
new Book { Id = 2, Title = "Преступление и наказание", Author = "Федор Достоевский", Year = 1866, Price = 24.99m },
new Book { Id = 3, Title = "Мастер и Маргарита", Author = "Михаил Булгаков", Year = 1967, Price = 19.99m }
};
// Запрос: получить все книги
public IQueryable<Book> GetBooks() => Books.AsQueryable();
// Запрос: получить книгу по ID
public Book? GetBookById(int id) => Books.FirstOrDefault(b => b.Id == id);
}
Методы, возвращающие данные, называются resolver'ами. Hot Chocolate автоматически определяет их имена и типы. Обратите внимание: названия методов в C# преобразуются в camelCase в GraphQL (например, GetBooks станет getBooks).
Тестирование в Playground
Запустите приложение (dotnet run) и откройте браузер по адресу http://localhost:5000/playground. Введите следующий запрос:
query {
books {
id
title
author
year
}
}
Вы получите список книг. Попробуйте запросить только title и author — вы увидите, что лишние поля не возвращаются. Это и есть магия GraphQL.
3. Мутации: добавление и изменение данных
Мутации (mutations) — это операции, которые изменяют данные на сервере (аналог POST, PUT, DELETE в REST). Создадим класс Mutation в файле GraphQL/Mutation.cs:
using GraphQLDemo.Models;
namespace GraphQLDemo.GraphQL;
public class Mutation
{
// Временное хранилище (в реальном проекте — инжекция через DI)
private static readonly List<Book> Books = new()
{
new Book { Id = 1, Title = "Война и мир", Author = "Лев Толстой", Year = 1869, Price = 29.99m },
new Book { Id = 2, Title = "Преступление и наказание", Author = "Федор Достоевский", Year = 1866, Price = 24.99m }
};
private static int _nextId = 3;
// Мутация: добавить книгу
public Book AddBook(string title, string author, int year, decimal price)
{
var book = new Book
{
Id = _nextId++,
Title = title,
Author = author,
Year = year,
Price = price
};
Books.Add(book);
return book;
}
// Мутация: обновить цену книги
public Book? UpdateBookPrice(int id, decimal newPrice)
{
var book = Books.FirstOrDefault(b => b.Id == id);
if (book != null)
{
book.Price = newPrice;
}
return book;
}
}
Не забудьте зарегистрировать Mutation в Program.cs (мы уже добавили строку .AddMutationType<Mutation>()). Теперь перезапу