Path.Combine für URLs?


1243

Path.Combine ist praktisch, aber gibt es im .NET Framework eine ähnliche Funktion für URLs ?

Ich suche nach einer Syntax wie dieser:

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

was zurückkehren würde:

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


14
Flurl enthält eine Url.CombineMethode, die genau das tut.
Todd Menier

2
Tatsächlich wird das // vom Routing der Website oder des Servers und nicht vom Browser verwaltet. Es wird gesendet, was Sie in die Adressleiste eingegeben haben. Aus diesem Grund treten Probleme auf, wenn wir htp: // anstelle von http: // eingeben. Daher kann // auf einigen Websites große Probleme verursachen. Ich schreibe eine DLL für einen Crawler, der eine bestimmte Website verwaltet, die eine 404 auslöst, wenn Sie // in der URL haben.
Dave Gordon

Antworten:


73

Es gibt einen Kommentar von Todd Menier darüber, dass Flurl a enthält Url.Combine.

Mehr Details:

Url.Combine ist im Grunde eine Path.Combine für URLs, die ein und nur ein Trennzeichen zwischen Teilen sicherstellt:

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" 

Holen Sie sich Flurl.Http auf NuGet :

PM> Install-Package Flurl.Http

Oder holen Sie sich den eigenständigen URL-Builder ohne die HTTP-Funktionen:

PM> Install-Package Flurl


4
Nun, diese Frage hat viel Verkehr und die Antwort mit mehr als 1000 Upvotes funktioniert nicht in allen Fällen. Jahre später benutze ich tatsächlich Flurl dafür, also akzeptiere ich dieses. Es scheint in allen Fällen zu funktionieren, denen ich begegnet bin. Wenn Leute keine Abhängigkeit annehmen wollen, habe ich eine Antwort gepostet, die auch gut funktioniert.
Brian MacKay

und wenn Sie nicht verwenden Flurlund eine leichte Version bevorzugen würden, github.com/jean-lourenco/UrlCombine
lizzy91

1157

Uri hat einen Konstruktor, der dies für Sie tun sollte: new Uri(Uri baseUri, string relativeUri)

Hier ist ein Beispiel:

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

Hinweis des Editors: Achtung, diese Methode funktioniert nicht wie erwartet. In einigen Fällen kann ein Teil von baseUri abgeschnitten werden. Siehe Kommentare und andere Antworten.


369
Ich mag die Verwendung der Uri-Klasse, leider verhält sie sich nicht wie Path.Combine, wie es das OP verlangt. Zum Beispiel neue Uri (neue Uri (" test.com/mydirectory/" ), "/helloworld.aspx"). ToString () gibt Ihnen " test.com/helloworld.aspx "; Das wäre falsch, wenn wir ein Ergebnis im Path.Combine-Stil wünschen würden.
Doktor Jones

195
Es ist alles in den Schrägstrichen. Wenn der relative Pfadteil mit einem Schrägstrich beginnt, verhält er sich wie beschrieben. Wenn Sie den Schrägstrich jedoch weglassen, funktioniert er wie erwartet (beachten Sie den fehlenden Schrägstrich im zweiten Parameter): new Uri (neuer Uri (" test.com/mydirectory/" ), "helloworld.aspx" ) .ToString () führt zu " test.com/mydirectory/helloworld.aspx ". Path.Combine verhält sich ähnlich. Wenn der relative Pfadparameter mit einem Schrägstrich beginnt, wird nur der relative Pfad zurückgegeben und nicht kombiniert.
Joel Beckham

70
Wenn Ihre baseUri zufällig "test.com/mydirectory/mysubdirectory" wäre, wäre das Ergebnis "test.com/mydirectory/helloworld.aspx" anstelle von "test.com/mydirectory/mysubdirectory/helloworld.aspx". Der subtile Unterschied ist das Fehlen eines abschließenden Schrägstrichs beim ersten Parameter. Ich bin alle dafür, vorhandene Framework-Methoden zu verwenden. Wenn ich dort bereits den abschließenden Schrägstrich haben muss, dann denke ich, dass partUrl1 + partUrl2 viel weniger riecht - ich hätte diesen abschließenden Schrägstrich möglicherweise schon eine ganze Weile verfolgen können um nicht string concat zu machen.
Carl

