Path.Combine for URLs?

前端 未结 30 2121
不思量自难忘°
不思量自难忘° 2020-11-22 14:28

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.         


        
相关标签:
30条回答
  • 2020-11-22 15:11

    Why not just use the following.

    System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")
    
    0 讨论(0)
  • 2020-11-22 15:12
    Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
    
    0 讨论(0)
  • 2020-11-22 15:12

    As found in other answers, either new Uri() or TryCreate() can do the tick. However, the base Uri has to end with / and the relative has to NOT begin with /; otherwise it will remove the trailing part of the base Url

    I think this is best done as an extension method, i.e.

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

    and to use it:

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

    In terms of performance, this consumes more resources than it needs, because of the Uri class which does a lot of parsing and validation; a very rough profiling (Debug) did a million operations in about 2 seconds. This will work for most scenarios, however to be more efficient, it's better to manipulate everything as strings, this takes 125 milliseconds for 1 million operations. I.e.

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

    And if you still want to return a URI, it takes around 600 milliseconds for 1 million operations.

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

    I hope this helps.

    0 讨论(0)
  • 2020-11-22 15:13

    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 image description here

    0 讨论(0)
  • 2020-11-22 15:14

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

    string.Format("{0}/{1}", Url1.Trim('/'), Url2);
    
    0 讨论(0)
  • 2020-11-22 15:14

    If you don't want to have a dependency like Flurl, you can use its source code:

        /// <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;
            });
        }
    
    0 讨论(0)
提交回复
热议问题