ChatGPT解决这个技术问题 Extra ChatGPT

用于 URL 的 Path.Combine?

Path.Combine 很方便,但 .NET 框架中是否有类似的功能用于 URLs

我正在寻找这样的语法:

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

这将返回:

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

Flurl 包括一个 Url.Combine 方法,它就是这样做的。
实际上, // 是由网站或服务器的路由处理的,而不是由浏览器处理的。它将发送您放入地址栏中的内容。这就是为什么当我们输入 htp:// 而不是 http:// 时会出现问题所以 // 在某些站点上可能会导致严重问题。我正在为一个处理特定网站的爬虫编写一个 .dll,如果 URL 中有 // 则抛出 404。

K
Karel Kral

Uri 有一个应该为您执行此操作的构造函数:new Uri(Uri baseUri, string relativeUri)

这是一个例子:

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

编者注:请注意,此方法无法按预期工作。在某些情况下,它可以削减 baseUri 的一部分。查看评论和其他答案。


我喜欢使用 Uri 类,不幸的是它不会像 OP 所要求的那样表现得像 Path.Combine 。例如 new Uri(new Uri("test.com/mydirectory/"), "/helloworld.aspx").ToString() 给你 "test.com/helloworld.aspx"; 如果我们想要一个 Path.Combine 样式的结果,这是不正确的。
这一切都在斜线中。如果相对路径部分以斜杠开头,那么它的行为与您描述的一样。但是,如果你把斜线去掉,那么它会按照你期望的方式工作(注意第二个参数上缺少的斜线): new Uri(new Uri("test.com/mydirectory/"), "helloworld.aspx").ToString( ) 结果为“test.com/mydirectory/helloworld.aspx”。Path.Combine 的行为类似。如果相对路径参数以斜杠开头,则它只返回相对路径而不将它们组合起来。
如果您的 baseUri 恰好是“test.com/mydirectory/mysubdirectory”,那么结果将是“test.com/mydirectory/helloworld.aspx”而不是“test.com/mydirectory/mysubdirectory/helloworld.aspx”。细微的区别是第一个参数上没有斜杠。我完全赞成使用现有的框架方法,如果我必须在那里已经有了斜杠,那么我认为做 partUrl1 + partUrl2 的气味要少得多 - 我可能已经追逐那个斜杠很长一段时间了为了不做字符串连接。
我想要一个 URI 组合方法的唯一原因是我不必检查尾部斜杠。如果您的应用程序位于根目录,则 Request.ApplicationPath 为“/”,如果不是,则为“/foo”。
我 -1 这个答案,因为这不能解决问题。当你想组合 url 时,比如当你想使用 Path.Combine 时,你不想关心尾随的 /。有了这个,你必须关心。我更喜欢上面的 Brian MacKay 或 mdsharpe 的解决方案
M
Matthew Sharpe

这可能是一个适当简单的解决方案:

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

+1:虽然这不处理相对样式的路径(../../whatever.html),但我喜欢它的简单性。我还会为“\”字符添加修剪。
请参阅我的答案以获得更完整的版本。
@BrianMacKay,OP 从不要求相对风格的路径......
@MladenB。好吧,我是OP。 :) 虽然我没有明确要求,但支持相对样式路径的需求是总体问题域的固有部分......如果人们试图重用它,如果不这样做可能会导致令人困惑的结果。
2022: 虽然是一个不错的解决方案,但可能不建议将它用于 URL,就像 string 用于文件和文件夹路径一样(您将使用 Path.xxx() 代替)
s
slugster

这里已经有一些很好的答案。根据 mdsharpe 的建议,当您想要处理 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('/'))));
    }
}

以及用法示例:

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

这将产生 http://example.com/subpath/part1/part2

如果您想使用字符串而不是 Uris,那么以下内容也将产生相同的结果,只需对其进行调整以适应您的需要:

public string JoinUriSegments(string uri, params string[] segments)
{
    if (string.IsNullOrWhiteSpace(uri))
        return null;

    if (segments == null || segments.Length == 0)
        return uri;

    return segments.Aggregate(uri, (current, segment) => $"{current.TrimEnd('/')}/{segment.TrimStart('/')}");
}

var uri = JoinUriSegements("http://example.com/subpath/", "/part1/", "part2");

此解决方案使编写与 Path.Combine() 非常相似的 UriUtils.Combine("base url", "part1", "part2", ...) 静态方法变得简单。好的!
为了支持相对 URI,我必须在 Uri 构造函数中使用 ToString() 而不是 AbsoluteUri 和 UriKind.AbsoluteOrRelative。
感谢您提供有关相对 Uris 的提示。不幸的是,Uri 并不容易处理相对路径,因为涉及到 Request.ApplicationPath 总是有一些麻烦。也许您也可以尝试使用 new Uri(HttpContext.Current.Request.ApplicationPath) 作为基础并在其上调用 Append?这将为您提供绝对路径,但应该可以在站点结构中的任何位置使用。
我还添加了检查是否有任何要附加的路径既不是空字符串也不是空字符串。
当我查看所有答案时,我就像......“为什么还没有人发布扩展方法,我要发布一个”......没关系。 +1
R
Ryan Cook

