В этом обзоре мы познакомимся с библиотекой Refit для .NET, которая значительно упрощает работу с REST API.

Refit позволяет разработать типобезопасные http клиенты с использованием интерфейсов и атрибутов, что делает код более читаемым и удобным для сопровождения.

Содержание

1. Введение
2. Основные преимущества Refit
3. Актуальность и популярность Refit
4. Описание данных для примеров
5. Как описать http запрос через Refit
6. Как инициализировать Refit клиент
7. Как настроить методы интерфейса Refit клиента
8. Сравнение реализаций http запроса System.Net.Http.HttpClient и Refit в .Net 8
9. Точечная обработка запросов (хэндлеры запросов)
10. Заключение
11. Другие ресурсы по Refit в .NET

1. Введение

Refit — это библиотека, необходимая для быстрого создания типизированных http api клиентов.

Его особенность состоит в реализации http клиента через описание интерфейса посредством использования атрибутов библиотеки Refit.

Пример описания Refit клиента:

using Refit;
public interface IUsersApiClient
{
  [Get("/users/{id}")]
  Task<User> GetUser(int id);
}
//   Пример запроса для параметров: id = 32
//   Будет сформирован следующий запрос:
//   GET https://api.example.com/users/32

Refit работает поверх HttpClient, превращая интерфейсы в http запросы. Во время создания интерфейса и определения методов с атрибутами, такими как [Get], [Post] и другими, Refit генерирует реализацию этого интерфейса, которая использует HttpClient для выполнения http запросов.

Refit автоматизирует процесс создания и отправки запросов. Можно просто вызывать методы интерфейса и Refit позаботится о формировании конечного запроса, обработке ответов, а также о десериализации данных в нужные типы.

Refit позволяет избежать ручной работы с HttpClient и снимает необходимость осуществления повторяющихся действий для работы с API, а именно: кодирования URL‑адресов, а также установки заголовков и параметров запросов.

2. Основные преимущества Refit

Продолжая свое развитие, System.Net.Http.HttpClient становится проще и понятнее в своем использовании. Refit в сравнении с System.Net.Http.HttpClient имеет главное преимущество — возможность типизации получаемых ответов без создания сервиса — обертки, а также объединение часто используемых паттернов проектирования http клиента в  один метод.

3. Актуальность и популярность Refit

Статистика по загрузкам данной библиотеки говорит о крайне положительной динамике популярности данной библиотеки на момент написания статьи. Популярность библиотеки Refit обусловлена простотой использования, автоматизацией http запросов через интерфейсы и удобной сериализацией данных.

4. Описание данных для примеров

В моих примерах я буду отправлять http запросы на некоторый сервер, который будет возвращать данные о пользователях.

Сущность пользователя описана следующим образом:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

5. Как описать http запрос через Refit

Refit клиент настраивает http запросы через описание интерфейса. Сам интерфейс является образом самого клиента, а его методы и их описание — настраивают запрос на конкретный адрес.

Подключаем библиотеку Refit нужной версии (например, через nuget‑пакеты) к проекту.

После этого создаем интерфейс http клиента. Каждый метод интерфейса должен иметь атрибут, отвечающий за тип http запроса и относительный URL‑ адрес в параметрах этого атрибута. Пример описания http запроса через Refit:

using Refit;
public interface IUsersApiClient
{
  [Get("/users/{id}")]
  Task<User> GetUser(int id, int type);
}
//   Пример запроса для параметров: id = 32, type = 1.
//   Будет сформирован следующий запрос:
//   GET https://api.example.com/users/32?type=1

Refit предоставляет работу со следующими методами: Get, Post, Put, Delete, Patch, Head, Options, Multipart.

using Refit;
public interface IUsersApiClient
{
  [Post("/users")]
  Task<User> CreateUser(User user, int type);
}
//   Пример запроса для параметров: type = 1.
//   Будет сформирован следующий запрос:
//   Post https://api.example.com/users?type=1 
//   Body запроса: {"Id":0,"Name":"Никита"}

Другие примеры из официального репозитория

6. Как инициализировать Refit клиент

Способ 1 - через механизм внедрения зависимостей (DI)

данный способ подходит только для.Net (Core)

1) Подключаем Refit.HttpClientFactory
2) Регистрируем и настраиваем клиент в Program.cs:

using Refit;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRefitClient<IUsersApiClient>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com/"));

3) Теперь можно получать этот сервис различными способами через механизм внедрения зависимостей.
Например, через конструктор сервиса:

public class UsersService
{
    private readonly IUsersApiClient _usersApiClient;

    public UsersService(IUsersApiClient usersApiClient)
    {
        _usersApiClient = usersApiClient;
    }