64
Der einzige Grund, warum ich eine URI-Kombinationsmethode möchte, ist, dass ich nicht nach dem abschließenden Schrägstrich suchen muss. Request.ApplicationPath ist '/', wenn sich Ihre Anwendung im Stammverzeichnis befindet, aber '/ foo', wenn dies nicht der Fall ist.
Nickd

24
Ich -1 diese Antwort, weil dies das Problem nicht beantwortet. Wenn Sie eine URL kombinieren möchten, z. B. wenn Sie Path.Combine verwenden möchten, möchten Sie sich nicht um das nachfolgende / kümmern. und damit muss man sich kümmern. Ich bevorzuge Lösung von Brian MacKay oder mdsharpe oben
Baptiste Pernet

161

Dies kann eine entsprechend einfache Lösung sein:

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

7
+1: Obwohl dies keine Pfade im relativen Stil (../../whatever.html) behandelt, mag ich diesen wegen seiner Einfachheit. Ich würde auch Verkleidungen für das Zeichen '\' hinzufügen.
Brian MacKay

3
Siehe meine Antwort für eine ausführlichere Version davon.
Brian MacKay

149

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

Wird zurückkehren:

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


53
+1: Das ist gut, obwohl ich ein irrationales Problem mit dem Ausgabeparameter habe. ;)
Brian MacKay

10
@Brian: Wenn es hilft, haben alle TryXXX-Methoden ( int.TryParse, DateTime.TryParseExact) diesen Ausgabeparameter, um die Verwendung in einer if-Anweisung zu vereinfachen. Übrigens müssen Sie die Variable nicht wie in diesem Beispiel von Ryan initialisieren.
Abel

41
Diese Antwort hat das gleiche Problem wie Joels : Beitritt test.com/mydirectory/und /helloworld.aspxführt dazu, dass test.com/helloworld.aspxes anscheinend nicht das ist, was Sie wollen.
Matt Kocaj

3
Hallo, dies ist aus folgenden Gründen fehlgeschlagen: if (Uri.TryCreate (neuer Uri (" localhost / MyService /" ), "/ Event / SomeMethod? Abc = 123", Ergebnis)) {Console.WriteLine (Ergebnis); } Es zeigt mir das Ergebnis als: localhost / Event / SomeMethod? Abc = 123 Hinweis: "http: //" wird hier von der Basis Uri durch stackoverflow ersetzt
Faisal Mq

3
@FaisalMq Dies ist das richtige Verhalten, da Sie einen root-relativen zweiten Parameter übergeben haben. Wenn Sie den führenden / zweiten Parameter weggelassen hätten, hätten Sie das erwartete Ergebnis erhalten.
Tom Lint

127

Hier gibt es bereits einige gute Antworten. Basierend auf dem Vorschlag von mdsharpe ist hier eine Erweiterungsmethode, die leicht verwendet werden kann, wenn Sie mit Uri-Instanzen arbeiten möchten:

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

Und Anwendungsbeispiel:

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

Dadurch wird http://example.com/subpath/part1/part2 erstellt


2
Diese Lösung macht es trivial, eine statische Methode von UriUtils.Combine ("Basis-URL", "Teil1", "Teil2", ...) zu schreiben, die Path.Combine () sehr ähnlich ist. Nett!
Angularsen

Um relative URIs zu unterstützen, musste ich ToString () anstelle von AbsoluteUri und UriKind.AbsoluteOrRelative im Uri-Konstruktor verwenden.
Angularsen

