Path.Combine для URL-адрес?


1243

Path.Combine зручна, але чи є аналогічна функція в .NET рамці для 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+ оновлень насправді не працює у всіх випадках. Через роки я фактично використовую для цього Flurl, тому приймаю цей. Здається, це працює у всіх випадках, з якими я стикався. Якщо люди не хочуть приймати залежність, я опублікував відповідь, яка також добре працює.
Брайан Маккей

і якщо ви не користуєтесь Flurlі надаєте
перевагу

1156

Uri має конструктор, який повинен зробити це для вас: new Uri(Uri baseUri, string relativeUri)

Ось приклад:

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

Примітка редактора: Остерігайтеся, цей метод працює не так, як очікувалося. Він може вирізати частину основиUri в деяких випадках. Дивіться коментарі та інші відповіді.


369
Мені подобається використання класу Uri, на жаль, він не буде вести себе як Path.Combine, як просив ОП. Наприклад новий Uri (новий Uri (" test.com/mydirectory/" ), "/helloworld.aspx"). ToString () дає " test.com/helloworld.aspx "; що було б неправильно, якби ми бажали результату стилю Path.Combine.
Лікар Джонс

195
Це все в косах. Якщо відносна частина шляху починається з косої риски, то вона поводиться так, як ви описали. Але, якщо ви залишите косу рису назовні, вона працює так, як ви очікували (відзначте пропущений косу рису на другому параметрі): новий Uri (новий 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 пахне набагато менше - я, можливо, міг би переслідувати цей кінець косої риски досить довго, все для заради того, щоб не робити строкову конфат.
Карл

63
Єдина причина, за якою я хочу метод комбінування URI, полягає в тому, що мені не доведеться перевіряти наявність косої риски. Request.ApplicationPath - це "/", якщо ваша програма знаходиться в корені, але "/ foo", якщо ні.
нік

24
Я -1 на цю відповідь, оскільки це не відповідає проблемі. Коли ви хочете поєднати URL, наприклад, коли ви хочете використовувати Path.Combine, ви не хочете піклуватися про те, як слід /. і з цим треба дбати. Я віддаю перевагу рішення Брайана Маккея або mdsharpe вище
Baptiste Pernet

160

Це може бути відповідне просте рішення:

public static string Combine(string uri1, string uri2)
{
    uri1 = uri1.TrimEnd('/');
    uri2 = uri2.TrimStart('/');
    return string.Format("{0}/{1}", uri1, uri2);
}

7
+1: Хоча це не обробляє контури відносного стилю (../../wever.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що, здавалося б, не те, що ви хочете.
Метт Кокай

3
Привіт, це не вдалося для наступного: if (Uri.TryCreate (новий Uri (" localhost / MyService /" ), "/ Event / SomeMethod? Abc = 123", результат)) {Console.WriteLine (результат); } Він показує мені результат як: localhost / Event / SomeMethod? Abc = 123 Примітка: "http: //" замінено тут із бази Uri на stackoverflow
Faisal Mq

3
@FaisalMq Це правильна поведінка, оскільки ви передали кореневий другий параметр. Якби ви випустили провідний / другий параметр, ви отримали б очікуваний результат.
Том Лінт

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 ("базовий URL", "part1", "part2", ...), який дуже схожий на Path.Combine (). Приємно!
angularsen

Для підтримки відносних URI мені довелося використовувати ToString () замість AbsoluteUri та UriKind.AbsoluteOrRelative в конструкторі Uri.
angularsen

Дякую за пораду про відносну Уріс. На жаль, Uri не спрощує спілкування з відносними шляхами, оскільки завжди існує певна розмова щодо участі Request.ApplicationPath. Можливо, ви також можете спробувати використовувати новий Uri (HttpContext.Current.Request.ApplicationPath) як базу та просто зателефонувати Додавати до нього? Це дасть вам абсолютні шляхи, але має працювати в будь-якому місці в структурі сайту.
Алесь Поточник Гахоніна

Чудово. Радий, що це допомогло комусь іншому. Використовували це деякий час і не мали жодних проблем.
Алесь Поточник Гахоніна

Я також додав перевірити, чи будь-який шлях для додавання не є нульовим або порожнім рядком.
n.podbielski

91

Відповідь Райана Кука близька до того, що я хочу, і може бути більш підходящим для інших розробників. Однак він додає 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
ви можете використовувати парами string [] і рекурсивно приєднуватися до них, щоб дозволити більше двох комбінацій
Jaider

4
Я впевнений, хотів би, щоб це було в Бібліотеці базового класу, як Path.Combine.
Урія Блатервік

1
@MarkHurd Я знову відредагував код, щоб він був поведінково таким же, як C #, а також синтаксично еквівалентний.
JJS

1
@BrianMacKay Я зламав її, Мархерд вказав на мою помилку і відкотився, і я знову оновився ... ура
JJS

36

Path.Combine не працює для мене, оскільки там можуть бути символи типу "|" в аргументах QueryString і, отже, URL-адресі, що призведе до аргументації Exxception.

Я вперше спробував новий 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 / Брайан МакКейс і розробив його трохи далі для роботи з кількома частинами 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 () має бути довжиною, щоб вам не потрібно було включати 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");).


+1, оскільки він близький тому, що я шукаю, хоча було б ідеально, якби це працювало для будь-якої старої URL-адреси. Я подвійно, це вийде набагато елегантніше, ніж те, що запропонував mdsharpe.
Брайан Маккей

2
Застереження правильне, воно не може працювати з абсолютним урисом, і результат завжди відносний від кореня. Але це має додаткову користь, обробляє тильду, як і у випадку «~ /». Це робить це комбінацією клавіш для швидкого Server.MapPathпоєднання.
Авель

25
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")

12
path.Replace(Path.DirectorySeparatorChar, '/');
Jaider

5
path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
SliverNinja - MSFT

1
Щоб отримати його до wrk, ви повинні видалити перший / другий аргумент, тобто "/ Images" - / Path.Combine (" Http://MyUrl.com ", "Images / Image.jpg")
за 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");
}

10

Простий спосіб їх поєднання та забезпечення правильності завжди:

string.Format("{0}/{1}", Url1.Trim('/'), Url2);

+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

У мене є одиничні тести, які охоплюють три найбільш поширені випадки:

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


2
На мене це гарно виглядає. Хоча для кращої чіткості ви можете замінити цикл I на цикл foreach.
Кріс Марісіч

Дякую Крису. Я щойно змінив свій код, щоб використовувати Foreach.
Вірте2014,

1
+1 за всі додаткові зусилля. Мені потрібно трохи підтримати це питання для деяких вищих проголосованих відповідей, ви кинули рукавичку. ;)
Брайан Маккей

