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

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

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

Начать курс

Библиотека Carter для C# (.NET): Полное руководство

1. Что это и зачем нужна

Carter — это легковесный, высокопроизводительный фреймворк для создания HTTP API на платформе ASP.NET Core. Он вдохновлён популярным фреймворком NancyFX и предлагает разработчикам знакомый, декларативный подход к маршрутизации и обработке запросов. В отличие от традиционного MVC (Model-View-Controller), Carter позволяет определять эндпоинты (endpoints) непосредственно в коде, без необходимости создавать отдельные контроллеры и настраивать сложную маршрутизацию.

Зачем использовать Carter? Основная причина — простота и скорость разработки. Если вам нужно быстро создать минимальное API или микросервис, Carter предоставляет лаконичный синтаксис, который сокращает шаблонный код. Он идеально подходит для проектов, где не требуется полная мощь MVC, но нужна гибкость и удобство работы с запросами и ответами. Carter также отлично интегрируется с ASP.NET Core, используя его встроенный контейнер зависимостей, middleware и другие возможности.

Ключевые особенности Carter включают: автоматическое связывание параметров маршрута и тела запроса (model binding), встроенную поддержку валидации, простую обработку ошибок, а также возможность легко тестировать эндпоинты. Фреймворк активно поддерживается сообществом и используется в production-среде многими компаниями.

2. Установка

Для установки Carter в ваш проект ASP.NET Core используйте менеджер пакетов NuGet. Выполните одну из следующих команд в терминале:

dotnet add package Carter

или через консоль диспетчера пакетов Visual Studio:

Install-Package Carter

После установки необходимо зарегистрировать Carter в вашем Program.cs (или Startup.cs в старых версиях):

using Carter;

var builder = WebApplication.CreateBuilder(args);

// Добавляем Carter в контейнер DI
builder.Services.AddCarter();

var app = builder.Build();

// Подключаем middleware Carter
app.MapCarter();

app.Run();

Обратите внимание: в современных версиях .NET (6+) используется минимальное API, но Carter всё ещё актуален для более сложной логики.

3. Быстрый старт — минимальный рабочий пример

Создайте новый проект ASP.NET Core (пустой или Web API). Установите Carter, как описано выше. Затем создайте класс, реализующий интерфейс ICarterModule:

using Carter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

public class HomeModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        // Определяем GET-эндпоинт по пути "/"
        app.MapGet("/", () => "Hello from Carter!");
        
        // Определяем GET-эндпоинт с параметром
        app.MapGet("/hello/{name}", (string name) => $"Hello, {name}!");
    }
}

Теперь зарегистрируйте этот модуль в Program.cs:

using Carter;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCarter();
var app = builder.Build();
app.MapCarter();
app.Run();

Запустите приложение и перейдите по адресу http://localhost:5000/hello/World. Вы увидите ответ: Hello, World!.

4. Основные методы/функции/классы

Рассмотрим ключевые элементы Carter, которые вы будете использовать чаще всего.

4.1. ICarterModule

Сигнатура: public interface ICarterModule

Что делает: Базовый интерфейс для создания модуля маршрутизации. Все ваши эндпоинты должны быть определены в классе, реализующем этот интерфейс.

public class MyModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapGet("/my-endpoint", () => "OK");
    }
}

4.2. MapGet

Сигнатура: IEndpointConventionBuilder MapGet(this IEndpointRouteBuilder app, string pattern, Delegate handler)

Что делает: Определяет эндпоинт, обрабатывающий HTTP GET-запросы по указанному маршруту.

app.MapGet("/users", () => new[] { "Alice", "Bob" });

4.3. MapPost

Сигнатура: IEndpointConventionBuilder MapPost(this IEndpointRouteBuilder app, string pattern, Delegate handler)

Что делает: Определяет эндпоинт для обработки HTTP POST-запросов.

app.MapPost("/users", (User user) => Results.Created($"/users/{user.Id}", user));

4.4. MapPut

Сигнатура: IEndpointConventionBuilder MapPut(this IEndpointRouteBuilder app, string pattern, Delegate handler)

Что делает: Определяет эндпоинт для обработки HTTP PUT-запросов (обновление ресурса).

app.MapPut("/users/{id}", (int id, User updatedUser) => Results.Ok(updatedUser));

4.5. MapDelete

Сигнатура: IEndpointConventionBuilder MapDelete(this IEndpointRouteBuilder app, string pattern, Delegate handler)

Что делает: Определяет эндпоинт для обработки HTTP DELETE-запросов.

app.MapDelete("/users/{id}", (int id) => Results.NoContent());

4.6. Bind (Model Binding)

Сигнатура: T Bind(this HttpRequest request) (расширение)

Что делает: Автоматически связывает данные из тела запроса (JSON) с объектом указанного типа. Используется внутри обработчика.

app.MapPost("/users", async (HttpRequest req) => 
{
    var user = await req.Bind<User>();
    return Results.Created($"/users/{user.Id}", user);
});

4.7. Validate