    public async Task<User> GetUserById(int id)
    {
        User user = await _usersApiClient.GetUser(id);
        return user;
    }
}

Или, если вы работаете с Microsoft.AspNetCore.Mvc, то можно вызвать refit клиент через HttpContext, как и любой другой зарегистрированный сервис:

var client = HttpContext.RequestServices.GetService<IUsersApiClient>();

Способ 2 - подходит как для .Net (Core), так и для .Net Framework

используем статический RestService:

using Refit;
IUsersApiClient client = RestService.For<IUsersApiClient>("https://api.example.com/");

7. Как настроить методы интерфейса Refit клиента

Посредством описания атрибутов метода и интерфейса настраивают то, как должны происходить определенные http запросы.

Этих атрибутов очень много, здесь я опишу самые часто используемые.

7.1 [AliasAs] — помогает сопоставить имена параметров в методе интерфейса с параметрами в URL

using Refit;
public interface IUsersApiClient
{
  [Get("/users/{userId}")]
  Task<User> GetUser([AliasAs("userId")]int id);
}
//userId = id

7.2 [Header] — указывает, что следующий параметр переменной будет отвечать за добавляемый заголовок в http запрос.

В этом примере будет отправлен GET‑запрос с заголовком, ключ которого «key», а значение будет описывать переменная «token»:

using Refit;
public interface IUsersApiClient
{
  [Get("/users/{id}")]
  Task<User> GetUser(int id, [Header("key")]string token);
}

7.3 [Headers] — указывает на то, какие неизменяемые заголовки будут переданы в запрос. Атрибут Headers можно указать для определенных запросов:

using Refit;
public interface IUsersApiClient
{
  [Get("/users/{id}")]
  [Headers("User-Agent: TestClient")]
  Task<User> GetUser(int id, [Header("key")]string token);
}

Также атрибут может быть применен ко всем методам (запросам), обозначенным в интерфейсе

using Refit;
[Headers("User-Agent: TestClient")]
public interface IUsersApiClient
{
  [Get("/users/{id}")]
  Task<User> GetUser(int id, [Header("key")]string token);
}

7.4 [Multipart] в библиотеке Refit используется для создания запросов, которые содержат часть данных с типом multipart/form‑data.

Это особенно полезно в следующих случаях:

  • Загрузка файлов: Когда вы хотите отправить файлы на сервер вместе с другими данными, атрибут Multipart позволяет корректно сформировать запрос с файлами и дополнительными параметрами.

  • Поддержка больших данных: При отправке больших объемов данных, использование multipart позволяет разбивать информацию на части, что может улучшить производительность и управляемость данных.

public interface IFileApiClient
{
  [Multipart]
  [Put("updateImage")]
  Task UpdateImage(StreamPart stream);
}

7.5 [Body] и [Query] используются для указания того, что параметр метода должен быть отправлен в теле HTTP-запроса (через [Body]) или в строке запроса (query string).

public interface IEventApiClient
{
  [Post]
  Task<bool> PostEvent([Query] string evnetId, [Query] string token, [Body] FaceBookRequest request);
}

8. Сравнение реализаций http запроса System.Net.Http.HttpClient и Refit в .Net 8

В этом примере я покажу способ отправки одного и того же http запроса через встроенный HttpClient и через Refit.

8.1 Реализация через System.Net.Http.HttpClient:

using System.Net.Http.Json;

int userId = 1;

//1. Объявляем и инициализируем клиент
HttpClient httpClient = new HttpClient();
//2. Отправляем запрос, получаем ответ
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync($"https://api.example.com/users/{userId}");
//3. Вызываем Exception, если код ответа не между 200 и 299
httpResponseMessage.EnsureSuccessStatusCode();
//4. Сериализуем json body в объект класса User
User user = await httpResponseMessage.Content.ReadFromJsonAsync<User>();

8.2 Реализация через Refit:

using Refit;

int userId = 1;

//1. Объявляем и инициализируем клиент
IUsersApiClient client = RestService.For<IUsersApiClient>("https://api.example.com/");
//В Refit пункты 2,3,4 из реализации через HttpClient выполняются одним методом:  
//2. Отправляем запрос, получаем ответ
//3. Вызываем Exception, если код ответа не между 200 и 299
//4. Сериализуем json body в объект класса User
User user = await client.GetUser(userId);

//0. Refit клиент обязывает нас создавать интерфейс клиента и описывать http запрос
public interface IUsersApiClient
{
    [Get("/users/{userId}")]
    Task<User> GetUser(int userId);
}

Можно заметить, что refit клиент объединяет в стандартном случае некоторые шаги, которые разделены в примере реализации через System.Net.Http.HttpClient. Это может быть полезно для создания корпоративного стиля оформления http запросов, однако усложняет работу при частом вариативном использовании клиентов.