您使用 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);
}

将返回:

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


+1:这很好,虽然我对输出参数有一个不合理的问题。 ;)
@Brian:如果有帮助,所有 TryXXX 方法(int.TryParseDateTime.TryParseExact)都具有此输出参数,以便更容易在 if 语句中使用它们。顺便说一句,您不必像 Ryan 在此示例中所做的那样初始化变量。
此答案与 Joel's 存在相同的问题:加入 test.com/mydirectory//helloworld.aspx 将导致 test.com/helloworld.aspx 这似乎不是您想要的。
嗨,这失败了: if (Uri.TryCreate(new Uri("localhost/MyService/"), "/Event/SomeMethod?abc=123", out result)) { Console.WriteLine(result); } 它显示给我结果为:localhost/Event/SomeMethod?abc=123 注意:此处的“http://”由 stackoverflow 替换为基本 Uri
@FaisalMq 这是正确的行为,因为您传递了与根相关的第二个参数。如果您在第二个参数上省略了前导 /,您将获得预期的结果。
m
mybrave

is a Todd Menier's comment above 中的 Flurl 包含一个 Url.Combine

更多细节:

Url.Combine 基本上是 URL 的 Path.Combine,确保部分之间只有一个分隔符:

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" 

获取 Flurl.Http on NuGet

PM> Install-Package Flurl.Http

或没有 HTTP 功能的 get the stand-alone URL builder

PM> Install-Package Flurl


好吧,这个问题获得了很多流量,而 1000+ 票的答案实际上并不适用于所有情况。多年后,我实际上为此使用了 Flurl,所以我接受了这个。它似乎适用于我遇到的所有情况。如果人们不想依赖,我发布了一个也可以正常工作的答案。
如果您不使用 Flurl 并且希望使用轻量级版本,github.com/jean-lourenco/UrlCombine
P
Peter Mortensen

Ryan Cook 的回答接近我所追求的,可能更适合其他开发人员。但是,它将 http:// 添加到字符串的开头,并且通常它的格式比我所追求的要多一些。

此外,对于我的用例,解析相对路径并不重要。

mdsharp 的答案还包含一个好主意的种子,尽管实际实现需要更多细节才能完成。这是一种充实它的尝试(我在生产中使用它):

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

此代码通过了以下测试,恰好在 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

谈到细节:如果参数是 Nothing,那么强制的 ArgumentNullException("url1") 呢?对不起,只是挑剔;-)。请注意,反斜杠与 URI 无关(如果存在,则不应修剪),因此您可以将其从 TrimXXX 中删除。
您可以使用参数 string[] 并递归地加入它们以允许超过 2 种组合
我当然希望这是在 Path.Combine 这样的基类库中。
@MarkHurd 我再次编辑了代码,使其在行为上与 C# 相同,并且在语法上也等效。
@BrianMacKay 我打破了它,markhurd 指出了我的错误并回滚,我再次更新了......干杯
P
PRMan

Path.Combine 对我不起作用,因为可以有像“|”这样的字符在 QueryString 参数和 URL 中,这将导致 ArgumentException。

我首先尝试了新的 Uri(Uri baseUri, string relativeUri) 方法,但由于像 http://www.mediawiki.org/wiki/Special:SpecialPages 这样的 URI 而失败了:

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

将导致 Special:SpecialPages,因为 Special 之后的冒号表示方案。

所以我最终不得不采用 mdsharpe/Brian MacKays 路线并进一步开发它以处理多个 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;
}

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


+1:现在我们正在谈论......我要试试这个。这甚至可能最终成为新接受的答案。在尝试新的 Uri() 方法后,我真的不喜欢它。太花哨了。
这正是我所需要的!不喜欢我在哪里放斜杠等...
+1 用于进行空值检查,因此它不会爆炸。
Count() 应该是 Length,这样您就不需要为此在库中包含 Linq。
这正是我想要的。
K
Kolappan N

根据您提供的示例 URL,我假设您想要合并与您的网站相关的 URL。

基于这个假设,我将提出这个解决方案作为对您的问题的最合适的回答:“Path.Combine 很方便,URL 框架中是否有类似的功能?”

由于在 URL 的框架中有一个 类似的功能,我建议正确的是:“VirtualPathUtility.Combine”方法。这是 MSDN 参考链接:VirtualPathUtility.Combine Method