Danke für den Tipp über relative Uris. Leider macht es Uri nicht einfach, mit relativen Pfaden umzugehen, da bei Request.ApplicationPath immer einiges durcheinander ist. Vielleicht könnten Sie auch versuchen, neues Uri (HttpContext.Current.Request.ApplicationPath) als Basis zu verwenden und einfach Append darauf aufrufen? Dies gibt Ihnen absolute Pfade, sollte aber überall innerhalb der Site-Struktur funktionieren.
Ales Potocnik Hahonina

Großartig. Ich bin froh, dass es jemand anderem geholfen hat. Ich benutze dies seit einiger Zeit und hatte keine Probleme.
Ales Potocnik Hahonina

Ich habe auch hinzugefügt, ob einer der anzuhängenden Pfade weder null noch eine leere Zeichenfolge ist.
n.podbielski

92

Die Antwort von Ryan Cook entspricht in etwa meiner Suche und ist möglicherweise besser für andere Entwickler geeignet. Es fügt jedoch http: // am Anfang der Zeichenfolge hinzu und formatiert im Allgemeinen etwas mehr als ich.

Für meine Anwendungsfälle ist es auch nicht wichtig, relative Pfade aufzulösen.

Die Antwort von mdsharp enthält auch den Keim einer guten Idee, obwohl für diese tatsächliche Implementierung einige weitere Details erforderlich waren, um vollständig zu sein. Dies ist ein Versuch, es zu konkretisieren (und ich verwende dies in der Produktion):

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

Dieser Code besteht den folgenden Test, der sich zufällig in VB befindet:

<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
Apropos Details: Was ist mit der obligatorischen, ArgumentNullException("url1")wenn das Argument ist Nothing? Sorry, nur wählerisch zu sein ;-). Beachten Sie, dass ein Backslash in einem URI nichts zu tun hat (und wenn er vorhanden ist, sollte er nicht gekürzt werden), sodass Sie ihn aus Ihrem TrimXXX entfernen können.
Abel

4
Sie können params string [] verwenden und rekursiv verbinden, um mehr als 2 Kombinationen zuzulassen
Jaider

4
Ich wünschte, dies wäre in der Basisklassenbibliothek wie Path.Combine.
Uriah Blatherwick

1
@MarkHurd Ich habe den Code erneut bearbeitet, sodass er verhaltensmäßig mit dem C # identisch und auch syntaktisch äquivalent ist.
JJS

1
@BrianMacKay Ich habe es gebrochen, Markhurd hat auf meinen Fehler hingewiesen und zurückgerollt, ich habe erneut aktualisiert ... Prost
JJS

36

Path.Combine funktioniert bei mir nicht, da es Zeichen wie "|" geben kann. in QueryString-Argumenten und damit der URL, was zu einer ArgumentException führt.

Ich habe zuerst den neuen Uri(Uri baseUri, string relativeUri)Ansatz ausprobiert , der für mich aufgrund von URIs wie http://www.mediawiki.org/wiki/Special:SpecialPages:

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

führt zu Special: SpecialPages, da der Doppelpunkt danach Specialein Schema kennzeichnet.

Also musste ich endlich den Weg von mdsharpe / Brian MacKays nehmen und ihn etwas weiterentwickeln, um mit mehreren URI-Teilen zu arbeiten:

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

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


1
+1: Jetzt reden wir ... Ich werde das ausprobieren. Dies könnte sogar die neu akzeptierte Antwort sein. Nachdem ich versucht habe, eine neue Uri () -Methode anzuwenden, mag ich sie wirklich nicht. Zu finnicky.
Brian MacKay

Genau das brauchte ich! War kein Fan davon, sich darum kümmern zu müssen, wo ich nachgestellte Schrägstriche usw. platziere ...
Gromer

+1 für das Einrollen der Nullprüfung, damit sie nicht explodiert.
NightOwl888

Count () sollte Length sein, damit Sie Linq nicht nur dafür in Ihre Bibliothek aufnehmen müssen.
PRMan

