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

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

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

Начать курс

Refit для C# (.NET): Type-safe REST клиент

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

Refit — это библиотека с открытым исходным кодом для платформы .NET, которая автоматически генерирует реализации HTTP-клиентов на основе интерфейсов, описанных разработчиком. Она позволяет превратить обычный C# интерфейс в полноценный REST-клиент, избавляя от необходимости вручную писать шаблонный код для выполнения HTTP-запросов, сериализации/десериализации JSON и обработки ошибок.

Основная проблема, которую решает Refit — это борьба с дублированием кода при работе с REST API. Вместо того чтобы для каждого эндпоинта создавать отдельный метод с HttpClient, настраивать заголовки, обрабатывать ответы и исключения, разработчик просто объявляет интерфейс с методами, помеченными атрибутами Refit. Библиотека сама генерирует реализацию, используя HttpClientFactory (рекомендуемый подход) или напрямую HttpClient.

Refit особенно полезен при разработке микросервисных архитектур, интеграции со сторонними API (GitHub, Stripe, Telegram и т.д.) и в любых проектах, где требуется типобезопасное взаимодействие с HTTP-сервисами. Он поддерживает все основные HTTP-методы (GET, POST, PUT, DELETE, PATCH), работу с заголовками, query-параметрами, телом запроса, мультипарт-формами и файлами.

2. Установка

Refit распространяется через NuGet. Для установки используйте одну из следующих команд:

# Через .NET CLI
dotnet add package Refit

# Через Package Manager Console (Visual Studio)
Install-Package Refit

# Или через NuGet Package Manager в IDE

Для работы с асинхронными методами и интеграции с ASP.NET Core рекомендуется также установить пакет Refit.HttpClientFactory:

dotnet add package Refit.HttpClientFactory

Если вы используете System.Text.Json (по умолчанию в .NET Core 3.1+), дополнительных пакетов не требуется. Для Newtonsoft.Json установите Refit.Newtonsoft.Json.

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

Создадим простой клиент для получения данных с JSONPlaceholder (тестовый REST API).

Шаг 1: Определите модель данных:

public class Todo
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string Title { get; set; }
    public bool Completed { get; set; }
}

Шаг 2: Создайте интерфейс с атрибутами Refit:

using Refit;

public interface ITodoApi
{
    [Get("/todos/{id}")]
    Task<Todo> GetTodoAsync(int id);

    [Get("/todos")]
    Task<List<Todo>> GetAllTodosAsync();
}

Шаг 3: Используйте Refit для создания клиента:

using Refit;

class Program
{
    static async Task Main(string[] args)
    {
        // Создаем клиент напрямую (без DI)
        var todoApi = RestService.For<ITodoApi>("https://jsonplaceholder.typicode.com");
        
        // Получаем один Todo
        var todo = await todoApi.GetTodoAsync(1);
        Console.WriteLine($"Todo: {todo.Title}, Completed: {todo.Completed}");
        
        // Получаем все Todos
        var todos = await todoApi.GetAllTodosAsync();
        Console.WriteLine($"Total todos: {todos.Count}");
    }
}

Этот пример демонстрирует базовую механику: вы описываете интерфейс, а Refit генерирует реализацию, которая выполняет HTTP-запросы и десериализует ответы.

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

4.1. RestService.For<T>()

Сигнатура: public static T For<T>(string hostUrl, RefitSettings settings = null)

Что делает: Создает реализацию интерфейса T для заданного базового URL. Это основной способ создания клиента без использования DI.

var api = RestService.For<IGitHubApi>("https://api.github.com");

4.2. RestService.For<T>(HttpClient)

Сигнатура: public static T For<T>(HttpClient client, RefitSettings settings = null)

Что делает: Создает реализацию интерфейса на основе существующего HttpClient. Полезно, когда нужно поделить один HttpClient между несколькими клиентами.

var client = new HttpClient { BaseAddress = new Uri("https://api.example.com") };
var api = RestService.For<IMyApi>(client);

4.3. RefitSettings

Сигнатура: public class RefitSettings { ... }

Что делает: Класс для настройки поведения Refit: выбор сериализатора (System.Text.Json или Newtonsoft.Json), обработчики исключений, политики повторных попыток и т.д.

var settings = new RefitSettings
{
    ContentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    })
};
var api = RestService.For<IMyApi>("https://api.example.com", settings);

4.4. Атрибут [Get]

Сигнатура: [Get(string path)]

Что делает: Указывает, что метод должен выполнять HTTP GET запрос по указанному пути. Поддерживает шаблоны URL с параметрами.

public interface IUserApi
{
    [Get("/users/{userId}")]
    Task<User> GetUserAsync(int userId);
    
    [Get("/users?role={role}&active={isActive}")]
    Task<List<User>> GetUsersByRoleAsync(string role, bool isActive);
}

4.5. Атрибут [Post]

Сигнатура: [Post(string path)]

Что делает: Указывает, что метод должен выполнять HTTP POST запрос. Тело запроса передается через параметр с атрибутом [Body].

public interface IOrderApi
{
    [Post("/orders")]
    Task<Order> CreateOrderAsync([Body] Order order);
    
    [Post("/orders/batch")]
    Task<List<Order>> CreateOrdersAsync([Body] List<Order> orders);
}

4.6. Атрибут [Put] и [Patch]

Сигнатура: [Put(string path)] / [Patch(string path)]