有一个警告:我相信这仅适用于与您的网站相关的 URL(也就是说,您不能使用它来生成指向另一个网站的链接。例如,var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");)。


+1,因为它接近我正在寻找的内容,尽管如果它适用于任何旧网址,那将是理想的。我加倍它会比 mdsharpe 提出的要优雅得多。
警告是正确的,它不能与绝对 uri 一起使用,并且结果始终是相对于根的。但它还有一个额外的好处,它处理波浪号,就像“~/”一样。这使它成为 Server.MapPath 和组合的快捷方式。
J
JeremyWeir
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")

path.Replace(Path.DirectorySeparatorChar, '/');
path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
要让它工作,你必须在第二个参数中删除第一个 / 即 "/Images" - / Path.Combine("Http://MyUrl.com", "Images/Image.jpg")
@SliverNinja 不正确此字段的值在 UNIX 上是反斜杠('\'),在 Windows 和 Macintosh 操作系统上是斜杠('/')。在 Linux 系统上使用 Mono 时,您会得到错误的分隔符。
所有在目录分隔符上搞怪的人都忘记了字符串可能来自与您现在不同的操作系统。只需用正斜杠替换反斜杠即可。
P
Peter Mortensen

我只是整理了一个小的扩展方法:

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

它可以这样使用:

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

P
Peter Mortensen

诙谐的例子,Ryan,以函数的链接结束。做得好。

一个建议 Brian:如果您将此代码包装在一个函数中,您可能希望在 TryCreate 调用之前使用 UriBuilder 来包装基本 URL。

否则,基本 URL 必须包含方案(UriBuilder 将假定 http://)。只是一个想法:

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

P
Peter Mortensen

将它们组合并确保其始终正确的一种简单方法是:

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

+1,尽管这与 mdsharpe 的回答非常相似,但我在回答中对此进行了改进。这个版本很好用,除非 Url2 以 / 或 \ 开头,或者 Url1 不小心以 \ 结尾,或者其中一个是空的! :)
P
Peter Mortensen

组合 URL 的多个部分可能有点棘手。您可以使用双参数构造函数 Uri(baseUri, relativeUri),也可以使用 Uri.TryCreate() 实用程序函数。

在任何一种情况下,您最终都可能返回不正确的结果,因为这些方法不断地从第一个参数 baseUri 中截断相关部分,即从 http://google.com/some/thinghttp://google.com

为了能够将多个部分组合成最终 URL,您可以复制以下两个函数:

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

可在 https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs 找到带有单元测试以演示用法的完整代码

我有单元测试来涵盖三种最常见的情况:

https://i.stack.imgur.com/gidEj.png


+1 所有额外的努力。对于一些投票率较高的答案,我需要稍微保留这个问题,你已经放弃了挑战。 ;)
M
Mahmoud Hanafy

正如在其他答案中发现的那样,新的 Uri()TryCreate() 都可以打勾。但是,基本 Uri 必须以 / 结尾,而相对不能以 / 开头;否则它将删除基本 URL 的尾随部分

我认为这是最好的扩展方法,即

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

并使用它:

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

在性能方面,这消耗的资源比它需要的要多,因为 Uri 类做了很多解析和验证;一个非常粗略的分析(调试)在大约 2 秒内完成了一百万次操作。这适用于大多数场景,但是为了更高效,最好将所有内容都作为字符串进行操作,100 万次操作需要 125 毫秒。 IE

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

如果你仍然想返回一个 URI,100 万次操作大约需要 600 毫秒。

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

我希望这有帮助。


G
GoldenAge

我认为这应该为您提供更大的灵活性,因为您可以处理任意数量的路径段:

public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));

P
Peter Mortensen

我发现 UriBuilder 非常适合这种事情:

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

有关更多构造函数和文档,请参阅 UriBuilder Class - MSDN


e
er sd

如果你不想像 Flurl 这样的依赖,你可以使用它的源代码:

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

T
TheGeneral

我发现以下有用并具有以下功能:

抛出空或空白

为多个 Url 段采用多个 params 参数

抛出 null 或空

班级

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

测试

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

测试的一些问题: // Result = test1/test2/test3\ 对于第 4 个和最后一个 throws 测试给出 ArgumentNullException 而不是 ArgumentException
A
Alex Titarenko

我的通用解决方案:

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

这种辅助方法非常灵活,适用于许多不同的用例。谢谢!
C
Chris Marisic

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

来源:GitHub


看起来这可能适用于路径,而不是 URL。
@BrianMacKay 同意它看起来像,但它来自 UrlUtility 类并在组合 URL 的上下文中使用
编辑澄清它属于哪个类
使用此类时要小心,该类的其余部分包含 SharePoint 特定的工件。
T
Tobias Schwarzinger