Genau das habe ich gesucht.
ThePeter

34

Basierend auf der von Ihnen angegebenen Beispiel- URL gehe ich davon aus, dass Sie URLs kombinieren möchten, die sich auf Ihre Website beziehen.

Basierend auf dieser Annahme werde ich diese Lösung als die am besten geeignete Antwort auf Ihre Frage vorschlagen: "Path.Combine ist praktisch, gibt es eine ähnliche Funktion im Framework für URLs?"

Da es im Framework für URLs eine ähnliche Funktion gibt, schlage ich die richtige vor: "VirtualPathUtility.Combine" -Methode. Hier ist der MSDN-Referenzlink: VirtualPathUtility.Combine-Methode

Es gibt eine Einschränkung: Ich glaube, dies funktioniert nur für URLs, die sich auf Ihre Website beziehen (das heißt, Sie können sie nicht zum Generieren von Links zu einer anderen Website verwenden. Zum Beispiel var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).


+1, weil es nahe an dem liegt, wonach ich suche, obwohl es ideal wäre, wenn es für eine alte URL funktionieren würde. Ich verdopple es wird viel eleganter als das, was mdsharpe vorgeschlagen hat.
Brian MacKay

2
Die Einschränkung ist richtig, es kann nicht mit absoluten Uris arbeiten und das Ergebnis ist immer relativ von der Wurzel. Aber es hat einen zusätzlichen Vorteil, es verarbeitet die Tilde wie bei "~ /". Dies macht es zu einer Abkürzung für Server.MapPathund zum Kombinieren.
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
Um es zum Schreiben zu bringen, müssen Sie das erste / zweite Argument entfernen, dh "/ Images" - / Path.Combine (" Http://MyUrl.com ", "Images / Image.jpg")
Per G

8
@SliverNinja Das stimmt nicht Der Wert dieses Feldes ist ein Backslash ('\') unter UNIX und ein Schrägstrich ('/') unter Windows- und Macintosh-Betriebssystemen. Wenn Sie Mono auf einem Linux-System verwenden, erhalten Sie das falsche Trennzeichen.
user247702

6
Alle, die sich mit dem Directory Separator beschäftigen, vergessen, dass die Zeichenfolgen möglicherweise von einem anderen Betriebssystem stammen als Sie. Ersetzen Sie einfach den Backslash durch den Forward Slash und schon sind Sie abgesichert.
JeremyWeir

17

Ich habe gerade eine kleine Erweiterungsmethode zusammengestellt:

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

Es kann folgendermaßen verwendet werden:

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

12

Witziges Beispiel, Ryan, um mit einem Link zur Funktion zu enden. Gut gemacht.

Eine Empfehlung Brian: Wenn Sie diesen Code in eine Funktion einschließen, möchten Sie möglicherweise einen UriBuilder verwenden, um die Basis-URL vor dem TryCreate-Aufruf zu umschließen.

Andernfalls MUSS die Basis-URL das Schema enthalten (wobei der UriBuilder http: // annimmt). Nur ein Gedanke:

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

Eine einfache Möglichkeit, sie zu kombinieren und sicherzustellen, dass sie immer korrekt sind, ist:

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

+1, obwohl dies der Antwort von mdsharpe sehr ähnlich ist, die ich in meiner Antwort verbessert habe. Diese Version funktioniert hervorragend, es sei denn, Url2 beginnt mit / oder \ oder Url1 endet versehentlich mit \ oder eine ist leer! :)
Brian MacKay

9

Das Kombinieren mehrerer Teile einer URL kann etwas schwierig sein. Sie können den Zwei-Parameter-Konstruktor Uri(baseUri, relativeUri)oder die Uri.TryCreate()Utility-Funktion verwenden.

In jedem Fall können Sie ein falsches Ergebnis zurückkehren , weil diese Methoden halten auf Kürzen die relativen Teile aus den ersten Parametern am Ende baseUri, also von so etwas wie http://google.com/some/thingzu http://google.com.

Um mehrere Teile zu einer endgültigen URL kombinieren zu können, können Sie die beiden folgenden Funktionen kopieren:

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

Den vollständigen Code mit Komponententests zum Nachweis der Verwendung finden Sie unter https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs

Ich habe Unit-Tests, um die drei häufigsten Fälle abzudecken:

Geben Sie hier die Bildbeschreibung ein


2
Sieht für mich ziemlich gut aus. Obwohl Sie die I-Schleife zur besseren Übersichtlichkeit durch eine foreach-Schleife ersetzen könnten.
Chris Marisic

Danke Chris. Ich habe gerade meinen Code geändert, um Foreach zu verwenden.
Believe2014

1
+1 für all den zusätzlichen Aufwand. Ich muss diese Frage ein wenig für einige der höher bewerteten Antworten beibehalten, Sie haben den Handschuh geworfen. ;)
Brian MacKay

