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

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

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

Начать курс

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

Этот пример демонстрирует:

  • По

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