Flurl для C# (.NET): полное руководство с примерами
Flurl — это современная, элегантная библиотека для .NET, объединяющая в себе fluent-построитель URL и HTTP-клиент. Она создана для того, чтобы сделать работу с HTTP-запросами максимально простой, читаемой и удобной. Вместо громоздкого кода с HttpClient, UriBuilder и множеством строковых конкатенаций, Flurl предлагает лаконичный и выразительный синтаксис, напоминающий цепочки методов в LINQ.
Зачем нужен Flurl? Основная проблема стандартного HttpClient — это многословность. Чтобы отправить простой GET-запрос с параметрами и заголовками, приходится писать 10-15 строк кода. Flurl сокращает это до 2-3 строк, делая код самодокументируемым. Кроме того, Flurl решает проблему безопасного построения URL: автоматически кодирует параметры, обрабатывает пути и query-строки, предотвращая ошибки, связанные с неправильным экранированием символов.
Библиотека идеально подходит для любых сценариев, где требуется взаимодействие с REST API: от простых мобильных приложений до сложных корпоративных систем. Flurl поддерживает все основные HTTP-методы, работу с JSON, загрузку файлов, таймауты, повторные попытки и многое другое. Она полностью асинхронна и совместима с .NET Standard 2.0+, что позволяет использовать её в .NET Framework, .NET Core и .NET 5/6/7/8.
Установка
Flurl можно установить через NuGet Package Manager. Для работы с HTTP-запросами требуется пакет Flurl.Http, который включает в себя и Flurl (построитель URL).
Через .NET CLI:
dotnet add package Flurl.Http
Через Package Manager Console:
Install-Package Flurl.Http
Через Visual Studio: ПКМ на проекте → Manage NuGet Packages → ищем "Flurl.Http" → Install.
Если вам нужен только построитель URL без HTTP-клиента, можно установить:
dotnet add package Flurl
Быстрый старт
Минимальный рабочий пример — получение данных с публичного API и вывод в консоль:
using Flurl.Http;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// GET-запрос к JSON API
var response = await "https://jsonplaceholder.typicode.com/posts/1"
.GetJsonAsync<dynamic>();
Console.WriteLine($"Title: {response.title}");
Console.WriteLine($"Body: {response.body}");
}
}
Этот код делает GET-запрос, автоматически десериализует JSON-ответ в динамический объект и выводит поля. Обратите внимание: Flurl позволяет вызывать методы прямо на строке URL — это и есть "fluent" стиль.
Основные методы и классы
1. Url.Combine() — безопасное объединение URL
Сигнатура: public static string Combine(params string[] parts)
Что делает: Объединяет несколько частей URL, автоматически добавляя/удаляя лишние слеши. Предотвращает дублирование слешей.
using Flurl;
string fullUrl = Url.Combine("https://api.example.com", "/v1/", "users", "/123");
Console.WriteLine(fullUrl); // https://api.example.com/v1/users/123
2. Url.SetQueryParams() — fluent-добавление параметров
Сигнатура: public static string SetQueryParams(this string url, object values)
Что делает: Добавляет query-параметры к URL из анонимного объекта или словаря. Автоматически кодирует значения.
using Flurl;
string url = "https://api.example.com/search"
.SetQueryParams(new { q = "hello world", page = 1, limit = 10 });
Console.WriteLine(url);
// https://api.example.com/search?q=hello%20world&page=1&limit=10
3. Url.AppendPathSegment() — добавление сегмента пути
Сигнатура: public static string AppendPathSegment(this string url, string segment)
Что делает: Добавляет сегмент к пути URL, корректно обрабатывая слеши и кодирование.
using Flurl;
string url = "https://api.example.com"
.AppendPathSegment("users")
.AppendPathSegment("123")
.AppendPathSegment("profile");
Console.WriteLine(url); // https://api.example.com/users/123/profile
4. GetJsonAsync<T>() — асинхронный GET с десериализацией JSON
Сигнатура: public static Task<T> GetJsonAsync<T>(this string url, CancellationToken cancellationToken = default)
Что делает: Выполняет GET-запрос и десериализует JSON-ответ в указанный тип T.
using Flurl.Http;
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}
var post = await "https://jsonplaceholder.typicode.com/posts/1"
.GetJsonAsync<Post>();
Console.WriteLine(post.Title);
5. PostJsonAsync() — асинхронный POST с JSON-телом
Сигнатура: public static Task<IFlurlResponse> PostJsonAsync(this string url, object data, CancellationToken cancellationToken = default)
Что делает: Отправляет POST-запрос с JSON-телом, автоматически сериализуя объект в JSON.
using Flurl.Http;
var newPost = new { Title = "Foo", Body = "Bar", UserId = 1 };
var response = await "https://jsonplaceholder.typicode.com/posts"
.PostJsonAsync(newPost);
Console.WriteLine(response.StatusCode); // 201 Created
6. PutJsonAsync() — асинхронный PUT с JSON-телом
Сигнатура: public static Task<IFlurlResponse> PutJsonAsync(this string url, object data, CancellationToken cancellationToken = default)
Что делает: Отправляет PUT-запрос для обновления ресурса, передавая JSON-тело.
using Flurl.Http;
var updatedPost = new { Id = 1, Title = "Updated", Body = "Updated body", UserId = 1 };
var response = await "https://jsonplaceholder.typicode.com/posts/1"
.PutJsonAsync(updatedPost);
Console.WriteLine(response.StatusCode); // 200 OK
7. DeleteAsync() — асинхронный DELETE
Сигнатура: public static Task<IFlurlResponse> DeleteAsync(this string url, CancellationToken cancellationToken = default)
Что делает: Отправляет DELETE-запрос для удаления ресурса.
using Flurl.Http;
var response = await "https://jsonplaceholder.typicode.com/posts/1"
.DeleteAsync();
Console.WriteLine(response.StatusCode); // 200 OK
8. WithHeader() / WithHeaders() — добавление заголовков
Сигнатура: public static IFlurlRequest WithHeader(this string url, string name, string value)
Сигнатура: public static IFlurlRequest WithHeaders(this string url, object headers)
Что делает: Добавляет один или несколько HTTP-заголовков к запросу. Возвращает объект IFlurlRequest для дальнейшей настройки.
using Flurl.Http;
var response = await "https://api.example.com/protected"
.WithHeader("Authorization", "Bearer my-token")
.WithHeaders(new { Accept = "application/json", "X-Custom" = "value" })
.GetJsonAsync<dynamic>();
9. WithTimeout() — установка таймаута
Сигнатура: public static IFlurlRequest WithTimeout(this string url, TimeSpan timeSpan)
Что делает: Устанавливает максимальное время ожидания ответа от сервера.
using Flurl.Http;
try
{
var response = await "https://httpbin.org/delay/10"
.WithTimeout(TimeSpan.FromSeconds(5))
.GetAsync();
}
catch (FlurlHttpTimeoutException)
{
Console.WriteLine("Request timed out!");
}
10. AllowHttpStatus() — разрешение нестандартных HTTP-статусов
Сигнатура: public static IFlurlRequest AllowHttpStatus(this string url, params int[] statusCodes)
Что делает: По умолчанию Flurl выбрасывает исключение при статус-кодах >= 400. Этот метод позволяет указать, какие статусы не должны вызывать исключение.
using Flurl.Http;
var response = await "https://httpbin.org/status/404"
.AllowHttpStatus(404)
.GetAsync();
Console.WriteLine(response.StatusCode); // 404, без исключения
Реальный практический пример
Создадим консольное приложение для работы с GitHub API: получение информации о пользователе, его репозиториях и создание issue.
using Flurl.Http;
using Flurl;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FlurlDemo
{
// Модели для десериализации
public class GitHubUser
{
public string Login { get; set; }
public string Name { get; set; }
public string Bio { get; set; }
public int PublicRepos { get; set; }
public string HtmlUrl { get; set; }
}
public class GitHubRepo
{
public string Name { get; set; }
public string Description { get; set; }
public int StargazersCount { get; set; }
public string HtmlUrl { get; set; }
}
public class IssueRequest
{
public string Title { get; set; }
public string Body { get; set; }
public List<string> Labels { get; set; }
}
public class IssueResponse
{
public int Id { get; set; }
public string Title { get; set; }
public string State { get; set; }
public string HtmlUrl { get; set; }
}
class Program
{
private const string GitHubApiBase = "https://api.github.com";
static async Task Main(string[] args)
{
Console.WriteLine("=== GitHub API Client with Flurl ===\n");
string username = "octocat"; // Можно заменить на любой username
try
{
// 1. Получаем информацию о пользователе
Console.WriteLine($"Fetching user: {username}");
var user = await GitHubApiBase
.AppendPathSegments("users", username)
.WithHeader("User-Agent", "FlurlDemoApp")
.GetJsonAsync<GitHubUser>();
Console.WriteLine($"Name: {user.Name}");
Console.WriteLine($"Bio: {user.Bio}");
Console.WriteLine($"Public repos: {user.PublicRepos}");
Console.WriteLine($"Profile: {user.HtmlUrl}\n");
// 2. Получаем список репозиториев (первые 5)
Console.WriteLine("Fetching repositories...");
var repos = await GitHubApiBase
.AppendPathSegments("users", username, "repos")
.SetQueryParams(new { per_page = 5, sort = "updated" })
.WithHeader("User-Agent", "FlurlDemoApp")
.GetJsonAsync<List<GitHubRepo>>();
Console.WriteLine($"\nTop 5 repos for {username}:");
foreach (var repo in repos)
{
Console.WriteLine($"- {repo.Name} (⭐ {repo.StargazersCount})");
Console.WriteLine($" {repo.Description ?? "No description"}");
Console.WriteLine($" {repo.HtmlUrl}\n");
}
// 3. Создаем issue в первом репозитории (требуется токен)
// Для демонстрации используем публичный репозиторий, куда можно писать
string repoOwner = "octocat";
string repoName = "Hello-World";
// Внимание: для создания issue нужен токен!
// Замените на свой токен из настроек GitHub
string token = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
if (!string.IsNullOrEmpty(token))
{
Console.WriteLine($"Creating issue in {repoOwner}/{repoName}...");
var newIssue = new IssueRequest
{
Title = "Test issue from Flurl demo",
Body = "This is a test issue created using Flurl library.",
Labels = new List<string> { "bug", "documentation" }
};
var createdIssue = await GitHubApiBase
.AppendPathSegments("repos", repoOwner, repoName, "issues")
.WithHeader("User-Agent", "FlurlDemoApp")
.WithHeader("Authorization", $"Bearer {token}")
.WithHeader("Accept", "application/vnd.github.v3+json")
.PostJsonAsync(newIssue)
.ReceiveJson<IssueResponse>();
Console.WriteLine($"Issue created! ID: {createdIssue.Id}");
Console.WriteLine($"Title: {createdIssue.Title}");
Console.WriteLine($"State: {createdIssue.State}");
Console.WriteLine($"URL: {createdIssue.HtmlUrl}");
}
else
{
Console.WriteLine("Skipping issue creation: GITHUB_TOKEN not set.");
Console.WriteLine("Set environment variable GITHUB_TOKEN to test issue creation.");
}
}
catch (FlurlHttpException ex)
{
var errorBody = await ex.GetResponseStringAsync();
Console.WriteLine($"HTTP Error: {ex.StatusCode}");
Console.WriteLine($"Response: {errorBody}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
}
}
Этот пример демонстрирует:
- По
Также в библиотеке
Обработка и редактирование изображений на TypeScript: Jimp, Sharp, Canvas
Zod — мощная библиотека для валидации данных в TypeScript
ORM для Rust: сравнение Diesel, SeaORM, SQLx и примеры кода
Обработка изображений на Go: изменение размера и обрезка (resize, crop)