Entschuldigung, aber nicht genug. Funktioniert in den wenigen Fällen, die Sie zeigen, ist jedoch weit davon entfernt, in allen Kombinationen verwendet zu werden. Zum Beispiel verursachen Doppelpunkte im Pfad Schaden.
Gábor

Können Sie bitte ein Beispiel geben, was Sie meinen? Ich werde das Problem gerne beheben und den nächsten Benutzern helfen.
Believe2014

7

Ich fand, dass es UriBuilderfür so etwas wirklich gut funktioniert hat:

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

Weitere Konstruktoren und Dokumentationen finden Sie unter UriBuilder-Klasse - MSDN .


4

Hier ist die Microsoft-Methode (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);
    }

Quelle: GitHub


Es sieht so aus, als ob dies eher für Pfade als für URLs gilt.
Brian MacKay

@BrianMacKay Einverstanden, dass es so aussieht, aber es ist aus der UrlUtility-Klasse und wird im Zusammenhang mit der Kombination von URLs verwendet

2
Bearbeitet, um zu klären, zu welcher Klasse es gehört

Seien Sie vorsichtig, wenn Sie diese Klasse verwenden. Der Rest der Klasse enthält SharePoint-spezifische Artefakte.
Harry Berry

4

Ich finde Folgendes nützlich und habe die folgenden Funktionen:

  • Wirft auf Null oder Leerzeichen
  • Nimmt mehrere paramsParameter für mehrere URL-Segmente
  • wirft auf null oder leer

Klasse

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

Tests

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 danke für die Bearbeitung
TheGeneral

Einige Probleme mit den Tests: // Ergebnis = test1 / test2 / test3 \ für den vierten und den letzten Wurf-Test gibt ArgumentNullException anstelle von ArgumentException
Moriya

3

Meine generische Lösung:

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

Diese Hilfsmethode ist sehr flexibel und funktioniert in vielen verschiedenen Anwendungsfällen gut. Vielen Dank!
Shiva

3

Ich habe diese Funktion erstellt, die Ihnen das Leben erleichtern wird:

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

Es funktioniert sowohl für URLs als auch für normale Pfade.

Verwendungszweck:

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

Warum nicht einfach das Folgende verwenden.

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

