FastEndpoints для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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();

Рекомендаций