Path.Combine удобен, но есть ли аналогичная функция в .NET framework для URL?
Я ищу такой синтаксис:
Url.Combine("http://MyUrl.com/", "/Images/Image.jpg")
который вернется:
"http://MyUrl.com/Images/Image.jpg"
Фактически, // обрабатывается маршрутизацией веб-сайта или сервера, а не браузером. Он отправит то, что вы поместили в адресную строку. Вот почему у нас возникают проблемы, когда мы набираем htp: // вместо http: //. Таким образом, // может вызвать серьезные проблемы на некоторых сайтах. Я пишу .dll для сканера, который обрабатывает конкретный веб-сайт, который выдает 404, если у вас есть // в URL-адресе.





Вы используете 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
+1: Это хорошо, хотя у меня иррациональная проблема с выходным параметром. ;)
Это гораздо лучший подход, так как он также будет работать для путей в форме "../../something.html"
@Brian: если это помогает, все методы TryXXX (int.TryParse, DateTime.TryParseExact) имеют этот выходной параметр, чтобы упростить их использование в операторе if. Кстати, вам не нужно инициализировать переменную, как это сделал Райан в этом примере.
Этот ответ страдает той же проблемой, что и Джоэла: объединение test.com/mydirectory/ и /helloworld.aspx приведет к test.com/helloworld.aspx, что, по-видимому, не то, что вы хотите.
Привет, это не удалось для следующего: if (Uri.TryCreate (new Uri ("localhost / MyService / ")," / Event / SomeMethod? Abc = 123 ", out result)) {Console.WriteLine (result);} Он показывает мне результат как : localhost / Событие / SomeMethod? abc = 123 Примечание: "http: //" заменяется здесь базовым Uri на stackoverflow
@FaisalMq Это правильное поведение, поскольку вы передали второй параметр, относящийся к корню. Если бы вы не указали начальную / во втором параметре, вы бы получили ожидаемый результат.
Та же проблема, что и в ответе вершина: Uri.TryCreate(new Uri("http://test.com/mydirectory"), "helloworld.aspx", out result) установит результат на http://test.com/helloworld.aspx
@ Lu55, если вы поместите / после каталога, он будет работать.
Чтобы это работало, базовый Uri должен иметь завершающую косую черту, а относительный Uri НЕ должен иметь ведущую косую черту. т.е. База: «test.com/test» Относительный: «do / did / done» результат: «test.com/test/do/did/done» Это также эквивалентно u = new Uri (new Uri («test.com/test/ "),« / do / did / done »);
Остроумный пример, Райан, заканчивающийся ссылкой на функцию. Отличная работа.
Одна рекомендация, Брайан: если вы заключите этот код в функцию, вы можете использовать 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");
}
Это может быть достаточно простое решение:
public static string Combine(string uri1, string uri2)
{
uri1 = uri1.TrimEnd('/');
uri2 = uri2.TrimStart('/');
return string.Format("{0}/{1}", uri1, uri2);
}
+1: Хотя это не обрабатывает пути относительного стиля (../../whatever.html), мне нравится этот путь за его простоту. Я бы также добавил обрезки для символа '\'.
См. Мой ответ для более полной версии этого.
@BrianMacKay, OP никогда не запрашивал пути относительного стиля ...
@MladenB. Ну, я ОП. :) Хотя я явно не просил об этом, необходимость поддержки путей относительного стиля является неотъемлемой частью всеобъемлющей проблемной области ... Невыполнение этого может привести к запутанным результатам, если люди попытаются использовать это повторно.
лол, не осознавал этого :) извините :)
У Uri есть конструктор, который сделает это за вас: new Uri(Uri baseUri, string relativeUri)
Вот пример:
Uri baseUri = new Uri("http://www.contoso.com");
Uri myUri = new Uri(baseUri, "catalog/shownew.htm");
Примечание редактора: будьте осторожны, этот метод не работает должным образом. В некоторых случаях он может вырезать часть baseUri. Смотрите комментарии и другие ответы.
Как фанат использования как можно большего количества уже созданного кода, мне было интересно, почему никто еще не предложил этого, пока я не заметил ваш ответ. ИМО, это лучший ответ.
Мне нравится использование класса Uri, к сожалению, он не будет вести себя как Path.Combine, как просил OP. Например, новый Uri (новый Uri ("test.com/mydirectory/ ")," /helloworld.aspx "). ToString () дает вам" test.com/helloworld.aspx "; что было бы неверно, если бы мы хотели получить результат в стиле Path.Combine.
Все дело в косых чертах. Если часть относительного пути начинается с косой черты, она ведет себя так, как вы описали. Но если убрать косую черту, все будет работать так, как вы и ожидали (обратите внимание на отсутствующую косую черту во втором параметре): new Uri (new Uri ("test.com/mydirectory/ ")," helloworld.aspx "). ToString () results в "test.com/mydirectory/helloworld.aspx". Path.Combine ведет себя аналогичным образом. Если параметр относительного пути начинается с косой черты, он возвращает только относительный путь и не объединяет их.
Если ваш baseUri оказался «test.com/mydirectory/mysubdirectory», то результатом будет «test.com/mydirectory/helloworld.aspx» вместо «test.com/mydirectory/mysubdirectory/helloworld.aspx». Тонкое отличие - отсутствие завершающей косой черты в первом параметре. Я за то, чтобы использовать существующие методы фреймворка, если у меня уже должна быть конечная косая черта, тогда я думаю, что выполнение partUrl1 + partUrl2 пахнет намного меньше - я мог бы довольно долго гоняться за этой конечной косой чертой. ради того, чтобы не выполнять конкатенацию строк.
Единственная причина, по которой мне нужен метод комбинирования URI, - это то, что мне не нужно проверять завершающую косую черту. Request.ApplicationPath - это '/', если ваше приложение находится в корне, и '/ foo', если это не так.
Я -1 этот ответ, потому что это не решает проблему. Когда вы хотите объединить URL-адреса, например, когда вы хотите использовать Path.Combine, вам не нужно заботиться о завершающем /. и об этом нужно заботиться. Я предпочитаю решение Брайана Маккея или mdsharpe выше
@Baptiste Pernet: Хороший момент - в целом вы правы. В конкретном случае OP, когда baseUri не имеет дополнительных элементов пути, конечная косая черта не влияет на результат. Если в baseUri есть дополнительные элементы пути, например "Http://MyUrl.com/some/folder", значит, вы правы, вам нужно обратить внимание, чтобы там оставалась косая черта в конце.
Этот код работает неправильно, если вы развертываете приложение по виртуальному пути, а не в корне веб-сайта. Если ваш базовый URL будет my.website.com/virtualdir, а относительный путь будет catalog / page.html, это вернет my.website.com/catalog/page.html, vitrualdir пропущен
Раньше я был большим поклонником этого подхода и использовал его повсюду, а затем меня сильно укусило, когда я узнал о неприятной привычке класса Uri к параметрам строки запроса декодирования URL. Хуже того, это задумано. См. stackoverflow.com/a/7307950. Как бы я ни ненавидел это, теперь я выполняю все конкатенации URL вручную. По крайней мере, это не испортит мою реплику.
Этот ответ не доставляет товары, если первый URI не является корнем домена. Стыд.
Потрясающие! он даже работает в том, что Uri baseUri = new Uri("http://www.foo.com/lol"); Uri myUri = new Uri(baseUri, "../test"); и myUri.ToString() дают http://www.foo.com/test
Слэши или нет, похоже, это не работает для URL-адресов, таких как var baseuri = new Uri("http://dotnettfs:8080/tfs/softwarecollection");var myuri = new Uri(baseuri, "_versionControl/changeset/244603");//myuri = http://dotnettfs:8080/tfs/_versionControl/changeset/244603
-1 new Uri(new Uri("test.com/mydirectory"), "helloworld.aspx") возвращает test.com/helloworld.aspx, чего никому не нужно.
@DoctorJones Это правильное поведение. Path.Combine примет любой параметр с ведущим \ в качестве нового корня, перекрывая любой ранее указанный корень. Так что конструктор Uri действительно поступает правильно. Эта «особенность» Path.Combine уже несколько раз укусила меня в прошлом.
@TomLint Я понимаю, что это так задумано. Я утверждал, что он ведет себя иначе, чем Path.Combine, потому что OP запросил эквивалент Path.Combine для URL-адресов. Конструктор Uri не эквивалент Path.Combine.
@DoctorJones Я не думаю, что вы полностью поняли, что я сказал; Path.Combine имеет точно такое же поведение в отношении параметров, начинающихся с символа разделителя пути. Поэтому объединение Uris таким образом является правильно, и именно то, что просил OP.
Это не ведет себя так, как просили операторы
По сути, это не работает, если базовый путь является относительным и не содержит всей необходимой информации, чтобы сделать его абсолютным URI. Итак, комбинируя /a/b/c/ с d/e/f с выбросом ArgumentOutOfRangeException. Совершенно непригоден для этой цели.
URL-адреса - это абстракции, которые МОГУТ переводиться в путь файловой системы, но не следует думать об этом. Это никогда не сможет работать как Path.Combine без некоторых серьезных предположений. Если у вас есть URL-адрес: blah.com/thing1/thing2, чей сказать, что thing2 - это каталог или файл? Если вы попытаетесь объединить это с относительным путем "../thing3", должно ли оно разрешиться на blah.com/thing1/thing3 или blah.com/thing3? Теперь thing3 - это каталог или файл? Без явного добавления завершающих слэшей ВРУЧНУЮ или какого-либо другого настраиваемого правила синтаксического анализа это не сработает. Большинство веб-серверов не раскрывают эту информацию.
Это решение генерирует предупреждение: docs.microsoft.com/de-de/visualstudio/code-quality/…
Если у нас есть такой url, результат будет неверным Пример: 127.0.0.1:8080/drupal
Считаю это правильным ответом. URI не может ссылаться на каталог. Он всегда относится к ресурсу, который является последней заданной частью, если он не заканчивается на /, и в этом случае он относится к ресурсу по умолчанию в этом пути. Следовательно, «локальный / каталог / подкаталог» является ложным предположением, потому что «подкаталог» на самом деле является ресурсом (файлом think), а не набором ресурсов. Именно так работают URI, и комбинирование этого с относительным путем всегда должно удалять часть «подкаталог». И это именно то, что делает конструктор Uri.
Основываясь на предоставленном вами образце URL, я предполагаю, что вы хотите объединить URL-адреса, относящиеся к вашему сайту.
Основываясь на этом предположении, я предлагаю это решение как наиболее подходящий ответ на ваш вопрос: «Path.Combine удобен, есть ли аналогичная функция в структуре для URL-адресов?»
Поскольку в структуре для URL-адресов есть аналогичная функция, я предлагаю правильный метод: "VirtualPathUtility.Combine". Вот справочная ссылка MSDN: VirtualPathUtility.Combine - метод
Есть одно предостережение: я считаю, что это работает только для URL-адресов, относящихся к вашему сайту (то есть вы не можете использовать его для создания ссылок на другой веб-сайт. Например, var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).
+1, потому что это близко к тому, что я ищу, хотя было бы идеально, если бы это работало для любого старого URL-адреса. Я удвоил это, и он станет намного элегантнее, чем то, что предлагал mdsharpe.
Предупреждение правильное, он не может работать с абсолютным uris, и результат всегда является относительным от корня. Но у него есть дополнительное преимущество, он обрабатывает тильду, как и "~ /". Это делает его ярлыком для Server.MapPath и комбинирования.
Ответ Райана Кука близок к тому, что мне нужно, и может быть более подходящим для других разработчиков. Однако он добавляет 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
Кстати о деталях: как насчет обязательного ArgumentNullException("url1"), если аргумент - Nothing? Извините, просто привередничать ;-). Обратите внимание, что обратная косая черта не имеет ничего общего с URI (и если она есть, ее не следует обрезать), поэтому вы можете удалить ее из TrimXXX.
вы можете использовать params string [] и рекурсивно объединить их, чтобы разрешить более 2 комбинаций
Это хорошее решение, но оно не касается следующих случаев: Assert.IsTrue(target.UrlCombine("../test1", "/") = "../test1/") Assert.IsTrue(target.UrlCombine("/", "test1/") = "/test1/")
Я понимаю, что вы имеете в виду по поводу тестов. Поскольку код на самом деле не работает с точки зрения разделения вещей на каталоги и последующего склеивания их вместе, а вместо этого просто использует текст, я даже не подумал добавить их. Они пройдут, поскольку они в основном покрыты ... Вы можете свободно редактировать код, если хотите.
Извините, я был неправ, ваш код действительно охватывает упомянутый случай!
@LouisRhys Так бывает, потому что в то время я работал над проектом VB.net. ;) Но я обновил это переводом на C#.
Я бы очень хотел, чтобы это было в библиотеке базовых классов, например Path.Combine.
@MarkHurd, я согласен. Я не понимал, что это Char (), я думал, что это был единственный Char. Имеет смысл, почему они так поступили. Я бы предпочел New Char() { "/"c, "\"c }
@MarkHurd Я снова отредактировал код, так что он поведенчески такой же, как C#, а также синтаксически эквивалентен.
@JJS Похоже, кто-то сломал это, а вы вернулись и исправили это - хорошая работа. :)
@BrianMacKay я сломал его, markhurd указал на мою ошибку и откатился, я снова обновился ... ура
Простой способ объединить их и убедиться, что он всегда правильный:
string.Format("{0}/{1}", Url1.Trim('/'), Url2);
+1, хотя это очень похоже на ответ mdsharpe, который я улучшил в своем ответе. Эта версия отлично работает, если только Url2 не начинается с / или \, или Url1 случайно не заканчивается на \, или один из них не пуст! :)
Я должен отметить, что Path.Combine, похоже, работает и для этого напрямую, по крайней мере, на .NET 4.
Если вы используете Path.Combine, у вас будет что-то вроде этого: www.site.com/foo\wrong\icon.png
Точно. Я потратил некоторое время на реализацию функции Uri.Combine именно по этой причине: stackoverflow.com/a/23399048/3481183
Я просто собрал небольшой метод расширения:
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");
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
path.Replace(Path.DirectorySeparatorChar, '/');path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)Чтобы заставить его работать, вы должны удалить первый / второй аргумент, т.е. «/ Images» - / Path.Combine («Http://MyUrl.com», «Images / Image.jpg»)
@SliverNinja Это неверно Значением этого поля является обратная косая черта ('\') в UNIX и косая черта ('/') в операционных системах Windows и Macintosh. При использовании Mono в системе Linux вы получите неправильный разделитель.
Удивительно, что ни один из других массовых сторонников не упомянул, что другие решения работают только для каталогов / имен два.
Все вы, придумавшие, что такое разделитель каталогов, забывают, что строки могли быть получены из другой ОС, нежели вы используете сейчас. Просто замените обратную косую черту на косую черту, и все готово.
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: Сейчас мы говорим ... Я попробую. Это может даже стать новым принятым ответом. После попытки использовать новый метод Uri () мне он действительно не понравился. Слишком привередливы.
Это именно то, что мне было нужно! Не был поклонником необходимости заботиться о том, где ставить конечные слэши и т. д.
+1 за откат нулевой проверки, чтобы он не взорвался.
Count () должен иметь значение Length, чтобы вам не нужно было включать Linq в свою библиотеку только для этого.
Это было именно то, что я искал.
Здесь уже есть отличные ответы. Основываясь на предложении 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
Это решение упрощает написание статического метода UriUtils.Combine («базовый URL», «часть1», «часть2», ...), который очень похож на Path.Combine (). Хороший!
Для поддержки относительных URI мне пришлось использовать ToString () вместо AbsoluteUri и UriKind.AbsoluteOrRelative в конструкторе Uri.
Спасибо за подсказку о родственнике Юрисе. К сожалению, Uri не упрощает работу с относительными путями, поскольку всегда есть некоторая путаница с задействованным Request.ApplicationPath. Возможно, вы также могли бы попробовать использовать новый Uri (HttpContext.Current.Request.ApplicationPath) в качестве основы и просто вызвать для него Append? Это даст вам абсолютные пути, но должно работать где угодно в структуре сайта.
Я также добавил проверку, не является ли какой-либо из добавляемых путей пустой или пустой строкой.
Когда я просматривал все ответы, я подумал ... «Почему еще никто не опубликовал метод расширения, я собираюсь опубликовать его» ... Неважно. +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». :) Код может быть излишне плотным - мне трудно взглянуть на это и сказать, да, это прекрасно. Это заставляет меня хотеть увидеть модульные тесты. Может это только я!
x.StartsWith ("/") &&! x.StartsWith ("http") - почему проверка http? что вы получаете?
Не пытайтесь убрать косую черту, если она начинается с http.
@BrianMacKay, я не уверен, что два лайнера оправдывают модульный тест, но если хотите, можете предоставить его. Не то чтобы я принимал патчи или что-то в этом роде, но не стесняйтесь редактировать предложение.
Я еще не использовал следующий код, но во время своих путешествий по Интернету нашел его для решения проблемы объединения URL-адресов - надеюсь, что это краткий (и успешный!) Ответ:
VirtualPathUtility.Combine
На самом деле не очень полезно. Есть несколько обращений Google, объясняющих некоторые из его проблем, но, помимо того, что ему не нравится "http: // ..." в начале, он фактически удаляет последний подпуть первого аргумента, если он не заканчивается на a "/"! Хотя описание MSDN звучит хорошо!
Я объяснил и дал решение этой проблемы в своем ответе stackoverflow.com/a/23399048/3481183
Я объединил все предыдущие ответы:
public static string UrlPathCombine(string path1, string path2)
{
path1 = path1.TrimEnd('/') + "/";
path2 = path2.TrimStart('/');
return Path.Combine(path1, path2)
.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
[TestMethod]
public void TestUrl()
{
const string P1 = "http://msdn.microsoft.com/slash/library//";
Assert.AreEqual("http://msdn.microsoft.com/slash/library/site.aspx", UrlPathCombine(P1, "//site.aspx"));
var path = UrlPathCombine("Http://MyUrl.com/", "Images/Image.jpg");
Assert.AreEqual(
"Http://MyUrl.com/Images/Image.jpg",
path);
}
Вы могли бы использовать класс VirtualPathUtiliy для безопасного добавления и удаления завершающих слэшей. Посмотрите мой ответ: stackoverflow.com/a/23399048/3481183
Я обнаружил, что UriBuilder действительно хорошо подходит для таких вещей:
UriBuilder urlb = new UriBuilder("http", _serverAddress, _webPort, _filePath);
Uri url = urlb.Uri;
return url.AbsoluteUri;
См. Класс UriBuilder - MSDN для получения дополнительных конструкторов и документации.
Вот мой подход, и я буду использовать его для себя:
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);
}
Это приемлемо только для вашего случая. Есть случаи, когда ваш код может быть нарушен. Кроме того, вы неправильно кодировали части пути. Это может быть огромной уязвимостью, когда дело доходит до атаки межсайтового скриптинга.
Я согласен с вашими пунктами. Код должен выполнять простое объединение двух частей URL.
Использовать:
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.
Объединение нескольких частей 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
У меня есть модульные тесты для трех наиболее распространенных случаев:

