Path.Combine dla adresów URL?


1243

Path.Combine jest przydatny, ale czy istnieje podobna funkcja w .NET dla adresów URL ?

Szukam takiej składni:

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

który zwróciłby:

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


14
Flurl zawiera Url.Combinemetodę, która właśnie to robi.
Todd Menier

2
W rzeczywistości // jest obsługiwany przez routing strony internetowej lub serwera, a nie przez przeglądarkę. Wyśle to, co umieścisz w pasku adresu. Dlatego napotykamy problemy, gdy wpisujemy htp: // zamiast http: //, więc // może powodować poważne problemy w niektórych witrynach. Piszę .dll dla robota, który obsługuje określoną witrynę internetową, która wyrzuca 404, jeśli masz // w adresie URL.
Dave Gordon

Odpowiedzi:


73

Istnieje komentarz Todda Meniera powyżej, że Flurl zawiera Url.Combine.

Więcej szczegółów:

Url.Combine jest w zasadzie Path.Combine dla adresów URL, zapewniając jeden i tylko jeden znak separatora między częściami:

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" 

Pobierz Flurl.Http na NuGet :

PM> Zainstaluj pakiet Flurl

Lub uzyskaj autonomiczny program do tworzenia adresów URL bez funkcji HTTP:

PM> Zainstaluj pakiet Flurl


4
Cóż, to pytanie ma duży ruch, a odpowiedź z ponad 1000 głosów pozytywnych nie działa we wszystkich przypadkach. Wiele lat później używam do tego Flurl, więc akceptuję ten. Wydaje się, że działa we wszystkich napotkanych przypadkach. Jeśli ludzie nie chcą brać zależności, opublikowałem odpowiedź, która również działa dobrze.
Brian MacKay

a jeśli nie używasz Flurli korzystałbyś z lekkiej wersji, github.com/jean-lourenco/UrlCombine
lizzy91

1156

Uri ma konstruktor, który powinien to zrobić dla Ciebie: new Uri(Uri baseUri, string relativeUri)

Oto przykład:

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

Uwaga od edytora: Uwaga: ta metoda nie działa zgodnie z oczekiwaniami. W niektórych przypadkach może wyciąć część baseUri. Zobacz komentarze i inne odpowiedzi.


369
Lubię korzystanie z klasy Uri, niestety nie będzie się zachowywać jak Path.Combine, jak poprosił OP. Na przykład nowy Uri (nowy Uri („ test.com/mydirectory/” ), „/helloworld.aspx”). ToString () daje „ test.com/helloworld.aspx ”; co byłoby niepoprawne, gdybyśmy chcieli uzyskać wynik w stylu Path.Combine.
Doktor Jones

195
Wszystko w ukośnikach. Jeśli względna część ścieżki zaczyna się od ukośnika, wówczas zachowuje się tak, jak opisano. Jeśli jednak pominiesz ukośnik, działa on tak, jak się spodziewałeś (zwróć uwagę na brakujący ukośnik w drugim parametrze): nowy Uri (nowy Uri („ test.com/mydirectory/” ), „helloworld.aspx” ) .ToString () powoduje „ test.com/mydirectory/helloworld.aspx ”. Path.Combine zachowuje się podobnie. Jeśli parametr ścieżki względnej rozpoczyna się od ukośnika, zwraca tylko ścieżkę względną i nie łączy ich.
Joel Beckham

70
Jeśli twoim baseUri jest „test.com/mydirectory/mysubdirectory”, wynikiem będzie „test.com/mydirectory/helloworld.aspx” zamiast „test.com/mydirectory/mysubdirectory/helloworld.aspx”. Subtelna różnica polega na braku końcowego ukośnika na pierwszym parametrze. Jestem za używaniem istniejących metod frameworka, jeśli muszę już mieć tam ukośnik, myślę, że robienie partUrl1 + partUrl2 pachnie znacznie mniej - mógłbym potencjalnie ścigać ten ukośnik od dłuższego czasu, wszystko przez po to, by nie robić konkatku.
Carl

63
Jedynym powodem, dla którego chcę metody łączenia URI, jest to, że nie muszę sprawdzać ukośnika końcowego. Request.ApplicationPath to „/”, jeśli aplikacja jest w katalogu głównym, ale „/ foo”, jeśli nie jest.
nickd 25.03.11

