Path.Combine for URLs?

2019-01-01 04:21发布

问题:

Path.Combine is handy, but is there a similar function in the .NET framework for URLs?

I\'m looking for syntax like this:

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

which would return:

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

回答1:

There is a Todd Menier\'s comment above that Flurl includes a Url.Combine.

More details:

Url.Combine is basically a Path.Combine for URLs, ensuring one and only one separator character between parts:

var url = Url.Combine(
    \"http://foo.com/\",
    \"/too/\", \"/many/\", \"/slashes/\",
    \"too\", \"few?\",
    \"x=1\", \"y=2\"
// result: \"http://www.foo.com/too/many/slashes/too/few?x=1&y=2\" 


回答2:

Uri has a constructor that should do this for you: new Uri(Uri baseUri, string relativeUri)

Here\'s an example:

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

Note from editor: Beware, this method does not work as expected. It can cut part of baseUri in some cases. See comments and other answers.



回答3:

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

Will return:

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



回答4:

This may be a suitably simple solution:

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


回答5:

There\'s already some great answers here. Based on mdsharpe suggestion, here\'s an extension method that can easily be used when you want to deal with Uri instances:

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

And usage example:

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

This will produce http://example.com/subpath/part1/part2



回答6:

Ryan Cook\'s answer is close to what I\'m after and may be more appropriate for other developers. However, it adds http:// to the beginning of the string and in general it does a bit more formatting than I\'m after.

Also, for my use cases, resolving relative paths is not important.

mdsharp\'s answer also contains the seed of a good idea, although that actual implementation needed a few more details to be complete. This is an attempt to flesh it out (and I\'m using this in production):

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

This code passes the following test, which happens to be in 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


回答7:

Based on the sample URL you provided, I\'m going to assume you want to combine URLs that are relative to your site.

Based on this assumption I\'ll propose this solution as the most appropriate response to your question which was: \"Path.Combine is handy, is there a similar function in the framework for URLs?\"

Since there the is a similar function in the framework for URLs I propose the correct is: \"VirtualPathUtility.Combine\" method. Here\'s the MSDN reference link: VirtualPathUtility.Combine Method

There is one caveat: I believe this only works for URLs relative to your site (that is, you cannot use it to generate links to another web site. For example, var url = VirtualPathUtility.Combine(\"www.google.com\", \"accounts/widgets\");).



回答8:

Path.Combine does not work for me because there can be characters like \"|\" in QueryString arguments and therefore the URL, which will result in an ArgumentException.

I first tried the new Uri(Uri baseUri, string relativeUri) approach, which failed for me because of URIs like http://www.mediawiki.org/wiki/Special:SpecialPages:

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

will result in Special:SpecialPages, because of the colon after Special that denotes a scheme.

So I finally had to take mdsharpe/Brian MacKays route and developed it a bit further to work with multiple URI parts:

public static string CombineUri(params string[] uriParts)
{
    string uri = string.Empty;
    if (uriParts != null && uriParts.Count() > 0)
    {
        char[] trims = new char[] { \'\\\\\', \'/\' };
        uri = (uriParts[0] ?? string.Empty).TrimEnd(trims);
        for (int i = 1; i < uriParts.Count(); i++)
        {
            uri = string.Format(\"{0}/{1}\", uri.TrimEnd(trims), (uriParts[i] ?? string.Empty).TrimStart(trims));
        }
    }
    return uri;
}

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



回答9:

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


回答10:

I just put together a small extension method:

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

It can be used like this:

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


回答11:

Witty example, Ryan, to end with a link to the function. Well done.

One recommendation Brian: if you wrap this code in a function, you may want to use a UriBuilder to wrap the base URL prior to the TryCreate call.

Otherwise, the base URL MUST include the scheme (where the UriBuilder will assume http://). Just a thought:

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


回答12:

Combining multiple parts of a URL could be a little bit tricky. You can use the two-parameter constructor Uri(baseUri, relativeUri), or you can use the Uri.TryCreate() utility function.

In either case, you might end up returning an incorrect result because these methods keep on truncating the relative parts off of the first parameter baseUri, i.e. from something like http://google.com/some/thing to http://google.com.

To be able to combine multiple parts into a final URL, you can copy the two functions below:

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

Full code with unit tests to demonstrate usage can be found at https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs

I have unit tests to cover the three most common cases:

\"Enter



回答13:

I found UriBuilder worked really well for this sort of thing:

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

See UriBuilder Class - MSDN for more constructors and documentation.



回答14:

An easy way to combine them and ensure it\'s always correct is:

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


回答15:

Here\'s Microsoft\'s (OfficeDev PnP) method 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);
    }

Source: GitHub



回答16:

My generic solution:

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


回答17:

I created this function that will make your life easier:

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

It works for URLs as well as normal paths.

Usage:

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


回答18:

I find the following useful and has the following features :

  • Throws on null or white space
  • Takes multiple params parameter for multiple Url segments
  • throws on null or empty

Class

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


回答19:

Rules while combining URLs with a URI

To avoid strange behaviour there\'s one rule to follow:

  • The path (directory) must end with \'/\'. If the path ends without \'/\', the last part is treated like a file-name, and it\'ll be concatenated when trying to combine with the next URL part.
  • There\'s one exception: the base URL address (without directory info) needs not to end with \'/\'
  • the path part must not start with \'/\'. If it start with \'/\', every existing relative information from URL is dropped...adding a string.Empty part path will remove the relative directory from the URL too!

If you follow rules above, you can combine URLs with the code below. Depending on your situation, you can add multiple \'directory\' parts to the 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();
        });


回答20:

Use:

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

It has the benefit of behaving exactly like Path.Combine.



回答21:

Here is my approach and I will use it for myself too:

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


回答22:

Use this:

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


回答23:

Why not just use the following.

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


回答24:

I used this code to solve the problem:

string[] brokenBaseUrl = Context.Url.TrimEnd(\'/\').Split(\'/\');
string[] brokenRootFolderPath = RootFolderPath.Split(\'/\');

for (int x = 0; x < brokenRootFolderPath.Length; x++)
{
    //if url doesn\'t already contain member, append it to the end of the string with / in front
    if (!brokenBaseUrl.Contains(brokenRootFolderPath[x]))
    {
        if (x == 0)
        {
            RootLocationUrl = Context.Url.TrimEnd(\'/\');
        }
        else
        {
            RootLocationUrl += String.Format(\"/{0}\", brokenRootFolderPath[x]);
        }
    }
}


回答25:

A simple one liner:

public static string Combine(this string uri1, string uri2) => $\"{uri1.TrimEnd(\'/\')}/{uri2.TrimStart(\'/\')}\";

Inspired by @Matt Sharpe\'s answer.



回答26:

Well, I just concatenate two strings and use regular expressions to do the cleaning part.

    public class UriTool
    {
        public static Uri Join(string path1, string path2)
        {
            string url = path1 + \"/\" + path2;
            url = Regex.Replace(url, \"(?<!http:)/{2,}\", \"/\");

            return new Uri(url);
        }
    }

So, you can use it like this:

    string path1 = \"http://someaddress.com/something/\";
    string path2 = \"/another/address.html\";
    Uri joinedUri = UriTool.Join(path1, path2);

    // joinedUri.ToString() returns \"http://someaddress.com/something/another/address.html\"


回答27:

I have combined all the previous answers:

    public static string UrlPathCombine(string path1, string path2)
    {
        path1 = path1.TrimEnd(\'/\') + \"/\";
        path2 = path2.TrimStart(\'/\');

        return Path.Combine(path1, path2)
            .Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
    }

    [TestMethod]
    public void TestUrl()
    {
        const string P1 = \"http://msdn.microsoft.com/slash/library//\";
        Assert.AreEqual(\"http://msdn.microsoft.com/slash/library/site.aspx\", UrlPathCombine(P1, \"//site.aspx\"));

        var path = UrlPathCombine(\"Http://MyUrl.com/\", \"Images/Image.jpg\");

        Assert.AreEqual(
            \"Http://MyUrl.com/Images/Image.jpg\",
            path);
    }


回答28:

Both of these work:

  Uri final = new Uri(Regex.Replace(baseUrl + \"/\" + relativePath, \"(?<!http:)/{2,}\", \"/\"));

Or

  Uri final =new Uri(string.Format(\"{0}/{1}\", baseUrl.ToString().TrimEnd(\'/\'), relativePath.ToString().TrimStart(\'/\')));

I.e. if

baseUrl = \"http://tesrurl.test.com/Int18\"

and

relativePath = \"To_Folder\"

output = http://tesrurl.test.com/Int18/To_Folder

Some errors will appear for the code below:

 // If you use the below code, some issues will be there in the final URI
 Uri final = new Uri(baseUrl, relativePath);


回答29:

We use the following simple helper method to join an arbitrary number of URL parts together:

public static string JoinUrlParts(params string[] urlParts)
{
    return string.Join(\"/\", urlParts.Where(up => !string.IsNullOrEmpty(up)).ToList().Select(up => up.Trim(\'/\')).ToArray());
}

Note, that it doesn\'t support \'../../something/page.htm\'-style relative URLs!



回答30:

I found that the Uri constructor flips \'\\\' into \'/\'. So you can also use Path.Combine, with the Uri constructor.

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