+1 за все дополнительные усилия. Мне нужно немного поддержать этот вопрос для некоторых из наиболее высоко оцененных ответов, вы бросили перчатку. ;)
Я просто соединяю две строки и использую регулярные выражения для очистки.
public class UriTool
{
public static Uri Join(string path1, string path2)
{
string url = path1 + "/" + path2;
url = Regex.Replace(url, "(?<!http:)/{2,}", "/");
return new Uri(url);
}
}
Итак, вы можете использовать это так:
string path1 = "http://someaddress.com/something/";
string path2 = "/another/address.html";
Uri joinedUri = UriTool.Join(path1, path2);
// joinedUri.ToString() returns "http://someaddress.com/something/another/address.html"
Мое общее решение:
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;
}
Этот вспомогательный метод очень гибкий и хорошо работает во многих различных случаях использования. Спасибо!
Вот метод UrlUtility.Combine от Microsoft (OfficeDev PnP):
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-адресов.
Отредактировано, чтобы уточнить, к какому классу он принадлежит
Будьте осторожны при использовании этого класса, остальная часть класса содержит артефакты, специфичные для SharePoint.
Я использовал этот код для решения проблемы:
string[] brokenBaseUrl = Context.Url.TrimEnd('/').Split('/');
string[] brokenRootFolderPath = RootFolderPath.Split('/');
for (int x = 0; x < brokenRootFolderPath.Length; x++)
{
//if url doesn't already contain member, append it to the end of the string with / in front
if (!brokenBaseUrl.Contains(brokenRootFolderPath[x]))
{
if (x == 0)
{
RootLocationUrl = Context.Url.TrimEnd('/');
}
else
{
RootLocationUrl += String.Format("/{0}", brokenRootFolderPath[x]);
}
}
}
Правила при объединении URL-адресов с URI
Чтобы избежать странного поведения, нужно следовать одному правилу:
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();
});
Я создал эту функцию, которая облегчит вам жизнь:
/// <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
Я считаю следующее полезным и имеет следующие особенности:
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
Некоторые проблемы с тестами: // Result = test1 / test2 / test3 \ для четвертого и последнего из тестов throws дает ArgumentNullException вместо ArgumentException
Оба из них работают:
Uri final = new Uri(Regex.Replace(baseUrl + "/" + relativePath, "(?<!http:)/{2,}", "/"));
Или же
Uri final =new Uri(string.Format("{0}/{1}", baseUrl.ToString().TrimEnd('/'), relativePath.ToString().TrimStart('/')));
Т.е. если
baseUrl = "http://tesrurl.test.com/Int18"
а также
relativePath = "To_Folder"
output = http://tesrurl.test.com/Int18/To_Folder
Некоторые ошибки появятся в приведенном ниже коде:
// If you use the below code, some issues will be there in the final URI
Uri final = new Uri(baseUrl, relativePath);
Простой однострочник:
public static string Combine(this string uri1, string uri2) => $"{uri1.TrimEnd('/')}/{uri2.TrimStart('/')}";
Вдохновленный ответом @Matt Sharpe.
Почему бы просто не использовать следующее.
System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")
Я искал версию этого PowerShell, которая будет: [System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg"), но это не сработает с результатом: /Images/Image.jpg. Удалите / из второго подпути, и он работает: [System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
Хорошая идея, но она терпит неудачу, когда один из параметров равен нулю.
Мы используем следующий простой вспомогательный метод, чтобы объединить произвольное количество частей URL:
public static string JoinUrlParts(params string[] urlParts)
{
return string.Join("/", urlParts.Where(up => !string.IsNullOrEmpty(up)).ToList().Select(up => up.Trim('/')).ToArray());
}
Обратите внимание, что он не поддерживает относительные URL-адреса в стиле '../../something/page.htm'!
Есть комментарий Тодда Меньера выше, что Flurl включает в себя Url.Combine.
Подробнее:
Url.Combine is basically a Path.Combine for URLs, ensuring one and only one separator character between parts:
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> Установить пакет Flurl.Http
Или получить автономный конструктор URL без функций HTTP:
PM> Установка пакета Flurl
Что ж, этот вопрос получает много трафика, и ответ с более чем 1000 положительными голосами на самом деле работает не во всех случаях. Спустя годы я фактически использую Flurl для этого, поэтому я принимаю его. Кажется, это работает во всех случаях, с которыми я сталкивался. Если люди не хотят принимать зависимость, я опубликовал ответ, который также отлично работает.
и если вы не используете Flurl и предпочитаете облегченную версию, github.com/jean-lourenco/UrlCombine
Я обнаружил, что конструктор Uri переворачивает '\' в '/'. Таким образом, вы также можете использовать Path.Combine с конструктором Uri.
Uri baseUri = new Uri("http://MyUrl.com");
string path = Path.Combine("Images", "Image.jpg");
Uri myUri = new Uri(baseUri, path);
Как бы то ни было, вот пара методов расширения. Первый объединяет пути, а второй добавляет параметры к 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();
}
Если вы не хотите добавлять стороннюю зависимость, такую как 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 таким образом, чтобы его было легко тестировать по модулю.
Итак, у меня есть другой подход, похожий на всех, кто использовал UriBuilder.
Я не хотел разделять свой BaseUrl (который может содержать часть пути - например, http://mybaseurl.com/dev/), как это сделал javajavajavajavajava.
В следующем фрагменте кода показан код + Tests.
Остерегаться: Это решение уменьшает регистр хоста и добавляет порт. Если это нежелательно, можно записать строковое представление, например, используя свойство UriUriBuilder.
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 обрабатывает этот случай в установщике 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 будет использоваться для ctor System.Uri.
Это очень надежный подход. Поднимите палец вверх за модульный тест !!
Для тех, кто ищет однострочник и просто хочет соединить части пути без создания нового метода или ссылки на новую библиотеку или создать значение 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
Как указано в других ответах, новый 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, который выполняет большой анализ и проверку; очень грубое профилирование (Debug) выполнило миллион операций примерно за 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);
}
Надеюсь, это поможет.
Недавно в пакет Energy.Core был добавлен метод Комбинировать, поэтому вы можете использовать его для соединения частей URL.
string url;
url = Energy.Base.Url.Combine("https://www.youtube.com", "watch?v=NHCgbs3TcYg");
Console.WriteLine(url);
url = Energy.Base.Url.Combine("https://www.youtube.com", "watch?v=NHCgbs3TcYg", "t=150");
Console.WriteLine(url);
Кроме того, он распознает часть параметра, поэтому он будет работать так, как вы могли ожидать (объединение пути с косой чертой и параметров с амперсандом).
Я думаю, это должно дать вам больше гибкости, так как вы можете работать с любым количеством сегментов пути:
public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));
Если вы не хотите иметь такую зависимость, как Flurl, вы можете использовать его исходный код:
/// <summary>
/// Basically a Path.Combine for URLs. Ensures exactly one '/' separates each segment,
/// and exactly on '&' separates each query parameter.
/// URL-encodes illegal characters but not reserved characters.
/// </summary>
/// <param name = "parts">URL parts to combine.</param>
public static string Combine(params string[] parts) {
if (parts == null)
throw new ArgumentNullException(nameof(parts));
string result = "";
bool inQuery = false, inFragment = false;
string CombineEnsureSingleSeparator(string a, string b, char separator) {
if (string.IsNullOrEmpty(a)) return b;
if (string.IsNullOrEmpty(b)) return a;
return a.TrimEnd(separator) + separator + b.TrimStart(separator);
}
foreach (var part in parts) {
if (string.IsNullOrEmpty(part))
continue;
if (result.EndsWith("?") || part.StartsWith("?"))
result = CombineEnsureSingleSeparator(result, part, '?');
else if (result.EndsWith("#") || part.StartsWith("#"))
result = CombineEnsureSingleSeparator(result, part, '#');
else if (inFragment)
result += part;
else if (inQuery)
result = CombineEnsureSingleSeparator(result, part, '&');
else
result = CombineEnsureSingleSeparator(result, part, '/');
if (part.Contains("#")) {
inQuery = false;
inFragment = true;
}
else if (!inFragment && part.Contains("?")) {
inQuery = true;
}
}
return EncodeIllegalCharacters(result);
}
/// <summary>
/// URL-encodes characters in a string that are neither reserved nor unreserved. Avoids encoding reserved characters such as '/' and '?'. Avoids encoding '%' if it begins a %-hex-hex sequence (i.e. avoids double-encoding).
/// </summary>
/// <param name = "s">The string to encode.</param>
/// <param name = "encodeSpaceAsPlus">If true, spaces will be encoded as + signs. Otherwise, they'll be encoded as %20.</param>
/// <returns>The encoded URL.</returns>
public static string EncodeIllegalCharacters(string s, bool encodeSpaceAsPlus = false) {
if (string.IsNullOrEmpty(s))
return s;
if (encodeSpaceAsPlus)
s = s.Replace(" ", "+");
// Uri.EscapeUriString mostly does what we want - encodes illegal characters only - but it has a quirk
// in that % isn't illegal if it's the start of a %-encoded sequence https://stackoverflow.com/a/47636037/62600
// no % characters, so avoid the regex overhead
if (!s.Contains("%"))
return Uri.EscapeUriString(s);
// pick out all %-hex-hex matches and avoid double-encoding
return Regex.Replace(s, "(.*?)((%[0-9A-Fa-f]{2})|$)", c => {
var a = c.Groups[1].Value; // group 1 is a sequence with no %-encoding - encode illegal characters
var b = c.Groups[2].Value; // group 2 is a valid 3-character %-encoded sequence - leave it alone!
return Uri.EscapeUriString(a) + b;
});
}
Url.Combine, который делает именно это.