24
I -1 tę odpowiedź, ponieważ to nie rozwiązuje problemu. Kiedy chcesz połączyć adres URL, na przykład gdy chcesz użyć Path.Combine, nie chcesz przejmować się końcowym /. i tym musisz się przejmować. Wolę rozwiązanie Briana MacKaya lub mdsharpe powyżej
Baptiste Pernet

160

Może to być odpowiednio proste rozwiązanie:

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

7
+1: Chociaż nie obsługuje ścieżek w stylu względnym (../../whokolwiek.html), podoba mi się ta ze względu na prostotę. Dodałbym również wykończenia dla znaku „\”.
Brian MacKay

3
Zobacz moją odpowiedź na pełniejszą wersję tego.
Brian MacKay

149

Używasz 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);
}

Wróci:

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


53
+1: To dobrze, chociaż mam irracjonalny problem z parametrem wyjściowym. ;)
Brian MacKay

10
@Brian: jeśli to pomaga, wszystkie metody TryXXX ( int.TryParse, DateTime.TryParseExact) mają ten parametr wyjściowy, aby ułatwić korzystanie z nich w instrukcji if. Przy okazji, nie musisz inicjalizować zmiennej, jak Ryan w tym przykładzie.
Abel

41
Ta odpowiedź ma ten sam problem, co odpowiedź Joela : dołączenie test.com/mydirectory/i /helloworld.aspxspowoduje, że test.com/helloworld.aspxpozornie nie jest to, czego chcesz.
Matt Kocaj

3
Cześć, nie powiodło się to dla: if (Uri.TryCreate (new Uri („ localhost / MyService /” ), ”/ Event / SomeMethod? Abc = 123”, wynik out)) {Console.WriteLine (wynik); } Pokazuje mi wynik jako: localhost / Event / SomeMethod? Abc = 123 Uwaga: „http: //” zastępuje się tutaj z podstawowego Uri przez stackoverflow
Faisal Mq

3
@FaisalMq To jest prawidłowe zachowanie, ponieważ przekazano drugi parametr względny do katalogu głównego. Jeśli pominąłeś wiodący / drugi parametr, uzyskałbyś oczekiwany wynik.
Tom Lint

127

Tutaj jest już kilka świetnych odpowiedzi. W oparciu o sugestię mdsharpe, oto metoda rozszerzenia, której można łatwo użyć, gdy chcesz poradzić sobie z instancjami 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('/'))));
    }
}

I przykład użycia:

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

Spowoduje to utworzenie http://example.com/subpath/part1/part2


2
To rozwiązanie sprawia, że ​​napisanie statycznej metody UriUtils.Combine („podstawowy adres URL”, „część 1”, „część 2”, ...) jest bardzo proste, bardzo podobna do Path.Combine (). Miły!
angularsen

Aby wesprzeć względne identyfikatory URI, musiałem użyć ToString () zamiast AbsoluteUri i UriKind.AbsoluteOrRelative w konstruktorze Uri.
angularsen

Dzięki za wskazówkę na temat krewnego Urisa. Niestety, Uri nie ułatwia radzenia sobie ze ścieżkami względnymi, ponieważ zawsze występuje pewne zamieszanie związane z Request.ApplicationPath. Być może mógłbyś także spróbować użyć nowego Uri (HttpContext.Current.Request.ApplicationPath) jako bazy i po prostu wywołać Append na nim? Zapewni to ścieżki bezwzględne, ale powinno działać w dowolnym miejscu w strukturze witryny.
Ales Potocnik Hahonina

Świetny. Cieszę się, że pomógł komuś innemu. Używam tego od jakiegoś czasu i nie miałem żadnych problemów.
Ales Potocnik Hahonina

Dodałem również sprawdzenie, czy którakolwiek ze ścieżek do dołączenia nie jest ciągiem zerowym ani pustym.
n.podbielski

91

Odpowiedź Ryana Cooka jest zbliżona do tego, czego szukam i może być bardziej odpowiednia dla innych programistów. Dodaje jednak http: // na początku łańcucha i generalnie wykonuje nieco więcej formatowania niż ja.

Również w moich przypadkach użycia rozwiązywanie ścieżek względnych nie jest ważne.

Odpowiedź mdsharp zawiera również zalążek dobrego pomysłu, chociaż faktyczna implementacja wymagała jeszcze kilku szczegółów. To jest próba rozwinięcia go (i używam tego w produkcji):

