1538 Stimmen

Path.Combine für URLs?

Pfad.Kombinieren ist praktisch, aber gibt es eine ähnliche Funktion im .NET-Framework für URLs ?

Ich bin auf der Suche nach einer Syntax wie dieser:

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

die zurückkehren würde:

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

9voto

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

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

Das folgende Snippet zeigt den Code + Tests.

Vorsicht! Bei dieser Lösung wird der Host kleingeschrieben und ein Port angehängt. Wenn dies nicht erwünscht ist, kann man eine String-Darstellung schreiben, indem man z.B. die Uri Eigentum von 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?

Auch wenn Path.Combine wird Backslashes zurückgeben (zumindest unter Windows), der UriBuilder behandelt 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 die Aufforderung 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 dies der beste Ansatz?

Sicherlich ist diese Lösung ziemlich selbsterklärend (zumindest meiner Meinung nach). Aber Sie verlassen sich auf undokumentierte (zumindest fand ich nichts mit einer schnellen Google-Suche) "Funktion" von der .NET-API. Dies kann sich mit einer zukünftigen Version ändern, also decken Sie bitte die Methode mit Tests ab.

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

Nebenbemerkung: Man könnte auch mit dem UriBuilder.Uri Eigenschaft direkt zu verwenden, wenn die uri für eine System.Uri ctor.

9voto

Believe2014 Punkte 3784

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

In beiden Fällen kann es passieren, dass Sie ein falsches Ergebnis erhalten, weil diese Methoden die relativen Teile des ersten Parameters abschneiden baseUri d.h. von etwas wie http://google.com/some/thing à 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 Unit-Tests zur Demonstration der Verwendung finden Sie unter https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs

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

Enter image description here

6voto

Dave Black Punkte 6469

Ich habe eine Version zur Erstellung von Zeichenketten ohne Zuweisung, die ich mit großem Erfolg verwendet habe.

HINWEIS:

  1. Für die erste Zeichenkette: Das Trennzeichen wird mit TrimEnd(separator) - also nur ab dem Ende der Zeichenkette.
  2. Für die Reste: Es trimmt das Trennzeichen mit Trim(separator) - also sowohl Anfang als auch Ende der Pfade
  3. Es wird kein abschließender Schrägstrich/Trennzeichen angehängt. Mit einer einfachen Änderung lässt sich diese Fähigkeit jedoch hinzufügen.

Ich hoffe, Sie finden dies nützlich!

/// <summary>
/// This implements an allocation-free string creation to construct the path.
/// This uses 3.5x LESS memory and is 2x faster than some alternate methods (StringBuilder, interpolation, string.Concat, etc.).
/// </summary>
/// <param name="str"></param>
/// <param name="paths"></param>
/// <returns></returns>
public static string ConcatPath(this string str, params string[] paths)
{
    const char separator = '/';
    if (str == null) throw new ArgumentNullException(nameof(str));

    var list = new List<ReadOnlyMemory<char>>();
    var first = str.AsMemory().TrimEnd(separator);

    // get length for intial string after it's trimmed
    var length = first.Length;
    list.Add(first);

    foreach (var path in paths)
    {
        var newPath = path.AsMemory().Trim(separator);
        length += newPath.Length + 1;
        list.Add(newPath);
    }

    var newString = string.Create(length, list, (chars, state) =>
    {
        // NOTE: We don't access the 'list' variable in this delegate since 
        // it would cause a closure and allocation. Instead we access the state parameter.

        // track our position within the string data we are populating
        var position = 0;

        // copy the first string data to index 0 of the Span<char>
        state[0].Span.CopyTo(chars);

        // update the position to the new length
        position += state[0].Span.Length;

        // start at index 1 when slicing
        for (var i = 1; i < state.Count; i++)
        {
            // add a separator in the current position and increment position by 1
            chars[position++] = separator;

            // copy each path string to a slice at current position
            state[i].Span.CopyTo(chars.Slice(position));

            // update the position to the new length
            position += state[i].Length;
        }
    });
    return newString;
}

mit Benchmark DotNet-Ausgabe:

|                Method |     Mean |    Error |   StdDev |   Median | Ratio | RatioSD |  Gen 0 | Allocated |
|---------------------- |---------:|---------:|---------:|---------:|------:|--------:|-------:|----------:|
| ConcatPathWithBuilder | 404.1 ns | 27.35 ns | 78.48 ns | 380.3 ns |  1.00 |    0.00 | 0.3347 |   1,400 B |
|            ConcatPath | 187.2 ns |  5.93 ns | 16.44 ns | 183.2 ns |  0.48 |    0.10 | 0.0956 |     400 B |