所以我有另一种方法,类似于所有使用 UriBuilder 的人。

我不想像 javajavajavajavajava 那样拆分我的 BaseUrl(它可以包含路径的一部分 - 例如 http://mybaseurl.com/dev/)。

以下代码段显示了代码 + 测试。

注意:此解决方案将主机小写并附加一个端口。如果不希望这样做,可以通过例如利用 UriBuilderUri 属性来编写字符串表示。

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

在 Windows 10 上使用 .NET Core 2.1 进行测试。

为什么这行得通?

即使 Path.Combine 将返回反斜杠(至少在 Windows 上),UriBuilder 在 Path 的 Setter 中处理这种情况。

取自 https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs(请注意对 string.Replace 的调用)

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

这是最好的方法吗?

当然,这个解决方案是非常自我描述的(至少在我看来)。但是您依赖于 .NET API 中未记录的(至少我通过快速的 google 搜索一无所获)“功能”。这可能会随着未来的版本而改变,所以请用测试覆盖方法。

https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs (Path_Get_Set) 中有测试检查 \ 是否正确转换。

旁注:如果 uri 将用于 System.Uri ctor,也可以直接使用 UriBuilder.Uri 属性。


这是一种非常可靠的方法。为单元测试点赞!!
D
Dave Black

我有一个无分配的字符串创建版本,我一直在使用它并取得了巨大的成功。

笔记:

对于第一个字符串:它使用 TrimEnd(separator) 修剪分隔符 - 所以只能从字符串的末尾开始。对于其余部分:它使用 Trim(separator) 修剪分隔符 - 因此路径的开头和结尾都不会附加尾部斜杠/分隔符。尽管可以进行简单的修改来添加此功能。

希望你觉得这个有用!

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

使用基准点网输出:

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

P
Peter Mortensen

我创建了这个功能,让您的生活更轻松:

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

它适用于 URL 以及普通路径。

用法:

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

P
Peter Mortensen

我发现 Uri 构造函数将“\”翻转为“/”。因此,您也可以将 Path.CombineUri 构造函数一起使用。

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

A
Andreas

为什么不只使用以下内容。

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

我正在寻找它的 PowerShell 版本,它是:[System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")但是这会失败,结果是:/Images/Image.jpg。从第二个子路径中删除 / 并且它可以工作:[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
好主意,但是当参数之一为空时,它会失败。
L
LawMan

对于它的价值,这里有几个扩展方法。第一个将组合路径,第二个将参数添加到 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();
    }

N
Neo

如果您不想在 ASP.NET Core(也可在 Microsoft.Owin 中使用)中添加第三方依赖项(例如 Flurl)或创建自定义扩展方法,则可以使用 PathString,其目的是建立 URI 路径。然后,您可以使用 UriUriBuilder 的组合创建完整的 URI。

在这种情况下,它将是:

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

这为您提供了所有组成部分,而无需在基本 URL 中指定分隔符。不幸的是,PathString 要求将 / 附加到每个字符串之前,否则它实际上会抛出 ArgumentException!但至少您可以以一种易于单元测试的方式确定性地构建您的 URI。


D
DubDub

对于任何正在寻找单线并且只想加入部分路径而不创建新方法或引用新库或构造 URI 值并将其转换为字符串的人,然后...

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

这是非常基本的,但我看不出你还需要什么。如果您害怕加倍的“/”,那么您可以在之后简单地执行 .Replace("//", "/")。如果您害怕替换“https://”中的双倍“//”,那么请改为一次加入,替换双倍的“/”,然后加入网站网址(但我很确定大多数浏览器会自动将前面带有“https:”的任何内容转换为以正确格式读取)。这看起来像:

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

这里有很多答案可以处理上述所有问题,但就我而言,我只需要在一个位置使用它一次,并且不需要严重依赖它。此外,很容易看到这里发生了什么。

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


N
Nick N.

一个简单的衬里:

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

受@Matt Sharpe 回答的启发。


P
Peter Mortensen

将 URL 与 URI 组合时的规则

为了避免奇怪的行为,需要遵循一条规则:

路径(目录)必须以“/”结尾。如果路径没有“/”结尾,则最后一部分将被视为文件名,并且在尝试与下一个 URL 部分组合时将被连接起来。

有一个例外:基本 URL 地址(没有目录信息)不需要以 '/' 结尾

路径部分不能以“/”开头。如果它以“/”开头,则从 URL 中删除所有现有的相关信息……添加一个字符串。空部分路径也会从 URL 中删除相关目录!

如果您遵循上述规则,您可以将 URL 与下面的代码结合起来。根据您的情况,您可以将多个“目录”部分添加到 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();
        });