Что делает: Выполняют HTTP PUT (полная замена ресурса) и PATCH (частичное обновление) запросы.

public interface IProductApi
{
    [Put("/products/{id}")]
    Task<Product> UpdateProductAsync(int id, [Body] Product product);
    
    [Patch("/products/{id}")]
    Task<Product> PatchProductAsync(int id, [Body] Dictionary<string, object> updates);
}

4.7. Атрибут [Delete]

Сигнатура: [Delete(string path)]

Что делает: Выполняет HTTP DELETE запрос для удаления ресурса.

public interface IArticleApi
{
    [Delete("/articles/{id}")]
    Task DeleteArticleAsync(int id);
    
    [Delete("/articles")]
    Task DeleteAllArticlesAsync();
}

4.8. Атрибут [Headers]

Сигнатура: [Headers(string header1, string header2, ...)]

Что делает: Позволяет задать статические или динамические заголовки для всех методов интерфейса или конкретного метода. Поддерживает интерполяцию строк.

[Headers("User-Agent: MyApp/1.0", "Accept: application/json")]
public interface IAuthenticatedApi
{
    [Get("/me")]
    [Headers("Authorization: Bearer")]
    Task<User> GetCurrentUserAsync(string authorization);
    
    [Post("/data")]
    [Headers("X-Custom-Header: {value}")]
    Task SendDataAsync(string value, [Body] object data);
}

4.9. Атрибут [Query] и [AliasAs]

Сигнатура: [Query(string prefix)] / [AliasAs(string name)]

Что делает: [Query] указывает, что параметр должен быть передан как query-параметр. [AliasAs] позволяет переименовать параметр в URL (полезно для C# имен, не совпадающих с API).

public interface ISearchApi
{
    [Get("/search")]
    Task<SearchResult> SearchAsync(
        [Query] string query,
        [Query("page")] int pageNumber,
        [AliasAs("per_page")] int pageSize
    );
}

4.10. Атрибут [Multipart]

Сигнатура: [Multipart]

Что делает: Указывает, что запрос должен быть отправлен как multipart/form-data. Используется для загрузки файлов.

public interface IFileApi
{
    [Multipart]
    [Post("/upload")]
    Task<UploadResult> UploadFileAsync(
        [AliasAs("file")] StreamPart stream,
        [AliasAs("description")] string description
    );
}

// Использование:
var fileStream = File.OpenRead("photo.jpg");
var streamPart = new StreamPart(fileStream, "photo.jpg", "image/jpeg");
var result = await api.UploadFileAsync(streamPart, "My vacation photo");

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

Разработаем полноценный клиент для GitHub API, который будет получать информацию о репозиториях, создавать issue и работать с файлами.

Модели данных

public class GitHubRepo
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string FullName { get; set; }
    public string Description { get; set; }
    public string HtmlUrl { get; set; }
    public int StargazersCount { get; set; }
    public int ForksCount { get; set; }
    public DateTime UpdatedAt { get; set; }
}

public class GitHubIssue
{
    public long Id { get; set; }
    public int Number { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public string State { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class CreateIssueRequest
{
    [AliasAs("title")]
    public string Title { get; set; }
    
    [AliasAs("body")]
    public string Body { get; set; }
    
    [AliasAs("labels")]
    public string[] Labels { get; set; }
}

public class GitHubFile
{
    public string Name { get; set; }
    public string Path { get; set; }
    public string Content { get; set; }
    public string Encoding { get; set; }
}

Интерфейс клиента

[Headers("User-Agent: RefitDemo/1.0", "Accept: application/vnd.github.v3+json")]
public interface IGitHubApi
{
    [Get("/repos/{owner}/{repo}")]
    Task<GitHubRepo> GetRepositoryAsync(string owner, string repo);
    
    [Get("/repos/{owner}/{repo}/issues")]
    Task<List<GitHubIssue>> GetIssuesAsync(
        string owner, 
        string repo, 
        [Query] string state = "open",
        [Query("per_page")] int perPage = 30
    );
    
    [Post("/repos/{owner}/{repo}/issues")]
    Task<GitHubIssue> CreateIssueAsync(
        string owner, 
        string repo, 
        [Body] CreateIssueRequest request
    );
    
    [Get("/repos/{owner}/{repo}/contents/{path}")]
    Task<GitHubFile> GetFileContentAsync(
        string owner, 
        string repo, 
        string path
    );
    
    [Multipart]
    [Put("/repos/{owner}/{repo}/contents/{path}")]
    Task CreateOrUpdateFileAsync(
        string owner,
        string repo,
        string path,
        [AliasAs("message")] string commitMessage,
        [AliasAs("content")] StreamPart fileContent
    );
}

Использование клиента

using Refit;
using System.Text.Json;

class GitHubClientDemo
{
    private readonly IGitHubApi _gitHubApi;
    
    public GitHubClientDemo(string token = null)
    {
        var settings = new RefitSettings
        {
            ContentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
                WriteIndented = true
            })
        };
        
        var httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://api.github.com")
        };
        
        if (!string.IsNullOrEmpty(token))
        {
            httpClient.DefaultRequestHeaders.Authorization = 
                new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
        }
        
        _gitHubApi = RestService.For<IGitHubApi>(httpClient, settings);
    }
    
    public async Task RunAsync()
    {
        try
        {
            // 1. Получаем информацию о репозитории                    

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