Вибачте, але недостатньо. Працює в кількох показаних вами випадках, але далеко не корисних для інших комбінацій. Наприклад, колонки на шляху принесуть шкоду.
Габор

Чи можете ви надати приклад того, що ви маєте на увазі? Я буду радий виправити проблему та допомогти наступним користувачам.
Повір2014

7

Я виявив, що UriBuilderдуже добре працював над подібними речами:

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

Дивіться клас UriBuilder - MSDN для отримання додаткових конструкторів та документації.


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
  • кидає на null або порожній

Клас

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

Деякі проблеми з тестами: // Результат = test1 / test2 / test3 \ для 4-го та останнього тестів на кидки дає 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

3

Чому б просто не скористатися наступним.

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")
Under Under

Хороша ідея, але вона не вдається, коли один з параметрів є нульовим.
pholpar

2

Правила при поєднанні URL-адрес з URI

Щоб уникнути дивної поведінки, є одне правило:

  • Шлях (каталог) повинен закінчуватися символом '/'. Якщо шлях закінчується без '/', остання частина трактується як ім'я файлу, і вона буде об'єднана при спробі поєднання з наступною частиною URL-адреси.
  • Є один виняток: основна URL-адреса (без інформації про каталог) не повинна закінчуватися символом "/"
  • частина шляху не повинна починатися з '/'. Якщо він починається з '/', вся існуюча відносна інформація з URL-адреси випадає ... додавши string.Emptyшлях до частини, також буде видалено відповідний каталог з URL-адреси!

Якщо ви дотримуєтесь правил вище, ви можете комбінувати 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/ ), як це робила javajavajaваяваява .

Наступний фрагмент показує код + Тести.

Остерігайтеся. Це рішення знижує розмір хоста та додає порт. Якщо це не бажано, можна написати рядкове подання, наприклад, використовуючи 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, що повернеться Backslashes (у Windows atleast), 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) "функцію" від API .NET. Це може змінитися з майбутнім випуском, тому, будь ласка, накрийте Метод тестами.

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

Бічна примітка: Можна також безпосередньо працювати з UriBuilder.Uriвластивістю, якщо урі буде використовуватися для System.Urictor.


Це дуже надійний підхід. Великі пальці для одиничного тесту !!
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


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.


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

Це прийнятно лише для вашого випадку. Є випадки, які можуть порушити ваш код. Крім того, ви не зробили належне кодування частин шляху. Це може бути величезною вразливістю, коли мова йде про міжсайтовий сценарій атаки.
Повірте2014

Я згоден з вашими пунктами. Код повинен робити просто просте поєднання двох частин 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()можна зробити галочку. Однак основа Урі повинна закінчуватися, /і відносна НЕ повинна починатися /; інакше він видалить задні частини основної 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 секунди. Це буде працювати для більшості сценаріїв, однак, щоб бути ефективнішим, краще маніпулювати всім, як рядки, для 1 мільйона операцій потрібно 125 мілісекунд. Тобто

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, на 1 мільйон операцій потрібно близько 600 мілісекунд.

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('/'))));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.