Path.Combine для URL?

1244

Path.Combine удобен, но есть ли подобная функция в .NET Framework для URL ?

Я ищу синтаксис, как это:

Url.Combine("http://MyUrl.com/", "/Images/Image.jpg")

который бы вернулся:

"http://MyUrl.com/Images/Image.jpg"

Брайан Маккей
источник
14
Flurl включает Url.Combineметод, который делает именно это.
Тодд Меньер
2
На самом деле // обрабатывается маршрутизацией веб-сайта или сервера, а не браузером. Он отправит то, что вы положили в адресную строку. Вот почему у нас возникают проблемы, когда мы вводим htp: // вместо http: // Таким образом, // может вызвать серьезные проблемы на некоторых сайтах. Я пишу .dll для сканера, который обрабатывает определенный веб-сайт, который выбрасывает 404, если у вас есть // в URL.
Дэйв Гордон,

Ответы:

73

Существует комментарий Тодда Меньера, что Flurl включает в себя Url.Combine.

Больше деталей:

Url.Combine - это, по сути, Path.Combine для URL, обеспечивающий один и только один символ-разделитель между частями:

var url = Url.Combine(
    "http://MyUrl.com/",
    "/too/", "/many/", "/slashes/",
    "too", "few?",
    "x=1", "y=2"
// result: "http://www.MyUrl.com/too/many/slashes/too/few?x=1&y=2" 

Получить Flurl.Http на NuGet :

PM> Install-Package Flurl.Http

Или получите автономный построитель URL без функций HTTP:

PM> Install-Package Flurl

Майкл Фрейдгейм
источник
4
Ну, этот вопрос получает много трафика, и ответ с 1000+ upvotes на самом деле не работает во всех случаях. Спустя годы я фактически использую Flurl для этого, поэтому я принимаю этот. Кажется, работает во всех случаях, с которыми я столкнулся. Если люди не хотят получать зависимость, я разместил ответ, который также отлично работает.
Брайан Маккей
и если вы не используете Flurlи предпочитаете облегченную версию, github.com/jean-lourenco/UrlCombine
lizzy91
1157

Uri есть конструктор, который должен сделать это для вас: new Uri(Uri baseUri, string relativeUri)

Вот пример:

Uri baseUri = new Uri("http://www.contoso.com");
Uri myUri = new Uri(baseUri, "catalog/shownew.htm");

Примечание редактора: будьте осторожны, этот метод не работает должным образом. В некоторых случаях он может вырезать часть baseUri. Смотрите комментарии и другие ответы.

Джоэл Бекхэм
источник
369
Мне нравится использование класса Uri, к сожалению, он не будет вести себя как Path.Combine, как спросил ОП. Например, новый Uri (новый Uri (« test.com/mydirectory/» ), «/helloworld.aspx»). ToString () дает вам « test.com/helloworld.aspx »; что было бы неправильно, если бы мы хотели получить результат в стиле Path.Combine.
Доктор Джонс
195
Это все в разрезе. Если относительная часть пути начинается с косой черты, она ведет себя так, как вы описали. Но если вы оставите косую черту, то она будет работать так, как вы ожидаете (обратите внимание на отсутствующую косую черту во втором параметре): new Uri (new Uri (" test.com/mydirectory/" ), "helloworld.aspx") ) .ToString () приводит к « test.com/mydirectory/helloworld.aspx ». Path.Combine ведет себя аналогично. Если параметр относительного пути начинается с косой черты, он возвращает только относительный путь и не объединяет их.
Джоэл Бекхэм
70
Если ваш baseUri оказался «test.com/mydirectory/mysubdirectory», то результатом будет «test.com/mydirectory/helloworld.aspx» вместо «test.com/mydirectory/mysubdirectory/helloworld.aspx». Тонкая разница - отсутствие косой черты по первому параметру. Я целиком и полностью использую существующие методы фреймворка. Если у меня уже есть косая черта, то я думаю, что выполнение partUrl1 + partUrl2 пахнет намного меньше - я мог бы довольно долго гоняться за этой косой чертой, ради того, чтобы не делать строки concat.
Карл
64
Единственная причина, по которой мне нужен метод комбинирования URI, заключается в том, что мне не нужно проверять конечный слеш. Request.ApplicationPath - это «/», если ваше приложение находится в корневом каталоге, но «/ foo», если это не так.
nickd
24
Я -1 этот ответ, потому что это не отвечает на проблему. Когда вы хотите объединить URL, например, когда вы хотите использовать Path.Combine, вы не хотите заботиться о трейлинг /. и с этим вы должны заботиться. Я предпочитаю решение Брайана Маккея или mdsharpe выше
Baptiste Pernet
161

Это может быть достаточно простым решением:

public static string Combine(string uri1, string uri2)
{
    uri1 = uri1.TrimEnd('/');
    uri2 = uri2.TrimStart('/');
    return string.Format("{0}/{1}", uri1, uri2);
}
Мэтью Шарп
источник
7
+1: хотя это не относится к путям относительного стиля (../../whwhat.html), мне нравится этот из-за его простоты. Я бы также добавил обрезки для символа '\'.
Брайан Маккей
3
Смотрите мой ответ для более полной версии этого.
Брайан Маккей
149

Вы используете Uri.TryCreate( ... ):

Uri result = null;

if (Uri.TryCreate(new Uri("http://msdn.microsoft.com/en-us/library/"), "/en-us/library/system.uri.trycreate.aspx", out result))
{
    Console.WriteLine(result);
}

Вернусь:

http://msdn.microsoft.com/en-us/library/system.uri.trycreate.aspx

Райан Кук
источник
53
+1: это хорошо, хотя у меня иррациональная проблема с выходным параметром. ;)
Брайан Маккей
10
@Brian: если это помогает, все методы TryXXX ( int.TryParse, DateTime.TryParseExact) имеют этот выходной параметр, чтобы их было проще использовать в операторе if. Кстати, вам не нужно инициализировать переменную, как это сделал Райан в этом примере.
Авель
41
Этот ответ страдает той же проблемой, что и Джоэл : присоединение test.com/mydirectory/и /helloworld.aspxприведет к тому, test.com/helloworld.aspxчто, казалось бы, не то, что вы хотите.
Matt Kocaj
3
Здравствуйте, это не удалось для следующего: if (Uri.TryCreate (new Uri (" localhost / MyService /" ), "/ Event / SomeMethod? Abc = 123", out result)) {Console.WriteLine (result); } Это показывает мне результат как: localhost / Event / SomeMethod? Abc = 123 Примечание: здесь «http: //» заменяется из базового Uri на stackoverflow
Faisal Mq
3
@FaisalMq Это правильное поведение, поскольку вы передали второй относительный параметр root. Если бы вы пропустили ведущий / второй параметр, вы получили бы ожидаемый результат.
Том Линт
127

Здесь уже есть несколько отличных ответов. Основываясь на предложении mdsharpe, вот метод расширения, который можно легко использовать, когда вы хотите иметь дело с экземплярами Uri:

using System;
using System.Linq;

public static class UriExtensions
{
    public static Uri Append(this Uri uri, params string[] paths)
    {
        return new Uri(paths.Aggregate(uri.AbsoluteUri, (current, path) => string.Format("{0}/{1}", current.TrimEnd('/'), path.TrimStart('/'))));
    }
}

И пример использования:

var url = new Uri("http://example.com/subpath/").Append("/part1/", "part2").AbsoluteUri;

Это создаст http://example.com/subpath/part1/part2

Алесь Поточник Хахонина
источник
2
Это решение упрощает написание статического метода UriUtils.Combine ("base url", "part1", "part2", ...), который очень похож на Path.Combine (). Ницца!
angularsen
Для поддержки относительных URI мне пришлось использовать ToString () вместо AbsoluteUri и UriKind.AbsoluteOrRelative в конструкторе Uri.
angularsen
Спасибо за совет об относительной Юрис. К сожалению, Uri не позволяет легко иметь дело с относительными путями, поскольку всегда существует некоторая путаница с вовлеченным Request.ApplicationPath. Возможно, вы также можете попробовать использовать новый Uri (HttpContext.Current.Request.ApplicationPath) в качестве базы и просто вызвать на нем команду Append? Это даст вам абсолютные пути, но должно работать в любом месте структуры сайта.
Алесь Поточник Хахонина
Отлично. Рад, что это помогло кому-то еще. Использовал это в течение некоторого времени и не имел никаких проблем.
Алесь Поточник Хахонина
Я также добавил проверку, если какой-либо из путей для добавления не является ни нулевой, ни пустой строкой.
н.подбельски
92

Ответ Райана Кука близок к тому, что мне нужно, и может быть более подходящим для других разработчиков. Тем не менее, он добавляет http: // в начало строки и в целом он делает немного больше форматирования, чем я после.

Кроме того, для моих случаев использования определение относительных путей не важно.

Ответ mdsharp также содержит в себе идею хорошей идеи, хотя для ее фактической реализации потребовалось еще несколько подробностей. Это попытка уточнить это (и я использую это в производстве):

C #

public string UrlCombine(string url1, string url2)
{
    if (url1.Length == 0) {
        return url2;
    }

    if (url2.Length == 0) {
        return url1;
    }

    url1 = url1.TrimEnd('/', '\\');
    url2 = url2.TrimStart('/', '\\');

    return string.Format("{0}/{1}", url1, url2);
}

VB.NET