Сигнатура: ValidationResult Validate(this object instance) (расширение)

Что делает: Выполняет валидацию объекта с использованием атрибутов System.ComponentModel.DataAnnotations.

app.MapPost("/users", async (HttpRequest req) =>
{
    var user = await req.Bind<User>();
    var validationResult = user.Validate();
    if (!validationResult.IsValid)
        return Results.BadRequest(validationResult.Errors);
    return Results.Ok(user);
});

4.8. Negotiate (Content Negotiation)

Сигнатура: IResult Negotiate(this object model) (расширение)

Что делает: Автоматически выбирает формат ответа (JSON, XML и т.д.) на основе заголовка Accept запроса.

app.MapGet("/users", () => new[] { "Alice", "Bob" }.Negotiate());

4.9. AsJson

Сигнатура: IResult AsJson(this object model) (расширение)

Что делает: Принудительно возвращает ответ в формате JSON.

app.MapGet("/user", () => new { Name = "Alice", Age = 30 }.AsJson());

4.10. Before/After (Фильтры)

Сигнатура: void Before(Action<HttpContext> action) / void After(Action<HttpContext> action) (методы модуля)

Что делает: Позволяют выполнить код до или после обработки запроса в рамках модуля. Полезно для логирования, проверки аутентификации и т.д.

public class SecureModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        // Выполняется перед каждым запросом в этом модуле
        app.Before(ctx => 
        {
            if (!ctx.Request.Headers.ContainsKey("X-API-Key"))
            {
                ctx.Response.StatusCode = 401;
                return ctx.Response.WriteAsync("Unauthorized");
            }
            return Task.CompletedTask;
        });

        app.MapGet("/secure-data", () => "This is secure data");
    }
}

5. Реальный практический пример (100-200 строк кода)

Создадим простое API для управления списком задач (Todo List) с использованием Carter. Проект будет включать хранение в памяти, валидацию и все CRUD-операции.

using Carter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;

// Модель задачи
public class TodoItem
{
    public int Id { get; set; }

    [Required(ErrorMessage = "Title is required")]
    [StringLength(100, ErrorMessage = "Title cannot exceed 100 characters")]
    public string Title { get; set; } = string.Empty;

    public bool IsCompleted { get; set; }
}

// Хранилище в памяти (потокобезопасное)
public class TodoStore
{
    private readonly ConcurrentDictionary<int, TodoItem> _items = new();
    private int _nextId = 1;

    public IEnumerable<TodoItem> GetAll() => _items.Values;

    public TodoItem? GetById(int id) => _items.GetValueOrDefault(id);

    public TodoItem Add(TodoItem item)
    {
        item.Id = Interlocked.Increment(ref _nextId);
        _items[item.Id] = item;
        return item;
    }

    public TodoItem? Update(int id, TodoItem updated)
    {
        if (_items.TryGetValue(id, out var existing))
        {
            existing.Title = updated.Title;
            existing.IsCompleted = updated.IsCompleted;
            return existing;
        }
        return null;
    }

    public bool Delete(int id) => _items.TryRemove(id, out _);
}

// Модуль Carter для Todo
public class TodoModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        // GET /todos — получить все задачи
        app.MapGet("/todos", (TodoStore store) =>
        {
            return store.GetAll().Negotiate();
        });

        // GET /todos/{id} — получить задачу по ID
        app.MapGet("/todos/{id:int}", (int id, TodoStore store) =>
        {
            var item = store.GetById(id);
            return item is not null ? Results.Ok(item) : Results.NotFound();
        });

        // POST /todos — создать новую задачу
        app.MapPost("/todos", async (HttpRequest req, TodoStore store) =>
        {
            var item = await req.Bind<TodoItem>();
            var validationResult = item.Validate();
            if (!validationResult.IsValid)
                return Results.BadRequest(validationResult.Errors);

            var created = store.Add(item);
            return Results.Created($"/todos/{created.Id}", created);
        });

        // PUT /todos/{id} — обновить задачу
        app.MapPut("/todos/{id:int}", async (int id, HttpRequest req, TodoStore store) =>
        {
            var updated = await req.Bind<TodoItem>();
            var validationResult = updated.Validate();
            if (!validationResult.IsValid)
                return Results.BadRequest(validationResult.Errors);

            var result = store.Update(id, updated);
            return result is not null ? Results.Ok(result) : Results.NotFound();
        });

        // DELETE /todos/{id} — удалить задачу
        app.MapDelete("/todos/{id:int}", (int id, TodoStore store) =>
        {
            return store.Delete(id) ? Results.NoContent() : Results.NotFound();
        });
    }
}

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Регистрируем хранилище как singleton
builder.Services.AddSingleton<TodoStore>();
builder.Services.AddCarter();

var app = builder.Build();
app.MapCarter();
app.Run();

Этот пример демонстрирует все основные возможности Carter: маршрутизацию, связывание, валидацию, согласование контента и работу с DI. Вы можете протестировать его с помощью curl или Postman.

6. Советы по производительности и типичные ошибки

Советы по производительности

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