DO#

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

Ten kod przechodzi następujący test, który zdarza się w 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
Mówiąc o szczegółach: co z obowiązkowym, ArgumentNullException("url1")jeśli argument jest Nothing? Przepraszam, jestem wybredna ;-). Zwróć uwagę, że ukośnik odwrotny nie ma nic wspólnego z identyfikatorem URI (a jeśli już istnieje, nie należy go przycinać), więc możesz go usunąć z TrimXXX.
Abel

4
możesz użyć parametru params string [] i połączyć je rekurencyjnie, aby zezwolić na więcej niż 2 kombinacje
Jaider

4
Na pewno chciałbym, aby było to w Bibliotece klas podstawowych, np. Path.Combine.
Uriah Blatherwick,

1
@MarkHurd Znowu edytowałem kod, aby był behawioralnie taki sam jak C # i również pod względem składniowym.
JJS,

1
@BrianMacKay złamałem go, Markhurd wskazał mój błąd i wycofałem się, zaktualizowałem ponownie ...
Pozdrawiam

36

Path.Combine nie działa dla mnie, ponieważ mogą istnieć znaki takie jak „|” w argumentach QueryString, a zatem adres URL, co spowoduje wyjątek ArgumentException.

Najpierw wypróbowałem nowe Uri(Uri baseUri, string relativeUri)podejście, które nie powiodło się z powodu URI takich jak http://www.mediawiki.org/wiki/Special:SpecialPages:

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

spowoduje utworzenie Special: SpecialPages, ponieważ dwukropek Specialoznacza schemat.

W końcu musiałem wybrać trasę mdsharpe / Brian MacKays i rozwinąłem ją nieco dalej, aby pracować z wieloma częściami 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;
}

Stosowanie: CombineUri("http://www.mediawiki.org/", "wiki", "Special:SpecialPages")


1
+1: Teraz rozmawiamy ... Spróbuję tego. Może to nawet okazać się nową przyjętą odpowiedzią. Po wypróbowaniu nowej metody Uri () naprawdę mi się to nie podoba. Zbyt drobny.
Brian MacKay

Właśnie tego potrzebowałem! Nie był fanem konieczności dbania o to, gdzie umieszczam końcowe slash itp.
Gromer

+1 za rzutowanie na sprawdzanie zerowe, żeby się nie wysadziło.
NightOwl888

Count () powinien mieć długość, aby nie trzeba było w tym celu włączać Linq do biblioteki.
PRMan

Właśnie tego szukałem.
ThePeter

34

Na podstawie podanego przez Ciebie przykładowego adresu URL założę, że chcesz łączyć adresy URL dotyczące Twojej witryny.

W oparciu o to założenie zaproponuję to rozwiązanie jako najbardziej odpowiednią odpowiedź na twoje pytanie, które brzmiało: „Path.Combine jest przydatny, czy istnieje podobna funkcja w ramach dla adresów URL?”

Ponieważ w strukturze URL istnieje podobna funkcja, proponuję poprawną metodę: „VirtualPathUtility.Combine”. Oto link referencyjny MSDN: Metoda VirtualPathUtility.Combine

Jest jedno zastrzeżenie: uważam, że działa to tylko w przypadku adresów URL związanych z witryną (tzn. Nie można jej używać do generowania linków do innej witryny. Na przykład var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).


+1, ponieważ jest blisko tego, czego szukam, chociaż byłoby idealnie, gdyby działał dla dowolnego starego adresu URL. Podwoję, że stanie się znacznie bardziej elegancki niż to, co zaproponował mdsharpe.
Brian MacKay

2
Zastrzeżenie jest poprawne, nie może działać z absolutnym moczem, a wynik jest zawsze względny od korzenia. Ale ma tę dodatkową zaletę, że przetwarza tyldę, podobnie jak w przypadku „~ /”. To sprawia, że ​​jest to skrót do Server.MapPathłączenia i łączenia.
Abel

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
Aby dostać się do wrk musisz najpierw usunąć / w drugim arg np. „/ Images” - / Path.Combine („ Http://MyUrl.com ”, „Images / Image.jpg”)
Per G

8
@SliverNinja Niepoprawne Wartość tego pola to ukośnik odwrotny („\”) w systemie UNIX i ukośnik („/”) w systemach operacyjnych Windows i Macintosh. Używając Mono w systemie Linux, otrzymasz niewłaściwy separator.
user247702

6
Wszyscy, którzy szukają w Separatorze katalogów, zapominają, że łańcuchy mogły pochodzić z innego systemu operacyjnego niż obecnie. Wystarczy zamienić ukośnik odwrotny na ukośnik i jesteś objęty.
JeremyWeir,

17

Właśnie stworzyłem małą metodę rozszerzenia:

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('/');
        }