Ich habe nach der PowerShell-Version gesucht, die lautet: [System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")Dies schlägt jedoch mit folgendem Ergebnis fehl : /Images/Image.jpg. Entfernen Sie die /aus dem zweiten Unterpfad und es funktioniert:[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
Underverse

Gute Idee, aber sie schlägt fehl, wenn einer der Parameter null ist.
Pholpar

2

Regeln beim Kombinieren von URLs mit einem URI

Um seltsames Verhalten zu vermeiden, muss eine Regel befolgt werden:

  • Der Pfad (Verzeichnis) muss mit '/' enden. Wenn der Pfad ohne '/' endet, wird der letzte Teil wie ein Dateiname behandelt und beim Versuch, ihn mit dem nächsten URL-Teil zu kombinieren, verkettet.
  • Es gibt eine Ausnahme: Die Basis-URL-Adresse (ohne Verzeichnisinformationen) muss nicht mit '/' enden.
  • Der Pfadteil darf nicht mit '/' beginnen. Wenn es mit '/' beginnt, werden alle vorhandenen relativen Informationen aus der URL gelöscht. Durch Hinzufügen eines string.EmptyTeilepfads wird auch das relative Verzeichnis aus der URL entfernt!

Wenn Sie die oben genannten Regeln befolgen, können Sie URLs mit dem folgenden Code kombinieren. Abhängig von Ihrer Situation können Sie der URL mehrere 'Verzeichnis'-Teile hinzufügen ...

        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

Wenn Sie in ASP.NET Core (auch in Microsoft.Owin verfügbar) keine Abhängigkeit von Drittanbietern wie Flurl hinzufügen oder eine benutzerdefinierte Erweiterungsmethode erstellen möchten, können Sie diese verwenden, die zum Erstellen PathStringdes URI vorgesehen ist Wege. Sie können dann Ihren vollständigen URI mit einer Kombination aus diesem Uriund erstellen UriBuilder.

In diesem Fall wäre es:

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

Auf diese Weise erhalten Sie alle Bestandteile, ohne die Trennzeichen in der Basis-URL angeben zu müssen. PathStringErfordert leider, dass /jeder Zeichenfolge vorangestellt wird, da sonst tatsächlich ein ArgumentException! Aber zumindest können Sie Ihren URI deterministisch auf eine Weise aufbauen, die leicht auf Einheiten getestet werden kann.


2

Ich habe also einen anderen Ansatz, ähnlich wie alle, die UriBuilder verwendet haben.

Ich wollte meine BaseUrl (die einen Teil des Pfads enthalten kann - z. B. http://mybaseurl.com/dev/ ) nicht wie javajavajavajavajava aufteilen .

Das folgende Snippet zeigt den Code + Tests.

Achtung: Diese Lösung verkleinert den Host und hängt einen Port an. Wenn dies nicht erwünscht ist, kann man eine Zeichenfolgendarstellung schreiben, indem man beispielsweise die UriEigenschaft von nutzt 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));
         }
  }

Getestet mit .NET Core 2.1 unter Windows 10.

Warum funktioniert das?

Obwohl Path.CombineBackslashes (mindestens unter Windows) zurückgegeben werden, behandelt der UriBuilder diesen Fall im Setter von Path.

Entnommen aus https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs (beachten Sie den Aufruf an string.Replace)

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

Ist das der beste Ansatz?

Sicherlich ist diese Lösung ziemlich selbstbeschreibend (zumindest meiner Meinung nach). Sie verlassen sich jedoch auf die undokumentierte "Funktion" der .NET-API (zumindest habe ich bei einer schnellen Google-Suche nichts gefunden). Dies kann sich mit einer zukünftigen Version ändern. Bitte behandeln Sie die Methode mit Tests.

Unter https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs ( Path_Get_Set) gibt es Tests, die prüfen, ob das \korrekt transformiert wurde.

Randnotiz: Man könnte auch UriBuilder.Uridirekt mit der Eigenschaft arbeiten, wenn der uri für einen System.Urictor verwendet wird.


Dies ist ein sehr zuverlässiger Ansatz. Daumen hoch für den Unit Test !!
Aggsol

2

Für alle, die nach einem Einzeiler suchen und einfach Teile eines Pfads verbinden möchten, ohne eine neue Methode zu erstellen oder auf eine neue Bibliothek zu verweisen oder einen URI-Wert zu erstellen und diesen in einen String umzuwandeln, dann ...

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

