Avatar ·

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

Всем привет! Я новичок в C# и столкнулся с такой проблемой. Мне нужно программно формировать URL-адреса, объединяя базовый URL с относительными путями. В .NET есть замечательный метод `Path.Combine`, который отлично работает с файловыми путями, но когда я пытаюсь использовать его для URL, получаю ерунду. Вот пример моего кода: ```csharp string baseUrl = "https://example.com/api/"; string endpoint = "users/get"; string fullUrl = Path.Combine(baseUrl, endpoint); Console.WriteLine(fullUrl); ``` Вместо ожидаемого `https://example.com/api/users/get` я получаю `https://example.com/api\users\get`. Обратные слеши вместо прямых! И если убрать завершающий слеш в `baseUrl`, то результат будет `https://example.com/api\users\get` — то есть часть пути просто теряется или склеивается неправильно. Пробовал вручную добавлять слеши или использовать `String.Concat` — но это костыльно и легко ошибиться. Например, если в `baseUrl` уже есть слеш, а я добавлю ещё один, получится двойной слеш. А если его нет — путь сломается. Подскажите, есть ли встроенный способ правильно объединять части URL? Или нужно писать свой велосипед? Буду благодарен за примеры кода и объяснения. Заранее спасибо! <<>>

Правильное решение: используйте Uri или Flurl

Проблема, которую вы описали, очень распространена. Path.Combine действительно не подходит для URL, потому что он работает с файловыми путями Windows, используя обратную косую черту. Для URL нужно использовать прямой слеш и учитывать особенности протокола.

В .NET есть несколько способов корректно объединить части URL. Рассмотрим лучшие из них.

Способ 1: Класс Uri (встроенный, без сторонних библиотек)

Самый простой и надёжный способ — использовать конструктор Uri(Uri baseUri, string relativeUri). Он автоматически корректно объединит базовый URL с относительным путём, учитывая все слеши.

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

// Создаём базовый Uri
Uri baseUri = new Uri(baseUrl);
// Объединяем с относительным путём
Uri fullUri = new Uri(baseUri, endpoint);

string fullUrl = fullUri.ToString();
Console.WriteLine(fullUrl);
// Вывод: https://example.com/api/users/get

Этот метод работает без ошибок в следующих случаях:

  • Базовый URL с завершающим слешом: https://example.com/api/ + users/get → https://example.com/api/users/get
  • Базовый URL без завершающего слеша: https://example.com/api + users/get → https://example.com/users/get (внимание: базовый путь "api" будет потерян, так как Uri интерпретирует его как файл, а не как каталог)
  • Относительный путь с начальным слешом: https://example.com/api/ + /users/get → https://example.com/users/get (начальный слеш означает "относительно корня")

Чтобы избежать сюрпризов, всегда добавляйте завершающий слеш к базовому URL, если он указывает на каталог:

string baseUrl = "https://example.com/api".TrimEnd('/') + "/"; // гарантируем слеш
Uri baseUri = new Uri(baseUrl);
Uri fullUri = new Uri(baseUri, "users/get");
Console.WriteLine(fullUri); // https://example.com/api/users/get

Способ 2: Метод Uri.TryCreate

Если вы хотите избежать исключений при некорректных данных, используйте Uri.TryCreate:

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

if (Uri.TryCreate(baseUrl, UriKind.Absolute, out Uri baseUri) &&
    Uri.TryCreate(baseUri, endpoint, out Uri fullUri))
{
    Console.WriteLine(fullUri); // https://example.com/api/users/get
}
else
{
    Console.WriteLine("Некорректный URL");
}

Способ 3: Сторонняя библиотека Flurl

Если вы часто работаете с URL, рекомендую установить пакет Flurl через NuGet. Он предоставляет элегантный fluent-интерфейс:

// Установка: Install-Package Flurl
using Flurl;

string fullUrl = "https://example.com/api"
    .AppendPathSegment("users")
    .AppendPathSegment("get")
    .ToString();

Console.WriteLine(fullUrl); // https://example.com/api/users/get

Flurl автоматически управляет слешами и поддерживает добавление query-параметров:

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

Console.WriteLine(url); // https://example.com/api/users?page=1&limit=10

Вывод

Используйте встроенный класс Uri для простых случаев — это надёжно и не требует дополнительных зависимостей. Для сложных сценариев (много сегментов, query-параметры) лучше взять Flurl — он значительно упрощает код и снижает риск ошибок.

<<>>

Альтернативный подход: ручное объединение с помощью String.Join и Trim

Если по каким-то причинам вы не хотите использовать Uri или сторонние библиотеки, можно написать простой вспомогательный метод. Он будет работать корректно, если правильно обрабатывать слеши.

public static string CombineUrl(string baseUrl, string relativePath)
{
    // Убираем завершающий слеш у базового URL (если есть)
    baseUrl = baseUrl.TrimEnd('/');
    // Убираем начальный слеш у относительного пути (если есть)
    relativePath = relativePath.TrimStart('/');
    // Объединяем через один слеш
    return $"{baseUrl}/{relativePath}";
}

// Использование:
string fullUrl = CombineUrl("https://example.com/api/", "users/get");
Console.WriteLine(fullUrl); // https://example.com/api/users/get

fullUrl = CombineUrl("https://example.com/api", "/users/get");
Console.WriteLine(fullUrl); // https://example.com/api/users/get

Этот метод подходит для простых случаев, но у него есть недостатки:

  • Не проверяет валидность URL (например, не обработает протокол или порт)
  • Не работает с query-параметрами или фрагментами (якорями)
  • Может дать неверный результат для абсолютных путей (например, если relativePath начинается с http://)

Тем не менее, для внутренних проектов или простых сценариев это быстрое и лёгкое решение. Если вам нужно что-то более надёжное — используйте Uri из первого ответа.