FastEndpoints для C# (.NET): полное руководство с примерами
FastEndpoints — это современная, высокопроизводительная библиотека для создания веб-API на платформе .NET (ASP.NET Core), которая реализует паттерн REPR (Request-Endpoint-Response). В отличие от традиционного подхода MVC (Model-View-Controller) или Minimal API, FastEndpoints предлагает более чистую, модульную и тестируемую архитектуру, где каждый эндпоинт представляет собой отдельный класс, ответственный за обработку одного конкретного запроса.
Зачем нужна эта библиотека? В больших проектах с десятками и сотнями эндпоинтов традиционные контроллеры быстро превращаются в "божественные объекты" с раздутой логикой. FastEndpoints решает эту проблему, заставляя разработчика явно разделять обязанности: один класс — один эндпоинт. Это упрощает поддержку, тестирование и повторное использование кода. Кроме того, библиотека построена на нативном System.Text.Json и оптимизирована для максимальной производительности, что делает её одной из самых быстрых библиотек для создания API в экосистеме .NET.
FastEndpoints идеально подходит для микросервисной архитектуры, проектов с Domain-Driven Design (DDD) и Clean Architecture. Она предоставляет встроенную валидацию (через FluentValidation), поддержку Swagger/OpenAPI, обработку ошибок, кэширование, версионирование эндпоинтов и многое другое, избавляя разработчика от написания шаблонного кода.
Установка
FastEndpoints устанавливается через NuGet. Вам потребуется .NET SDK 6.0 или выше. Выполните одну из следующих команд в терминале:
dotnet add package FastEndpoints
Для интеграции со Swagger (опционально, но рекомендуется):
dotnet add package FastEndpoints.Swagger
Для использования FluentValidation (рекомендуется):
dotnet add package FluentValidation
Быстрый старт — минимальный рабочий пример
Создайте новый проект ASP.NET Core Web API (пустой или с контроллерами — не важно, мы всё равно будем использовать FastEndpoints).
Шаг 1: Настройка Program.cs
using FastEndpoints;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFastEndpoints(); // Регистрируем сервисы FastEndpoints
var app = builder.Build();
app.UseFastEndpoints(); // Подключаем middleware
app.Run();
Шаг 2: Создание эндпоинта
Создайте папку Endpoints и внутри неё файл HelloEndpoint.cs:
using FastEndpoints;
namespace MyApi.Endpoints;
public class HelloEndpoint : Endpoint<EmptyRequest, string>
{
public override void Configure()
{
Get("/api/hello"); // HTTP метод GET на /api/hello
AllowAnonymous(); // Доступен без авторизации
}
public override async Task HandleAsync(EmptyRequest req, CancellationToken ct)
{
await SendAsync("Hello, World!", cancellation: ct);
}
}
Шаг 3: Запуск
Запустите приложение (dotnet run) и откройте браузер по адресу http://localhost:5000/api/hello. Вы увидите ответ: Hello, World!
Основные классы и методы
Ниже приведены ключевые классы и методы библиотеки FastEndpoints с примерами использования.
1. Класс Endpoint<TRequest, TResponse>
Сигнатура: public abstract class Endpoint<TRequest, TResponse> : BaseEndpoint where TRequest : notnull, new()
Что делает: Базовый класс для создания эндпоинта. Принимает тип запроса (TRequest) и тип ответа (TResponse).
Пример:
public class CreateUserEndpoint : Endpoint<CreateUserRequest, UserResponse>
{
public override void Configure()
{
Post("/api/users");
AllowAnonymous();
}
public override async Task HandleAsync(CreateUserRequest req, CancellationToken ct)
{
// Логика создания пользователя
var response = new UserResponse { Id = 1, Name = req.Name };
await SendAsync(response, cancellation: ct);
}
}
2. Метод Configure()
Сигнатура: public abstract void Configure()
Что делает: Обязательный метод для настройки эндпоинта: определение HTTP-метода, маршрута, политик авторизации, валидации и т.д.
Пример:
public override void Configure()
{
Get("/api/products/{id:int}"); // Маршрут с параметром
Policies("AdminOnly"); // Требуется политика авторизации
Description(b => b // Настройка Swagger
.Produces<Product>(200)
.Produces(404));
}
3. Метод HandleAsync()
Сигнатура: public abstract Task HandleAsync(TRequest req, CancellationToken ct)
Что делает: Основной метод, содержащий логику обработки запроса. Принимает объект запроса и токен отмены.
Пример:
public override async Task HandleAsync(UpdateProductRequest req, CancellationToken ct)
{
var product = await _db.Products.FindAsync(req.Id, ct);
if (product is null)
{
await SendNotFoundAsync(ct); // Отправляем 404
return;
}
product.Name = req.Name;
await _db.SaveChangesAsync(ct);
await SendOkAsync(product, ct); // Отправляем 200
}
4. Метод SendAsync()
Сигнатура: protected Task SendAsync<TResponse>(TResponse response, int statusCode = 200, CancellationToken cancellation = default)
Что делает: Отправляет клиенту ответ с указанным объектом и HTTP-статус кодом. Это основной метод для возврата данных.
Пример:
await SendAsync(new { Message = "Success" }, statusCode: 201, cancellation: ct);
5. Метод SendOkAsync()
Сигнатура: protected Task SendOkAsync<TResponse>(TResponse response, CancellationToken cancellation = default)
Что делает: Отправляет ответ со статусом 200 OK. Удобная обёртка над SendAsync.
Пример:
await SendOkAsync(productsList, ct);
6. Метод SendNotFoundAsync()
Сигнатура: protected Task SendNotFoundAsync(CancellationToken cancellation = default)
Что делает: Отправляет ответ со статусом 404 Not Found. Полезно, когда запрашиваемый ресурс не найден.
Пример:
if (user is null)
await SendNotFoundAsync(ct);
7. Метод SendErrorsAsync()
Сигнатура: protected Task SendErrorsAsync(int statusCode = 400, CancellationToken cancellation = default)
Что делает: Отправляет ответ с ошибками валидации. Автоматически сериализует ошибки из ValidationFailures.
Пример:
public override async Task HandleAsync(CreateUserRequest req, CancellationToken ct)
{
if (string.IsNullOrEmpty(req.Email))
AddError(r => r.Email, "Email is required"); // Добавляем ошибку
ThrowIfAnyErrors(); // Если есть ошибки, вызовет SendErrorsAsync(400)
// ... логика создания
}
8. Атрибут/Метод AllowAnonymous()
Сигнатура: protected void AllowAnonymous()
Что делает: Указывает, что эндпоинт доступен без аутентификации. Должен быть вызван в методе Configure().
Пример:
public override void Configure()
{
Get("/api/public/info");
AllowAnonymous(); // Все могут получить доступ
}
9. Метод AddError()
Сигнатура: protected void AddError<TProperty>(Expression<Func<TRequest, TProperty>> expression, string errorMessage, string? errorCode = null)
Что делает: Добавляет ошибку валидации для конкретного поля запроса. Используется вместе с ThrowIfAnyErrors().
Пример:
AddError(r => r.Age, "Age must be between 18 and 99");
10. Метод ThrowIfAnyErrors()
Сигнатура: protected void ThrowIfAnyErrors()
Что делает: Проверяет, есть ли добавленные ошибки. Если есть, выбрасывает исключение, которое перехватывается middleware и отправляет клиенту ответ 400 с ошибками.
Пример:
AddError(r => r.Name, "Name is required");
AddError(r => r.Email, "Invalid email format");
ThrowIfAnyErrors(); // Отправит 400 с двумя ошибками
Реальный практический пример (150+ строк кода)
Создадим простой REST API для управления списком задач (To-Do List) с использованием FastEndpoints и Entity Framework Core InMemory.
1. Модели (Models/TodoItem.cs)
public class TodoItem
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public bool IsCompleted { get; set; }
}
public class CreateTodoRequest
{
public string Title { get; set; } = string.Empty;
}
public class UpdateTodoRequest
{
public int Id { get; set; }
public string? Title { get; set; }
public bool? IsCompleted { get; set; }
}
2. Контекст базы данных (Data/AppDbContext.cs)
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<TodoItem> TodoItems { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}
3. Эндпоинты (Endpoints/):
CreateTodoEndpoint.cs
using FastEndpoints;
public class CreateTodoEndpoint : Endpoint<CreateTodoRequest, TodoItem>
{
private readonly AppDbContext _db;
public CreateTodoEndpoint(AppDbContext db) => _db = db;
public override void Configure()
{
Post("/api/todos");
AllowAnonymous();
}
public override async Task HandleAsync(CreateTodoRequest req, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(req.Title))
{
AddError(r => r.Title, "Title cannot be empty");
ThrowIfAnyErrors();
}
var todo = new TodoItem { Title = req.Title };
_db.TodoItems.Add(todo);
await _db.SaveChangesAsync(ct);
await SendAsync(todo, statusCode: 201, cancellation: ct);
}
}
GetAllTodosEndpoint.cs
public class GetAllTodosEndpoint : Endpoint<EmptyRequest, List<TodoItem>>
{
private readonly AppDbContext _db;
public GetAllTodosEndpoint(AppDbContext db) => _db = db;
public override void Configure()
{
Get("/api/todos");
AllowAnonymous();
}
public override async Task HandleAsync(EmptyRequest req, CancellationToken ct)
{
var todos = await _db.TodoItems.ToListAsync(ct);
await SendOkAsync(todos, ct);
}
}
UpdateTodoEndpoint.cs
public class UpdateTodoEndpoint : Endpoint<UpdateTodoRequest, TodoItem>
{
private readonly AppDbContext _db;
public UpdateTodoEndpoint(AppDbContext db) => _db = db;
public override void Configure()
{
Put("/api/todos/{id:int}");
AllowAnonymous();
}
public override async Task HandleAsync(UpdateTodoRequest req, CancellationToken ct)
{
var todo = await _db.TodoItems.FindAsync(new object[] { req.Id }, ct);
if (todo is null)
{
await SendNotFoundAsync(ct);
return;
}
if (req.Title is not null) todo.Title = req.Title;
if (req.IsCompleted.HasValue) todo.IsCompleted = req.IsCompleted.Value;
await _db.SaveChangesAsync(ct);
await SendOkAsync(todo, ct);
}
}
DeleteTodoEndpoint.cs
public class DeleteTodoEndpoint : Endpoint<EmptyRequest>
{
private readonly AppDbContext _db;
public DeleteTodoEndpoint(AppDbContext db) => _db = db;
public override void Configure()
{
Delete("/api/todos/{id:int}");
AllowAnonymous();
}
public override async Task HandleAsync(EmptyRequest req, CancellationToken ct)
{
var id = Route<int>("id"); // Получаем параметр маршрута
var todo = await _db.TodoItems.FindAsync(new object[] { id }, ct);
if (todo is null)
{
await SendNotFoundAsync(ct);
return;
}
_db.TodoItems.Remove(todo);
await _db.SaveChangesAsync(ct);
await SendNoContentAsync(ct); // 204 No Content
}
}
4. Program.cs
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TodoDb"));
builder.Services.AddFastEndpoints();
var app = builder.Build();
app.UseFastEndpoints();
app.Run(); Также в библиотеке
Работа с XML в TypeScript: библиотеки, парсинг и создание XML-документов
ORM для JavaScript (Node.js): сравнение Sequelize, TypeORM, Prisma
Redis клиент для PHP: Predis и PhpRedis – подключение и примеры