Można go użyć w następujący sposób:

"www.example.com/".UriCombine("/images").UriCombine("first.jpeg");

12

Dowcipny przykład, Ryan, kończący się linkiem do funkcji. Dobra robota.

Jedna rekomendacja Brian: jeśli zawiniesz ten kod w funkcji, możesz użyć UriBuilder do zawinięcia podstawowego adresu URL przed wywołaniem TryCreate.

W przeciwnym razie podstawowy adres URL MUSI zawierać schemat (gdzie UriBuilder przyjmie http: //). Tylko myśl:

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

Łatwy sposób na ich połączenie i upewnienie się, że zawsze jest poprawny, to:

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

+1, chociaż jest to bardzo podobne do odpowiedzi mdsharpe, którą poprawiłem w mojej odpowiedzi. Ta wersja działa świetnie, chyba że Url2 zaczyna się od / lub \, albo Url1 przypadkowo kończy się na \, albo jedno z nich jest puste! :)
Brian MacKay

9

Łączenie wielu części adresu URL może być nieco trudne. Możesz użyć konstruktora dwuparametrowego Uri(baseUri, relativeUri)lub Uri.TryCreate()funkcji narzędziowej.

W obu przypadkach może skończyć się powrotem niepoprawny wynik, ponieważ metody te przechowywać na obcinanie części względne off z pierwszego parametru baseUri, czyli z czegoś podobnego http://google.com/some/thingdo http://google.com.

Aby połączyć wiele części w końcowy adres URL, możesz skopiować dwie funkcje poniżej:

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

Pełny kod z testami jednostkowymi w celu wykazania użycia można znaleźć na stronie https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs

Mam testy jednostkowe obejmujące trzy najczęstsze przypadki:

Wpisz opis zdjęcia tutaj


2
Jak dla mnie wygląda dobrze. Chociaż można zastąpić pętlę I pętlą foreach, aby uzyskać lepszą przejrzystość.
Chris Marisic

Dzięki Chris. Właśnie zmieniłem kod, aby korzystać z Foreach.
Believe2014

1
+1 za cały dodatkowy wysiłek. Muszę trochę podtrzymać to pytanie w przypadku niektórych wyżej głosowanych odpowiedzi, które rzuciłeś w dół rękawicy. ;)
Brian MacKay

Przepraszam, ale za mało. Działa w kilku pokazanych przypadkach, ale nie jest użyteczny we wszystkich kombinacjach. Na przykład dwukropki na ścieżce spowodują szkody.
Gábor

Czy możesz podać przykład, co masz na myśli? Z przyjemnością naprawię problem i pomogę kolejnym użytkownikom.
Believe2014

7

Uważam, że UriBuilderdziała naprawdę dobrze w tego typu rzeczach:

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

Zobacz Klasa UriBuilder - MSDN, aby uzyskać więcej konstruktorów i dokumentacji.


4

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

Źródło: GitHub


Wygląda na to, że może to dotyczyć ścieżek, a nie adresów URL.
Brian MacKay,

@BrianMacKay Zgodził się, że tak to wygląda, ale pochodzi z klasy UrlUtility i jest używany w kontekście łączenia adresów URL

2
Edytowano, aby wyjaśnić, do której klasy należy

Zachowaj ostrożność podczas korzystania z tej klasy, reszta klasy zawiera artefakty specyficzne dla SharePoint.
Harry Berry,

4

Uważam, że następujące przydatne i ma następujące funkcje:

  • Rzuca na pustą lub białą przestrzeń
  • Pobiera wiele paramsparametrów dla wielu segmentów adresu URL
  • rzuca na zero lub pusty

Klasa

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

Testy

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 dzięki za edycję
TheGeneral

Niektóre problemy z testami: // Wynik = test1 / test2 / test3 \ dla czwartego i ostatni z testów rzucania daje ArgumentNullException zamiast ArgumentException
Moriya