Es ist ziemlich einfach, aber ich sehe nicht, was Sie mehr brauchen. Wenn Sie Angst vor einem doppelten '/' haben, können Sie einfach .Replace("//", "/")danach eine machen. Wenn Sie Angst haben, das doppelte '//' in 'https: //' zu ersetzen, führen Sie stattdessen einen Join durch, ersetzen Sie das doppelte '/' und treten Sie dann der Website-URL bei (ich bin mir jedoch ziemlich sicher, dass die meisten Browser dies automatisch tun) konvertieren Sie alles mit 'https:' im Vordergrund, um es im richtigen Format zu lesen. Das würde so aussehen:

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

Hier gibt es viele Antworten, die alle oben genannten Fragen beantworten, aber in meinem Fall brauchte ich sie nur einmal an einem Ort und musste mich nicht stark darauf verlassen. Außerdem ist es sehr einfach zu sehen, was hier los ist.

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


1

Verwenden:

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

Es hat den Vorteil, sich genau so zu verhalten Path.Combine.


1

Hier ist mein Ansatz und ich werde ihn auch für mich selbst verwenden:

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

Dies ist nur für Ihren Fall akzeptabel. Es gibt Fälle, in denen Ihr Code beschädigt werden könnte. Außerdem haben Sie die Teile des Pfads nicht richtig codiert. Dies könnte eine große Sicherheitslücke sein, wenn es um Cross-Site-Scripting-Angriffe geht.
Believe2014

Ich stimme Ihren Punkten zu. Der Code soll einfach zwei URL-Teile kombinieren.
Amit Bhagat

1

Benutze das:

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

Netter Touch mit 'WebPath'. :) Der Code könnte jedoch unnötig dicht sein - es fällt mir schwer, einen Blick darauf zu werfen und zu sagen, ja, das ist perfekt. Ich möchte Unit-Tests sehen. Vielleicht bin das nur ich!
Brian MacKay

1
x.StartsWith ("/") &&! x.StartsWith ("http") - warum die http-Prüfung? was gewinnst du
Pinguat

Sie möchten nicht versuchen, den Schrägstrich zu entfernen, wenn er mit http beginnt.
Martin Murphy

@BrianMacKay, ich bin mir nicht sicher, ob ein Zwei-Liner einen Unit-Test rechtfertigt, aber wenn Sie möchten, können Sie gerne einen bereitstellen. Es ist nicht so, dass ich Patches oder ähnliches akzeptiere, aber Sie können den Vorschlag jederzeit bearbeiten.
Martin Murphy

1

Ich habe festgestellt, dass der UriKonstruktor '\' in '/' wechselt. Sie können also auch Path.Combinemit dem UriKonstruktor verwenden.

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

1

Für das, was es wert ist, hier ein paar Erweiterungsmethoden. Der erste kombiniert Pfade und der zweite fügt der URL Parameter hinzu.

    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

Wie in anderen Antworten zu finden, entweder neu Uri()oder TryCreate()kann das Häkchen machen. Die Basis Uri muss jedoch enden /und der Verwandte darf NICHT damit beginnen/ ; Andernfalls wird der nachfolgende Teil der Basis-URL entfernt

Ich denke, dies wird am besten als Erweiterungsmethode durchgeführt, dh

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

und um es zu benutzen:

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

In Bezug auf die Leistung verbraucht dies mehr Ressourcen als benötigt wird, da die Uri-Klasse viel analysiert und validiert. Ein sehr grobes Profiling (Debug) führte eine Million Operationen in ungefähr 2 Sekunden durch. Dies funktioniert in den meisten Szenarien. Um jedoch effizienter zu sein, ist es besser, alles als Zeichenfolgen zu bearbeiten. Dies dauert 125 Millisekunden für 1 Million Operationen. Dh

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

Wenn Sie dennoch einen URI zurückgeben möchten, dauert es ungefähr 600 Millisekunden für 1 Million Vorgänge.

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

Ich hoffe das hilft.


1

Ich denke, dies sollte Ihnen mehr Flexibilität geben, da Sie mit so vielen Pfadsegmenten umgehen können, wie Sie möchten:

public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.