Public Function UrlCombine(ByVal url1 As String, ByVal url2 As String) As String
    If url1.Length = 0 Then
        Return url2
    End If

    If url2.Length = 0 Then
        Return url1
    End If

    url1 = url1.TrimEnd("/"c, "\"c)
    url2 = url2.TrimStart("/"c, "\"c)

    Return String.Format("{0}/{1}", url1, url2)
End Function

Этот код проходит следующий тест, который происходит в VB:

<TestMethod()> Public Sub UrlCombineTest()
    Dim target As StringHelpers = New StringHelpers()

    Assert.IsTrue(target.UrlCombine("test1", "test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("test1/", "test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("test1", "/test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("test1/", "/test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("/test1/", "/test2/") = "/test1/test2/")
    Assert.IsTrue(target.UrlCombine("", "/test2/") = "/test2/")
    Assert.IsTrue(target.UrlCombine("/test1/", "") = "/test1/")
End Sub
Брайан Маккей
источник
4
Говоря о деталях: как насчет обязательного, ArgumentNullException("url1")если аргумент Nothing? Извините, просто придирчивый ;-). Обратите внимание, что обратный слеш не имеет ничего общего с URI (и, если он есть, его не следует обрезать), так что вы можете удалить его из TrimXXX.
Авель
4
Вы можете использовать params string [] и рекурсивно объединять их, чтобы разрешить более 2 комбинаций
Jaider
4
Я уверен, что хотелось бы, чтобы это было в библиотеке базовых классов, как Path.Combine.
Урия Блатервик
1
@MarkHurd Я снова отредактировал код, так что он по поведению такой же, как C #, и синтаксически эквивалентен.
JJS
1
@BrianMacKay, я сломал это, Маркхурд указал на мою ошибку и откатился, я обновил снова ... ура
JJS
36

Path.Combine не работает для меня, потому что могут быть такие символы, как "|" в аргументах QueryString и, следовательно, в URL, что приведет к ArgumentException.

Я сначала попробовал новый Uri(Uri baseUri, string relativeUri)подход, который потерпел неудачу для меня из-за URI как http://www.mediawiki.org/wiki/Special:SpecialPages:

new Uri(new Uri("http://www.mediawiki.org/wiki/"), "Special:SpecialPages")

приведет к Special: SpecialPages, потому что двоеточие после Specialэтого обозначает схему.

Поэтому мне, наконец, пришлось выбрать маршрут mdsharpe / Brian MacKays, и я разработал его немного дальше, чтобы работать с несколькими частями URI:

public static string CombineUri(params string[] uriParts)
{
    string uri = string.Empty;
    if (uriParts != null && uriParts.Length > 0)
    {
        char[] trims = new char[] { '\\', '/' };
        uri = (uriParts[0] ?? string.Empty).TrimEnd(trims);
        for (int i = 1; i < uriParts.Length; i++)
        {
            uri = string.Format("{0}/{1}", uri.TrimEnd(trims), (uriParts[i] ?? string.Empty).TrimStart(trims));
        }
    }
    return uri;
}

Применение: CombineUri("http://www.mediawiki.org/", "wiki", "Special:SpecialPages")

Майк Фукс
источник
1
+1: Теперь мы говорим ... Я собираюсь попробовать это. Это может даже оказаться новым принятым ответом. После попытки нового метода Uri () он мне действительно не нравится. Слишком привередливый.
Брайан Маккей
Это именно то, что мне было нужно! Не был фанатом необходимости заботиться о том, где я ставлю косые черты и т. Д.
Громер
+1 за бросок в нулевой проверке, чтобы она не взорвалась.
NightOwl888
Count () должен быть Length, чтобы вам не нужно было включать Linq в вашу библиотеку только для этого.
PRMan
Это было именно то, что я искал.
ThePeter
34

Основываясь на приведенном вами примере URL , я предполагаю, что вы хотите объединить URL, относящиеся к вашему сайту.

Исходя из этого предположения, я предложу это решение как наиболее подходящий ответ на ваш вопрос: «Path.Combine удобен, есть ли подобная функция в структуре для URL-адресов?»

Поскольку в структуре для URL-адресов есть аналогичная функция, я предлагаю правильный метод: «VirtualPathUtility.Combine». Вот ссылка на MSDN: VirtualPathUtility.Combine Метод

Есть одно предостережение: я считаю, что это работает только для URL, относящихся к вашему сайту (то есть вы не можете использовать его для создания ссылок на другой веб-сайт. Например, var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).

Джеронимо Колон III
источник
+1, потому что это близко к тому, что я ищу, хотя было бы идеально, если бы он работал для любого старого URL. Я удвою это получится намного элегантнее, чем предложено mdsharpe.
Брайан Маккей
2
Предостережение правильное, оно не может работать с абсолютным URIS, и результат всегда является относительным от корня. Но у него есть дополнительное преимущество: он обрабатывает тильду, как и «~ /». Это делает это ярлыком Server.MapPathи объединением.
Авель
25
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
JeremyWeir
источник
12
path.Replace(Path.DirectorySeparatorChar, '/');
Jaider
5
path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
SliverNinja - MSFT
1
Для того, чтобы получить его Wrk U необходимо удалить первый / второй в агд т.е. "/ Images" - / Path.Combine ( " Http://MyUrl.com ", "Images / Image.jpg")
Per G
8
@SliverNinja Это неверно . Значение этого поля - обратная косая черта ('\') в UNIX и косая черта ('/') в операционных системах Windows и Macintosh. При использовании Mono в системе Linux вы получите неправильный разделитель.
user247702
6
Все те, кто отвлекается на разделитель каталогов, забывают, что строки могли прийти из другой ОС, чем вы сейчас. Просто замените обратную косую черту на переднюю, и все будет в порядке.
JeremyWeir
17

Я просто собрал небольшой метод расширения:

public static string UriCombine (this string val, string append)
        {
            if (String.IsNullOrEmpty(val)) return append;
            if (String.IsNullOrEmpty(append)) return val;
            return val.TrimEnd('/') + "/" + append.TrimStart('/');
        }

Это можно использовать так:

"www.example.com/".UriCombine("/images").UriCombine("first.jpeg");
Урза
источник
12

Остроумный пример, Райан, чтобы закончить ссылкой на функцию. Отлично сработано.

Одна рекомендация Брайана: если вы заключите этот код в функцию, вы можете использовать UriBuilder для переноса базового URL-адреса перед вызовом TryCreate.

В противном случае базовый URL ДОЛЖЕН включать схему (где UriBuilder примет http: //). Просто мысль:

public string CombineUrl(string baseUrl, string relativeUrl) {
    UriBuilder baseUri = new UriBuilder(baseUrl);
    Uri newUri;

    if (Uri.TryCreate(baseUri.Uri, relativeUrl, out newUri))
        return newUri.ToString();
    else
        throw new ArgumentException("Unable to combine specified url values");
}
mtazva
источник
10

Простой способ объединить их и убедиться, что это всегда правильно:

string.Format("{0}/{1}", Url1.Trim('/'), Url2);
Alex
источник
+1, хотя это очень похоже на ответ mdsharpe, который я улучшил в своем ответе. Эта версия прекрасно работает, если Url2 не начинается с / или \, или Url1 случайно не заканчивается на \, или один из них не пуст! :)
Брайан Маккей
9

Объединение нескольких частей URL может быть немного сложнее. Вы можете использовать двухпараметрический конструктор Uri(baseUri, relativeUri)или Uri.TryCreate()функцию полезности.

В любом случае, вы могли бы в конечном итоге возвращаются неправильный результат , потому что эти методы продолжают усечения относительные части от первого параметра baseUri, то есть с чем - то вроде http://google.com/some/thingк http://google.com.

Чтобы иметь возможность объединить несколько частей в окончательный URL-адрес, вы можете скопировать две функции ниже:

    public static string Combine(params string[] parts)
    {
        if (parts == null || parts.Length == 0) return string.Empty;

        var urlBuilder = new StringBuilder();
        foreach (var part in parts)
        {
            var tempUrl = tryCreateRelativeOrAbsolute(part);
            urlBuilder.Append(tempUrl);
        }
        return VirtualPathUtility.RemoveTrailingSlash(urlBuilder.ToString());
    }

    private static string tryCreateRelativeOrAbsolute(string s)
    {
        System.Uri uri;
        System.Uri.TryCreate(s, UriKind.RelativeOrAbsolute, out uri);
        string tempUrl = VirtualPathUtility.AppendTrailingSlash(uri.ToString());
        return tempUrl;
    }

Полный код с модульными тестами для демонстрации использования можно найти по адресу https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs.

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

Введите описание изображения здесь

Believe2014
источник
2
Выглядит довольно хорошо для меня. Хотя вы можете заменить цикл I циклом foreach для большей ясности.
Крис Марисик
Спасибо, Крис. Я только что изменил свой код для использования Foreach.
Believe2014
1
+1 за все дополнительные усилия. Мне нужно немного поддержать этот вопрос для некоторых ответов с более высоким рейтингом, вы бросили вызов. ;)
Брайан Маккей,
Извините, но не достаточно. Работает в тех немногих случаях, которые вы показываете, но далеко не пригоден для использования в любых комбинациях. Например, двоеточие на пути причинит вред.
Габор
Можете ли вы привести пример того, что вы имеете в виду? Я буду рад решить проблему и помочь следующим пользователям.
Believe2014
7

Я нашел, UriBuilderработал действительно хорошо для такого рода вещей:

UriBuilder urlb = new UriBuilder("http", _serverAddress, _webPort, _filePath);
Uri url = urlb.Uri;
return url.AbsoluteUri;

Посмотрите Класс UriBuilder - MSDN для большего количества конструкторов и документации.

javajavajavajavajava
источник
4

Вот метод Microsoft (OfficeDev PnP) UrlUtility.Combine :

    const char PATH_DELIMITER = '/';

    /// <summary>
    /// Combines a path and a relative path.
    /// </summary>
    /// <param name="path"></param>
    /// <param name="relative"></param>
    /// <returns></returns>
    public static string Combine(string path, string relative) 
    {
        if(relative == null)
            relative = String.Empty;

        if(path == null)
            path = String.Empty;

        if(relative.Length == 0 && path.Length == 0)
            return String.Empty;

        if(relative.Length == 0)
            return path;

        if(path.Length == 0)
            return relative;

        path = path.Replace('\\', PATH_DELIMITER);
        relative = relative.Replace('\\', PATH_DELIMITER);

        return path.TrimEnd(PATH_DELIMITER) + PATH_DELIMITER + relative.TrimStart(PATH_DELIMITER);
    }

Источник: GitHub

Крис Марисик
источник
Похоже, что это может быть для путей, а не URL-адресов.
Брайан Маккей
@BrianMacKay Согласился, что это похоже на это, но это из класса UrlUtility и используется в контексте объединения URL
2
Отредактировано, чтобы уточнить, к какому классу он принадлежит
Будьте осторожны при использовании этого класса, остальная часть класса содержит специфичные для SharePoint артефакты.
Гарри Берри
4

Я считаю следующее полезным и имеет следующие функции:

  • Броски на ноль или пробел
  • Принимает несколько paramsпараметров для нескольких сегментов URL
  • бросает на ноль или пусто

Учебный класс

public static class UrlPath
{
   private static string InternalCombine(string source, string dest)
   {
      if (string.IsNullOrWhiteSpace(source))
         throw new ArgumentException("Cannot be null or white space", nameof(source));

      if (string.IsNullOrWhiteSpace(dest))
         throw new ArgumentException("Cannot be null or white space", nameof(dest));

      return $"{source.TrimEnd('/', '\\')}/{dest.TrimStart('/', '\\')}";
   }

   public static string Combine(string source, params string[] args) 
       => args.Aggregate(source, InternalCombine);
}

тесты

UrlPath.Combine("test1", "test2");
UrlPath.Combine("test1//", "test2");
UrlPath.Combine("test1", "/test2");

// Result = test1/test2

UrlPath.Combine(@"test1\/\/\/", @"\/\/\\\\\//test2", @"\/\/\\\\\//test3\") ;

// Result = test1/test2/test3

UrlPath.Combine("/test1/", "/test2/", null);
UrlPath.Combine("", "/test2/");
UrlPath.Combine("/test1/", null);

// Throws an ArgumentException
Генерал
источник
@PeterMortensen спасибо за редактирование
TheGeneral
Некоторые проблемы с тестами: // Result = test1 / test2 / test3 \ для 4-го и последний из тестов throws дает ArgumentNullException вместо ArgumentException
Мория
3

Мое общее решение:

public static string Combine(params string[] uriParts)
{
    string uri = string.Empty;
    if (uriParts != null && uriParts.Any())
    {
        char[] trims = new char[] { '\\', '/' };
        uri = (uriParts[0] ?? string.Empty).TrimEnd(trims);

        for (int i = 1; i < uriParts.Length; i++)
        {
            uri = string.Format("{0}/{1}", uri.TrimEnd(trims), (uriParts[i] ?? string.Empty).TrimStart(trims));
        }
    }

    return uri;
}
Алекс Титаренко
источник
Этот вспомогательный метод очень гибок и хорошо работает во многих различных случаях использования. Спасибо!
Шива
3

Я создал эту функцию, которая сделает вашу жизнь проще:

    /// <summary>
    /// The ultimate Path combiner of all time
    /// </summary>
    /// <param name="IsURL">
    /// true - if the paths are Internet URLs, false - if the paths are local URLs, this is very important as this will be used to decide which separator will be used.
    /// </param>
    /// <param name="IsRelative">Just adds the separator at the beginning</param>
    /// <param name="IsFixInternal">Fix the paths from within (by removing duplicate separators and correcting the separators)</param>
    /// <param name="parts">The paths to combine</param>
    /// <returns>the combined path</returns>
    public static string PathCombine(bool IsURL , bool IsRelative , bool IsFixInternal , params string[] parts)
    {
        if (parts == null || parts.Length == 0) return string.Empty;
        char separator = IsURL ? '/' : '\\';

        if (parts.Length == 1 && IsFixInternal)
        {
            string validsingle;
            if (IsURL)
            {
                validsingle = parts[0].Replace('\\' , '/');
            }
            else
            {
                validsingle = parts[0].Replace('/' , '\\');
            }
            validsingle = validsingle.Trim(separator);
            return (IsRelative ? separator.ToString() : string.Empty) + validsingle;
        }

        string final = parts
            .Aggregate
            (
            (string first , string second) =>
            {
                string validfirst;
                string validsecond;
                if (IsURL)
                {
                    validfirst = first.Replace('\\' , '/');
                    validsecond = second.Replace('\\' , '/');
                }
                else
                {
                    validfirst = first.Replace('/' , '\\');
                    validsecond = second.Replace('/' , '\\');
                }
                var prefix = string.Empty;
                if (IsFixInternal)
                {
                    if (IsURL)
                    {
                        if (validfirst.Contains("://"))
                        {
                            var tofix = validfirst.Substring(validfirst.IndexOf("://") + 3);
                            prefix = validfirst.Replace(tofix , string.Empty).TrimStart(separator);

                            var tofixlist = tofix.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);

                            validfirst = separator + string.Join(separator.ToString() , tofixlist);
                        }
                        else
                        {
                            var firstlist = validfirst.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);
                            validfirst = string.Join(separator.ToString() , firstlist);
                        }

                        var secondlist = validsecond.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);
                        validsecond = string.Join(separator.ToString() , secondlist);
                    }
                    else
                    {
                        var firstlist = validfirst.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);
                        var secondlist = validsecond.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);

                        validfirst = string.Join(separator.ToString() , firstlist);
                        validsecond = string.Join(separator.ToString() , secondlist);
                    }
                }
                return prefix + validfirst.Trim(separator) + separator + validsecond.Trim(separator);
            }
            );
        return (IsRelative ? separator.ToString() : string.Empty) + final;
    }