3

Moje ogólne rozwiązanie:

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

Ta metoda pomocnicza jest bardzo elastyczna i działa dobrze w wielu różnych przypadkach użycia. Dziękuję Ci!
Shiva

3

Stworzyłem tę funkcję, która ułatwi Ci życie:

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

Działa zarówno w przypadku adresów URL, jak i zwykłych ścieżek.

Stosowanie:

    // 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

Dlaczego nie skorzystać z poniższych.

System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")

Szukałem wersji PowerShell tego co byłoby: [System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")Jednak to nie powiedzie się z powodu: /Images/Image.jpg. Usuń /drugą ścieżkę podrzędną i zadziała:[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
Podważ

Fajny pomysł, ale kończy się niepowodzeniem, gdy jeden z parametrów ma wartość NULL.
pholpar

2

Reguły podczas łączenia adresów URL z identyfikatorem URI

Aby uniknąć dziwnego zachowania, należy przestrzegać jednej zasady:

  • Ścieżka (katalog) musi kończyć się na „/”. Jeśli ścieżka kończy się bez „/”, ostatnia część jest traktowana jak nazwa pliku i zostanie połączona podczas próby połączenia z kolejną częścią adresu URL.
  • Jest jeden wyjątek: podstawowy adres URL (bez informacji o katalogu) nie musi kończyć się na „/”
  • część ścieżki nie może zaczynać się od „/”. Jeśli zaczyna się od „/”, wszystkie istniejące informacje względne z adresu URL są string.Emptyusuwane ... dodanie ścieżki części spowoduje usunięcie katalogu względnego z adresu URL!

Jeśli przestrzegasz powyższych zasad, możesz łączyć adresy URL z poniższym kodem. W zależności od sytuacji możesz dodać wiele części katalogu do adresu 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

Jeśli nie chcesz dodawać zależności innej firmy, takiej jak Flurl, ani tworzyć niestandardowej metody rozszerzenia, w programie ASP.NET Core (dostępnym również w Microsoft.Owin), możesz użyć tego, PathStringco jest przeznaczone do budowania identyfikatora URI ścieżki Następnie możesz utworzyć pełny identyfikator URI, używając kombinacji tego Urii UriBuilder.

W takim przypadku byłoby to:

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

To daje wszystkie części składowe bez konieczności określania separatorów w podstawowym adresie URL. Niestety, PathStringwymaga, że /jest dołączane do każdego łańcucha, w przeciwnym razie w rzeczywistości wyrzuca ArgumentException! Ale przynajmniej możesz zbudować swój URI deterministycznie w sposób, który można łatwo przetestować jednostkowo.


2

Mam więc inne podejście, podobne do wszystkich, którzy korzystali z UriBuilder.

Nie chciałem podzielić moją baseURL (który może zawierać część ścieżki - np http://mybaseurl.com/dev/ ) jako javajavajavajavajava zrobił.

Poniższy fragment kodu pokazuje kod + testy.

Uwaga: to rozwiązanie obniża liczbę hostów i dodaje port. Jeśli nie jest to pożądane, można napisać ciąg znaków, np. Wykorzystując Uriwłaściwość 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));
         }
  }

Testowane z .NET Core 2.1 w systemie Windows 10.

Dlaczego to działa?

Mimo że Path.Combinezwróci ukośniki odwrotne (przynajmniej w systemie Windows), UriBuilder obsługuje tę sprawę w Setterze Path.

Zaczerpnięte z https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs (pamiętaj o wezwaniu do string.Replace)

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

Czy to najlepsze podejście?

Z pewnością to rozwiązanie jest dość samoopisujące (przynajmniej moim zdaniem). Ale polegasz na nieudokumentowanej (przynajmniej nie znalazłem nic dzięki szybkiemu wyszukiwaniu w Google) „funkcji” interfejsu API .NET. Może to ulec zmianie w przyszłej wersji, dlatego prosimy o objęcie metody testami.

Istnieją testy w https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs ( Path_Get_Set), które sprawdzają, czy dane \są poprawnie przekształcone.

Uwaga dodatkowa: Można również pracować UriBuilder.Uribezpośrednio z właściwością, jeśli identyfikator System.UriURI zostanie użyty dla ctor.