6voto

er sd Punkte 71

Wenn Sie keine Abhängigkeit wie Flurl haben wollen, können Sie dessen Quellcode verwenden:

    /// <summary>
    /// Basically a Path.Combine for URLs. Ensures exactly one '/' separates each segment,
    /// and exactly on '&amp;' separates each query parameter.
    /// URL-encodes illegal characters but not reserved characters.
    /// </summary>
    /// <param name="parts">URL parts to combine.</param>
    public static string Combine(params string[] parts) {
        if (parts == null)
            throw new ArgumentNullException(nameof(parts));

        string result = "";
        bool inQuery = false, inFragment = false;

        string CombineEnsureSingleSeparator(string a, string b, char separator) {
            if (string.IsNullOrEmpty(a)) return b;
            if (string.IsNullOrEmpty(b)) return a;
            return a.TrimEnd(separator) + separator + b.TrimStart(separator);
        }

        foreach (var part in parts) {
            if (string.IsNullOrEmpty(part))
                continue;

            if (result.EndsWith("?") || part.StartsWith("?"))
                result = CombineEnsureSingleSeparator(result, part, '?');
            else if (result.EndsWith("#") || part.StartsWith("#"))
                result = CombineEnsureSingleSeparator(result, part, '#');
            else if (inFragment)
                result += part;
            else if (inQuery)
                result = CombineEnsureSingleSeparator(result, part, '&');
            else
                result = CombineEnsureSingleSeparator(result, part, '/');

            if (part.Contains("#")) {
                inQuery = false;
                inFragment = true;
            }
            else if (!inFragment && part.Contains("?")) {
                inQuery = true;
            }
        }
        return EncodeIllegalCharacters(result);
    }

    /// <summary>
    /// URL-encodes characters in a string that are neither reserved nor unreserved. Avoids encoding reserved characters such as '/' and '?'. Avoids encoding '%' if it begins a %-hex-hex sequence (i.e. avoids double-encoding).
    /// </summary>
    /// <param name="s">The string to encode.</param>
    /// <param name="encodeSpaceAsPlus">If true, spaces will be encoded as + signs. Otherwise, they'll be encoded as %20.</param>
    /// <returns>The encoded URL.</returns>
    public static string EncodeIllegalCharacters(string s, bool encodeSpaceAsPlus = false) {
        if (string.IsNullOrEmpty(s))
            return s;

        if (encodeSpaceAsPlus)
            s = s.Replace(" ", "+");

        // Uri.EscapeUriString mostly does what we want - encodes illegal characters only - but it has a quirk
        // in that % isn't illegal if it's the start of a %-encoded sequence https://stackoverflow.com/a/47636037/62600

        // no % characters, so avoid the regex overhead
        if (!s.Contains("%"))
            return Uri.EscapeUriString(s);

        // pick out all %-hex-hex matches and avoid double-encoding 
        return Regex.Replace(s, "(.*?)((%[0-9A-Fa-f]{2})|$)", c => {
            var a = c.Groups[1].Value; // group 1 is a sequence with no %-encoding - encode illegal characters
            var b = c.Groups[2].Value; // group 2 is a valid 3-character %-encoded sequence - leave it alone!
            return Uri.EscapeUriString(a) + b;
        });
    }

6voto

DubDub Punkte 970

Wer auf der Suche nach einem Einzeiler ist und einfach nur Teile eines Pfades verbinden möchte, ohne eine neue Methode zu erstellen oder eine neue Bibliothek zu referenzieren oder einen URI-Wert zu konstruieren und diesen in eine Zeichenkette zu konvertieren, dann...

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

Es ist ziemlich einfach, aber ich wüsste nicht, was Sie noch brauchen. Wenn Sie Angst vor dem doppelten '/' haben, können Sie einfach ein .Replace("//", "/") nachher. Wenn Sie Angst davor haben, das doppelte "//" in "https://" zu ersetzen, machen Sie stattdessen eine Verknüpfung, ersetzen Sie das doppelte "/" und fügen Sie dann die Website-URL ein (ich bin mir jedoch ziemlich sicher, dass die meisten Browser alles mit "https:" davor automatisch umwandeln, um es im richtigen Format zu lesen). Dies würde wie folgt aussehen:

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

Es gibt viele Antworten hier, die alle oben genannten Punkte behandeln, aber in meinem Fall brauchte ich es nur einmal an einem Ort und werde mich nicht stark darauf verlassen müssen. Außerdem ist es wirklich einfach zu sehen, was hier vor sich geht.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X