9. Точечная обработка запросов (хэндлеры запросов)

В Refit можно обрабатывать каждый запрос, добавляя промежуточное программное обеспечение (middleware) через интерфейс HttpClient или используя обработчики сообщений. Один из способов — создать свой собственный HttpMessageHandler, который будет перехватывать и обрабатывать запросы и ответы.

Это позволяет добавить логику для аутентификации, логирования, обработки ошибок и т.д.

В примере ниже я опишу обработчик, логгирующий запросы и ответы http клиента, который будет привязан к конкретному Refit клиенту.

9.1 Создаем обработчик

public class LoggingDelegatingHandler(ILogger<LoggingDelegatingHandler> logger) : DelegatingHandler
{
	private readonly ILogger _logger = logger;

	protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
	{
		using (LogContext.PushProperty("HttpRequestId", Guid.NewGuid()))
		{
            //логгируем body запроса
			string requestBody = request.Content != null ? await request.Content.ReadAsStringAsync() : "Empty";
			_logger.LogInformation($"{request}{Environment.NewLine}Body: {requestBody}");

            //отправляем http запрос
			HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
            //обрабатываем ответ, логгируем body ответа
			string responseBody = response.Content != null ? await response.Content.ReadAsStringAsync() : "Empty";
			_logger.LogInformation($"{response}{Environment.NewLine}Body: {responseBody}");

			return response;
		}
	}
}

9.2 Настраиваем HttpClient с использованием обработчика

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Регистрация логгера
        services.AddLogging();

        // Регистрация кастомного обработчика 
        services.AddTransient<LoggingDelegatingHandler>();

        // Регистрация HttpClient и Refit клиента
        services.AddRefitClient<IUsersApiClient>()
          .ConfigureHttpClient(httpClient =>
        {
    		httpClient.BaseAddress = new Uri("https://api.example.com/");
    	})
    	.AddHttpMessageHandler<LoggingDelegatingHandler>();
    }
}

Теперь в каждом запросе к refit клиенту IUsersApiClient будет использоваться LoggingDelegatingHandler.

10. Заключение

Библиотека Refit в.NET представляет собой мощный инструмент для упрощения работы с REST API. Она позволяет разработчикам значительно сократить объем кода, необходимого для выполнения http запросов, благодаря использованию интерфейсов и автоматической генерации реализаций.

Использование Refit помогает улучшить читаемость и поддерживаемость кода, поскольку делает взаимодействие с API более декларативным. Функциональность, предоставляемая библиотекой, позволяет легко обрабатывать параметры запросов, заголовки и ответы, что делает процесс более удобным и интуитивным.

11. Другие ресурсы по Refit в .NET

Комментарии (8)


  1. megasuperlexa
    06.11.2024 12:04

    както немного запоздало, уже даже микрософт выпустила свою

    https://github.com/microsoft/kiota


    1. Neocriss
      06.11.2024 12:04

      Тем кому приходится поддерживать Legacy код спешить некуда.

      И как правило всё новое имеет более скудный функционал чем старое и зарекомендовавшее себя со временем.

      Вы сравнивали возможности Kiota с Refit ?


    1. buldo
      06.11.2024 12:04

      Kiota - генератор клиента по OpenAPI. Это вот совсем отличается от того, что делает refit.

      В нашем проекте мы просто помещаем dto в отдельную сборку, которую референсят и сборка с контроллерами и сборка с интерфейсом refit клиента. Потом это всё легко и просто улетает в nuget сервер и используется другими командами.

      С kiota придётся что-то мудрить на этапе сборки, чтобы генерировать клиент с отдельной копией моделек. Делали такое на nswag ранее. Как-то не очень зашло.


  1. Paulus
    06.11.2024 12:04

    A со swagger-то что не так?


    1. PolkovnikovNikita Автор
      06.11.2024 12:04

      Swagger и Refit - это разные вещи:

      • Swagger – для документирования и визуализации API.

      • Refit – для упрощения работы с http запросами в .NET приложениях.


  1. NeriaLab
    06.11.2024 12:04

    А чем не устраивает ServiceStack? В разы проще


  1. LuckyMeister
    06.11.2024 12:04

    Хорошая статья. Можно было бы дополнительно сравнить Refit и RestSharp. В каких случаях стоит использовать ту или иную библиотеку, когда нам достаточно будет автоматической генерации кода с помощью Refit, а когда понадобятся более гибкие возможности для настройки запросов и обработки ответов, используя RestSharp.


    1. Nivl
      06.11.2024 12:04

      https://habr.com/ru/articles/771670/

      Уже сравнивали. РестШарп гибче, но чуть медленнее