To bardzo niezawodne podejście. Kciuki w górę za test jednostkowy !!
aggsol

2

Dla każdego, kto szuka jednej linijki i po prostu chce połączyć części ścieżki bez tworzenia nowej metody lub odwoływania się do nowej biblioteki lub konstruowania wartości URI i konwertowania jej na ciąg, a następnie ...

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

To dość proste, ale nie widzę, czego więcej potrzebujesz. Jeśli boisz się podwojonego „/”, możesz po prostu zrobić .Replace("//", "/")później. Jeśli boisz się zastąpić podwójny „//” w „https: //”, to zamiast tego wykonaj jedno łączenie, zamień podwójne „/”, a następnie dołącz do adresu URL witryny (jednak jestem prawie pewien, że większość przeglądarek automatycznie przekonwertować wszystko za pomocą „https:” z przodu, aby odczytać w odpowiednim formacie). Wyglądałoby to tak:

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

Istnieje wiele odpowiedzi, które poradzą sobie z powyższymi, ale w moim przypadku potrzebowałem go tylko raz w jednym miejscu i nie będę musiał na nim polegać. Ponadto bardzo łatwo jest zobaczyć, co się tutaj dzieje.

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


1

Posługiwać się:

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

Ma tę zaletę, że zachowuje się dokładnie tak samo Path.Combine.


1

Oto moje podejście i wykorzystam je również dla siebie:

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

Jest to dopuszczalne tylko w twoim przypadku. Są przypadki, które mogą złamać twój kod. Ponadto nie wykonałeś właściwego kodowania części ścieżki. Może to być ogromna luka w zabezpieczeniach, jeśli chodzi o atak skryptowy między witrynami.
Believe2014

Zgadzam się z twoimi punktami. Kod powinien po prostu łączyć dwie części adresu URL.
Amit Bhagat

1

Użyj tego:

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

Niezły dotyk z „WebPath”. :) Kod może być jednak niepotrzebnie gęsty - ciężko mi na to spojrzeć i powiedzieć: tak, to idealnie. To sprawia, że ​​chcę zobaczyć testy jednostkowe. Może to tylko ja!
Brian MacKay,

1
x.StartsWith („/”) &&! x.StartsWith („http”) - dlaczego sprawdzanie HTTP? co zyskujesz
pingwin

Nie chcesz próbować usuwać ukośnika, jeśli zaczyna się od http.
Martin Murphy

@BrianMacKay, nie jestem pewien, czy dwa liniowce gwarantują test jednostkowy, ale jeśli chcesz, możesz je podać. To nie jest tak, że akceptuję łatki lub cokolwiek, ale edytuj sugestię.
Martin Murphy

1

Odkryłem, że Urikonstruktor zamienia „\” na „/”. Możesz więc użyć również Path.Combinez Urikonstruktorem.

 Uri baseUri = new Uri("http://MyUrl.com");
 string path = Path.Combine("Images", "Image.jpg");
 Uri myUri = new Uri(baseUri, path);

1

Dla tego, co warto, oto kilka metod rozszerzenia. Pierwszy połączy ścieżki, a drugi doda parametry do adresu 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

Jak stwierdzono w innych odpowiedziach, albo nowe, Uri()albo TryCreate()można zrobić tyknięcie. Jednak podstawowy Uri musi się kończyć, /a krewny NIE musi zaczynać /; w przeciwnym razie usunie końcową część podstawowego adresu URL

Myślę, że najlepiej to zrobić jako metodę rozszerzenia, tj

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

i użyć go:

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

Pod względem wydajności zużywa więcej zasobów, niż potrzebuje, ze względu na klasę Uri, która wykonuje wiele analiz i weryfikacji; bardzo zgrubne profilowanie (Debugowanie) wykonało milion operacji w około 2 sekundy. Będzie to działać w większości scenariuszy, jednak aby być bardziej wydajnym, lepiej manipulować wszystkim jako ciągami, zajmuje to 125 milisekund na 1 milion operacji. To znaczy

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

A jeśli nadal chcesz zwrócić identyfikator URI, zajmuje to około 600 milisekund na 1 milion operacji.

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

Mam nadzieję, że to pomoże.


1

Myślę, że powinno to zapewnić większą elastyczność, ponieważ możesz poradzić sobie z tyloma segmentami ścieżki, ile chcesz:

public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.