Avatar ·

Как правильно склеивать URL в C#? Аналог Path.Combine для веб-адресов

Всем привет! Я новичок в C# и пишу небольшое приложение для работы с API. Мне нужно динамически формировать URL-адреса разных эндпоинтов. Например, у меня есть базовый URL https://api.example.com/v1/ и я хочу добавить к нему путь типа /users/123 или /products?page=2.

Я попробовал использовать Path.Combine, как для обычных файловых путей, но это приводит к странным результатам. Вот пример:

string baseUrl = "https://api.example.com/v1/";
string endpoint = "/users/123";
string result = Path.Combine(baseUrl, endpoint);
Console.WriteLine(result);
// Вывод: "https:\\api.example.com\\v1\\users\\123"
// Обратите внимание: слеши в другую сторону!

Это полная ерунда! Path.Combine использует обратную косую черту, так как он предназначен для Windows. Если я попробую просто конкатенировать строки через +, то получаю проблемы с двойными слешами или их отсутствием:

string baseUrl = "https://api.example.com/v1";
string endpoint = "users/123";
string result = baseUrl + "/" + endpoint; // Работает, но некрасиво
// А если baseUrl уже заканчивается на "/"? Получится "//users/123"

Подскажите, как правильно и элегантно склеивать URL в C#? Есть ли встроенный метод, аналогичный Path.Combine, но для веб-адресов? Или нужно писать свою логику? Заранее спасибо!

<<>>

Использование класса Uri для безопасного комбинирования URL

В C# нет прямого аналога Path.Combine для URL, но есть мощный встроенный класс System.Uri, который решает эту задачу элегантно и безопасно. Он правильно обрабатывает слеши, схемы (http/https), порты и query-параметры.

Основной способ: конструктор Uri(Uri, string)

Самый простой метод — создать базовый URL как объект Uri и передать относительный путь вторым параметром:

using System;

string baseUrl = "https://api.example.com/v1/";
string endpoint = "users/123";

Uri baseUri = new Uri(baseUrl);
Uri fullUri = new Uri(baseUri, endpoint);

Console.WriteLine(fullUri.ToString());
// Вывод: https://api.example.com/v1/users/123

Этот метод автоматически:

  • Убирает лишние слеши (если baseUrl заканчивается на "/", а endpoint начинается с "/", он не дублирует их).
  • Использует правильные прямые слеши.
  • Сохраняет query-параметры, если они есть в относительном пути.
  • Работает с любыми протоколами (http, https, ftp и т.д.).

Работа с query-параметрами и фрагментами

Если вам нужно добавить query-параметры, используйте свойство Query или просто включите их в относительный путь:

string baseUrl = "https://api.example.com/v1/";
string endpoint = "users/123?page=2&limit=10";

Uri baseUri = new Uri(baseUrl);
Uri fullUri = new Uri(baseUri, endpoint);

Console.WriteLine(fullUri.ToString());
// Вывод: https://api.example.com/v1/users/123?page=2&limit=10

Использование UriBuilder для сложной сборки

Если вам нужно динамически менять схему, хост, порт или путь, используйте UriBuilder:

using System;

var builder = new UriBuilder("https", "api.example.com")
{
    Port = 443,
    Path = "/v1/users/123",
    Query = "page=2"
};

Uri result = builder.Uri;
Console.WriteLine(result.ToString());
// Вывод: https://api.example.com/v1/users/123?page=2

Метод расширения для удобства

Вы можете создать свой метод-расширение, чтобы писать код ещё короче:

public static class UriExtensions
{
    public static Uri Combine(this Uri baseUri, string relativePath)
    {
        return new Uri(baseUri, relativePath);
    }
}

// Использование:
Uri baseUri = new Uri("https://api.example.com/v1/");
Uri fullUri = baseUri.Combine("users/123");
Console.WriteLine(fullUri); // https://api.example.com/v1/users/123

Важные замечания

  • Всегда используйте Uri вместо строковой конкатенации — это предотвращает ошибки с кодированием специальных символов (пробелы, кириллица и т.д.).
  • Для абсолютных URL (схема + хост) обязательно передавайте их как Uri с конструктором, а не как строку.
  • Если вам нужно просто объединить две строки без создания объекта Uri, можно использовать string.Join с правильным слешем: string result = string.Join("/", baseUrl.TrimEnd('/'), endpoint.TrimStart('/'));, но это менее надёжный способ.
<<>>

Альтернативный подход: использование Flurl или ручная обработка строк

Если вы не хотите использовать встроенный Uri (например, в очень простых сценариях), можно применить один из следующих методов:

1. Ручная обработка с TrimEnd и TrimStart

string baseUrl = "https://api.example.com/v1/";
string endpoint = "/users/123";

string result = $"{baseUrl.TrimEnd('/')}/{endpoint.TrimStart('/')}";
Console.WriteLine(result);
// Вывод: https://api.example.com/v1/users/123

Этот способ прост, но не защищает от ошибок с query-параметрами и специальными символами.

2. Использование библиотеки Flurl

Популярная библиотека Flurl предоставляет fluent-интерфейс для работы с URL:

using Flurl;

string url = "https://api.example.com/v1/"
    .AppendPathSegment("users")
    .AppendPathSegment("123")
    .SetQueryParams(new { page = 2, limit = 10 });

Console.WriteLine(url);
// Вывод: https://api.example.com/v1/users/123?page=2&limit=10

Flurl автоматически кодирует параметры и правильно обрабатывает слеши. Для установки: Install-Package Flurl.

3. Когда какой метод выбирать?

  • Встроенный Uri — для стандартных проектов, без внешних зависимостей. Лучший выбор для большинства случаев.
  • Ручная обрезка слешей — только для простых скриптов или когда нет доступа к Uri (например, в Unity).
  • Flurl — если вы активно работаете с HTTP-запросами (через Flurl.Http) и хотите писать минималистичный код.

Избегайте конкатенации строк через + — это самый ненадёжный способ, особенно при работе с пользовательским вводом.