Он работает как для URL, так и для обычных путей.

Применение:

    // Fixes internal paths
    Console.WriteLine(PathCombine(true , true , true , @"\/\/folder 1\/\/\/\\/\folder2\///folder3\\/" , @"/\somefile.ext\/\//\"));
    // Result: /folder 1/folder2/folder3/somefile.ext

    // Doesn't fix internal paths
    Console.WriteLine(PathCombine(true , true , false , @"\/\/folder 1\/\/\/\\/\folder2\///folder3\\/" , @"/\somefile.ext\/\//\"));
    //result : /folder 1//////////folder2////folder3/somefile.ext

    // Don't worry about URL prefixes when fixing internal paths
    Console.WriteLine(PathCombine(true , false , true , @"/\/\/https:/\/\/\lul.com\/\/\/\\/\folder2\///folder3\\/" , @"/\somefile.ext\/\//\"));
    // Result: https://lul.com/folder2/folder3/somefile.ext

    Console.WriteLine(PathCombine(false , true , true , @"../../../\\..\...\./../somepath" , @"anotherpath"));
    // Result: \..\..\..\..\...\.\..\somepath\anotherpath
bigworld12
источник
3

Почему бы просто не использовать следующее.

System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")
Andreas
источник
Я искал версию PowerShell этого , который будет: [System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")однако это не удается с результатом: /Images/Image.jpg. Удалите /из второго subPath, и это работает:[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
Underverse
Хорошая идея, но она терпит неудачу, когда один из параметров имеет значение null.
pholpar
2

Правила при объединении URL с URI

Чтобы избежать странного поведения, есть одно правило:

  • Путь (каталог) должен заканчиваться на '/'. Если путь заканчивается без '/', последняя часть обрабатывается как имя файла и будет объединена при попытке объединить со следующей частью URL.
  • Есть одно исключение: базовый URL-адрес (без информации каталога) не должен заканчиваться на '/'
  • часть пути не должна начинаться с '/'. Если он начинается с '/', каждая существующая относительная информация из URL удаляется ... добавление string.Emptyпути к детали также удалит относительный каталог из URL!

Если вы следуете правилам выше, вы можете комбинировать URL с кодом ниже. В зависимости от вашей ситуации, вы можете добавить несколько частей «каталога» в URL ...

        var pathParts = new string[] { destinationBaseUrl, destinationFolderUrl, fileName };

        var destination = pathParts.Aggregate((left, right) =>
        {
            if (string.IsNullOrWhiteSpace(right))
                return left;

            return new Uri(new Uri(left), right).ToString();
        });
Бахи
источник
2

Если вы не хотите добавлять стороннюю зависимость, такую ​​как Flurl, или создавать собственный метод расширения, в ASP.NET Core (также доступен в Microsoft.Owin), вы можете использовать тот, PathStringкоторый предназначен для создания URI пути. Затем вы можете создать свой полный URI, используя комбинацию этого Uriи UriBuilder.

В этом случае это будет:

new Uri(new UriBuilder("http", "MyUrl.com").Uri, new PathString("/Images").Add("/Image.jpg").ToString())

Это дает вам все составные части без необходимости указывать разделители в базовом URL. К сожалению, PathStringтребует, чтобы /к каждой строке был добавлен префикс, в противном случае он фактически выбрасывает ArgumentException! Но, по крайней мере, вы можете создать свой URI детерминистическим способом, который легко тестируется юнитами.

Нео
источник
2

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

Я не хотел разделять свой BaseUrl (который может содержать часть пути - например, http://mybaseurl.com/dev/ ), как это сделал javajavajavajavajava .

Следующий фрагмент кода показывает код + тесты.

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

  public class Tests
  {
         public static string CombineUrl (string baseUrl, string path)
         {
           var uriBuilder = new UriBuilder (baseUrl);
           uriBuilder.Path = Path.Combine (uriBuilder.Path, path);
           return uriBuilder.ToString();
         }

         [TestCase("http://MyUrl.com/", "/Images/Image.jpg", "http://myurl.com:80/Images/Image.jpg")]
         [TestCase("http://MyUrl.com/basePath", "/Images/Image.jpg", "http://myurl.com:80/Images/Image.jpg")]
         [TestCase("http://MyUrl.com/basePath", "Images/Image.jpg", "http://myurl.com:80/basePath/Images/Image.jpg")]
         [TestCase("http://MyUrl.com/basePath/", "Images/Image.jpg", "http://myurl.com:80/basePath/Images/Image.jpg")]
         public void Test1 (string baseUrl, string path, string expected)
         {
           var result = CombineUrl (baseUrl, path);

           Assert.That (result, Is.EqualTo (expected));
         }
  }

Протестировано с .NET Core 2.1 на Windows 10.

Почему это работает?

Даже если Path.Combineбудет возвращаться обратная косая черта (по крайней мере в Windows), UriBuilder обрабатывает этот случай в Setter of Path.

Взято из https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs (обращайтесь к string.Replace)

[AllowNull]
public string Path
{
      get
      {
          return _path;
      }
      set
      {
          if ((value == null) || (value.Length == 0))
          {
              value = "/";
          }
          _path = Uri.InternalEscapeString(value.Replace('\\', '/'));
          _changed = true;
      }
 }

Это лучший подход?

Конечно, это решение довольно самоописуемо (по крайней мере, на мой взгляд). Но вы полагаетесь на недокументированную (по крайней мере, я ничего не нашел с помощью быстрого поиска в Google) «функцию» из .NET API. Это может измениться в будущем выпуске, поэтому, пожалуйста, опишите метод с помощью тестов.

В https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs ( Path_Get_Set) есть тесты, которые проверяют \правильность преобразования.

Примечание: можно также работать со UriBuilder.Uriсвойством напрямую, если URI будет использоваться дляSystem.Uri ctor uri.

Тобиас Шварцингер
источник
Это очень надежный подход. Недурно для модульного теста!
Aggsol
2

Для тех, кто ищет однострочник и просто хочет соединить части пути, не создавая новый метод, не ссылаясь на новую библиотеку, не создавая значение URI и не преобразовывая его в строку, тогда ...

string urlToImage = String.Join("/", "websiteUrl", "folder1", "folder2", "folder3", "item");

Это довольно просто, но я не вижу, что тебе еще нужно. Если вы боитесь удвоить «/», то вы можете просто сделать .Replace("//", "/")потом. Если вы боитесь заменить удвоенный «//» в «https: //», вместо этого сделайте одно объединение, замените удвоенный «/», а затем присоединитесь к URL веб-сайта (однако я уверен, что большинство браузеров автоматически преобразовать что-нибудь с «https:» перед ним, чтобы прочитать в правильном формате). Это будет выглядеть так:

string urlToImage = String.Join("/","websiteUrl", String.Join("/", "folder1", "folder2", "folder3", "item").Replace("//","/"));

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

См .: https://docs.microsoft.com/en-us/dotnet/api/system.string.join?view=netframework-4.8.

DubDub
источник
1

Использование:

    private Uri UriCombine(string path1, string path2, string path3 = "", string path4 = "")
    {
        string path = System.IO.Path.Combine(path1, path2.TrimStart('\\', '/'), path3.TrimStart('\\', '/'), path4.TrimStart('\\', '/'));
        string url = path.Replace('\\','/');
        return new Uri(url);
    }

Он имеет то преимущество, что ведет себя точно так же Path.Combine.

TruthOf42
источник
1

Вот мой подход, и я буду использовать его и для себя:

public static string UrlCombine(string part1, string part2)
{
    string newPart1 = string.Empty;
    string newPart2 = string.Empty;
    string seperator = "/";

    // If either part1 or part 2 is empty,
    // we don't need to combine with seperator
    if (string.IsNullOrEmpty(part1) || string.IsNullOrEmpty(part2))
    {
        seperator = string.Empty;
    }

    // If part1 is not empty,
    // remove '/' at last
    if (!string.IsNullOrEmpty(part1))
    {
        newPart1 = part1.TrimEnd('/');
    }

    // If part2 is not empty,
    // remove '/' at first
    if (!string.IsNullOrEmpty(part2))
    {
        newPart2 = part2.TrimStart('/');
    }

    // Now finally combine
    return string.Format("{0}{1}{2}", newPart1, seperator, newPart2);
}
Амит Бхагат
источник
Это приемлемо только для вашего случая. Есть случаи, которые могут сломать ваш код. Кроме того, вы не сделали правильное кодирование частей пути. Это может быть огромной уязвимостью, когда речь идет о межсайтовых скриптовых атаках.
Believe2014
Я согласен с вашими пунктами. Код должен делать простое объединение двух частей url.
Амит Бхагат
1

Использовать этот:

public static class WebPath
{
    public static string Combine(params string[] args)
    {
        var prefixAdjusted = args.Select(x => x.StartsWith("/") && !x.StartsWith("http") ? x.Substring(1) : x);
        return string.Join("/", prefixAdjusted);
    }
}
Мартин Мерфи
источник
Приятное прикосновение с «WebPath». :) Код может быть излишне плотным, хотя мне трудно взглянуть на это и сказать: да, это прекрасно. Это заставляет меня хотеть видеть юнит-тесты. Может быть, это только я!
Брайан Маккей
1
x.StartsWith ("/") &&! x.StartsWith ("http") - почему проверка http? что ты получаешь?
пингват
Вы не хотите пытаться удалить слеш, если он начинается с http.
Мартин Мерфи,
@BrianMacKay, я не уверен, что два лайнера требуют модульного теста, но, если хотите, можете его предоставить. Не то чтобы я принимаю патчи или что-то еще, но не стесняйтесь редактировать предложение.
Мартин Мерфи,
1

Я обнаружил, что Uriконструктор переворачивает '\' в '/'. Таким образом, вы также можете использовать Path.Combine, с Uriконструктором.

 Uri baseUri = new Uri("http://MyUrl.com");
 string path = Path.Combine("Images", "Image.jpg");
 Uri myUri = new Uri(baseUri, path);
Скиппи
источник
1

Для чего это стоит, здесь пара методов расширения. Первый объединяет пути, а второй добавляет параметры в URL.

    public static string CombineUrl(this string root, string path, params string[] paths)
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            return root;
        }

        Uri baseUri = new Uri(root);
        Uri combinedPaths = new Uri(baseUri, path);

        foreach (string extendedPath in paths)
        {
           combinedPaths = new Uri(combinedPaths, extendedPath);
        }

        return combinedPaths.AbsoluteUri;
    }

    public static string AddUrlParams(this string url, Dictionary<string, string> parameters)
    {
        if (parameters == null || !parameters.Keys.Any())
        {
            return url;
        }

        var tempUrl = new StringBuilder($"{url}?");
        int count = 0;

        foreach (KeyValuePair<string, string> parameter in parameters)
        {
            if (count > 0)
            {
                tempUrl.Append("&");
            }

            tempUrl.Append($"{WebUtility.UrlEncode(parameter.Key)}={WebUtility.UrlEncode(parameter.Value)}");
            count++;
        }

        return tempUrl.ToString();
    }
судебный исполнитель
источник
1

Как и в других ответах, либо новый, Uri()либо TryCreate()можно сделать галочку. Однако базовый Uri должен заканчиваться, /а родственник НЕ должен начинаться с/ ; в противном случае он удалит завершающую часть базового URL

Я думаю, что это лучше всего сделать как метод расширения, т.е.

public static Uri Append(this Uri uri, string relativePath)
{
    var baseUri = uri.AbsoluteUri.EndsWith('/') ? uri : new Uri(uri.AbsoluteUri + '/');
    var relative = relativePath.StartsWith('/') ? relativePath.Substring(1) : relativePath;
    return new Uri(baseUri, relative);
}

и использовать это:

var baseUri = new Uri("http://test.com/test/");
var combinedUri =  baseUri.Append("/Do/Something");

С точки зрения производительности, это потребляет больше ресурсов, чем нужно, из-за класса Uri, который выполняет большой анализ и проверку; очень грубое профилирование (отладка) сделало миллион операций примерно за 2 секунды. Это будет работать для большинства сценариев, однако, чтобы быть более эффективным, лучше манипулировать всем как строками, это займет 125 миллисекунд на 1 миллион операций. Т.е.

public static string Append(this Uri uri, string relativePath)
{
    //avoid the use of Uri as it's not needed, and adds a bit of overhead.
    var absoluteUri = uri.AbsoluteUri; //a calculated property, better cache it
    var baseUri = absoluteUri.EndsWith('/') ? absoluteUri : absoluteUri + '/';
    var relative = relativePath.StartsWith('/') ? relativePath.Substring(1) : relativePath;
    return baseUri + relative;
}

И если вы все еще хотите вернуть URI, это займет около 600 миллисекунд на 1 миллион операций.

public static Uri AppendUri(this Uri uri, string relativePath)
{
    //avoid the use of Uri as it's not needed, and adds a bit of overhead.
    var absoluteUri = uri.AbsoluteUri; //a calculated property, better cache it
    var baseUri = absoluteUri.EndsWith('/') ? absoluteUri : absoluteUri + '/';
    var relative = relativePath.StartsWith('/') ? relativePath.Substring(1) : relativePath;
    return new Uri(baseUri + relative);
}

Надеюсь, это поможет.

Махмуд Ханафи
источник
1

Я думаю, что это должно дать вам больше гибкости, поскольку вы можете работать с любым количеством отрезков пути:

public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));
Золотой век
источник