URL.Combine的URL?

Bri*_*Kay 1186 .net c# asp.net url path

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

我正在寻找这样的语法:

Url.Combine("http://MyUrl.com/", "/Images/Image.jpg")
Run Code Online (Sandbox Code Playgroud)

将返回:

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

Joe*_*ham 1119

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

这是一个例子:

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

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

  • 我喜欢使用Uri类,不幸的是它不会像OP那样表现得像Path.Combine.例如新的Uri(新的Uri("http://test.com/mydirectory/"),"/ helloworld.aspx").ToString()为您提供"http://test.com/helloworld.aspx"; 如果我们想要一个Path.Combine样式结果,这将是不正确的. (348认同)
  • 这都是斜线.如果相对路径部分以斜杠开头,那么它的行为与您所描述的相同.但是,如果你留下斜线,那么它会按你期望的方式工作(注意第二个参数上缺少的斜线):new Uri(new Uri("http://test.com/mydirectory/")," helloworld.aspx").ToString()导致"http://test.com/mydirectory/helloworld.aspx".Path.Combine表现相似.如果相对路径参数以斜杠开头,则它仅返回相对路径并且不合并它们. (183认同)
  • 如果您的baseUri恰好是"test.com/mydirectory/mysubdirectory",那么结果将是"test.com/mydirectory/helloworld.aspx"而不是"test.com/mydirectory/mysubdirectory/helloworld.aspx".细微差别是第一个参数缺少尾部斜杠.我全都使用现有的框架方法,如果我必须在那里使用尾部斜杠那么我认为做partUrl1 + partUrl2的气味要少得多 - 我可能已经在追逐那个尾随斜线了很长一段时间为了不做字符串连接. (65认同)
  • 我想要一个URI组合方法的唯一原因是我不必检查尾部斜杠.如果您的应用程序位于根目录,则Request.ApplicationPath为"/",如果不是,则为"/ foo". (61认同)
  • 我-1这个答案,因为这不能解决问题.当你想要组合url时,就像你想使用Path.Combine一样,你不想关心尾随/.有了这个,你必须要关心.我更喜欢上面的Brian MacKay或mdsharpe解决方案 (20认同)
  • 作为尽可能多地使用已经构建的代码的粉丝,我想知道为什么没有人建议这个,直到我发现你的答案.这是IMO的最佳答案. (7认同)
  • 当第一个URI不是域的根时,此答案不会传递货物.耻辱. (4认同)
  • -1`new Uri(new Uri("test.com/mydirectory"),"helloworld.aspx")`返回`test.com/helloworld.aspx`,这不是任何人想要的. (4认同)
  • 如果您在虚拟路径中而不是在网站的根目录中部署应用程序,则此代码可能会出错.如果您的基本网址是my.website.com/virtualdir,相对pathe将是catalog/page.html,这将返回my.website.com/catalog/page.html,错过了vitrualdir (3认同)
  • 我曾经是这种方法的忠实粉丝,并且无处不在地使用它,当我发现"Uri"类的URL解码查询字符串参数的讨厌习惯时,它让我很难受.更糟糕的是,这是设计上的.请参见http://stackoverflow.com/a/7307950.尽管我讨厌它,但我现在手动完成所有URL连接.至少它不会搞砸我的返回者. (3认同)
  • URL 是可以转换为文件系统路径的抽象,但不应该这样想。如果不做一些主要假设,这将永远无法像 Path.Combine 那样运行。如果你有一个 URL:blah.com/thing1/thing2 谁能说 thing2 是一个目录还是一个文件?如果您尝试将其与“../thing3”的相对路径结合起来,它应该解析为 blah.com/thing1/thing3 还是 blah.com/thing3?现在 thing3 是目录还是文件?如果不显式地手动添加尾部斜杠或其他一些自定义定义的解析规则,它将无法工作。大多数网络服务器不会公开该信息。 (3认同)
  • 斜线与否,这似乎不适用于诸如 `var baseuri = new Uri("http://dotnettfs:8080/tfs/softwarecollection");` `var myuri = new Uri(baseuri, "_versionControl/changeset /244603");``//myuri = http://dotnettfs:8080/tfs/_versionControl/changeset/244603` (2认同)
  • @DoctorJones 这是正确的行为。Path.Combine 会将任何以 \ 开头的参数作为新根,覆盖任何先前指定的根。所以 Uri 构造函数实际上做了正确的事情。Path.Combine 的这个“功能”过去已经让我困扰过好几次了。 (2认同)
  • 从根本上来说,如果基本路径是相对路径并且没有使其成为绝对 URI 所需的所有信息,则此方法不起作用。因此,将 `/a/b/c/` 与 `d/e/f` 结合起来会抛出 `ArgumentOutOfRangeException`。根本无法用于该目的。 (2认同)

Mat*_*rpe 145

这可能是一个非常简单的解决方案:

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

  • +1:虽然这不处理相对风格的路径(../../whatever.html),但我喜欢这个简单的路径.我还会为'\'字符添加修剪. (7认同)
  • 请参阅我的回答,了解更完整的版本. (3认同)
  • @姆拉登B。好吧,我是OP。:) 虽然我没有明确要求,但支持相对样式路径的需求是总体问题域的固有部分......如果人们尝试重新使用它,则不这样做可能会导致令人困惑的结果。 (2认同)

Rya*_*ook 143

你用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);
}
Run Code Online (Sandbox Code Playgroud)

将返回:

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

  • +1:这很好,虽然我对输出参数有一个不合理的问题.;) (50认同)
  • 这个答案遇到了与[Joel](http://stackoverflow.com/a/1527643/56145)相同的问题:加入`test.com/mydirectory /`和`/ helloworld.aspx`将导致`test.com/ helloworld.aspx`这看似不是你想要的. (37认同)
  • @Brian:如果有帮助,所有TryXXX方法(`int.TryParse`,`DateTime.TryParseExact`)都有这个输出参数,以便在if语句中更容易使用它们.顺便说一下,你不必像Ryan在这个例子中那样初始化变量. (10认同)
  • 嗨,这次失败了:if(Uri.TryCreate(new Uri("http:// localhost/MyService /"),"/ Event/SomeMethod?abc = 123",out result)){Console.WriteLine(result) ; 它显示我的结果为:http:// localhost/Event/SomeMethod?abc = 123注意:"http://"在这里由stackoverflow从基础Uri替换 (3认同)
  • @FaisalMq这是正确的行为,因为您传递了根相对第二个参数.如果你遗漏了第二个参数的前导/上限,你就得到了预期的结果. (3认同)

Ale*_*ina 122

这里已经有了一些很棒的答案.基于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('/'))));
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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

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

  • 这个解决方案使得编写与Path.Combine()非常相似的UriUtils.Combine("base url","part1","part2",...)静态方法变得微不足道.太好了! (2认同)

Bri*_*Kay 87

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);
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

此代码通过以下测试,恰好在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
Run Code Online (Sandbox Code Playgroud)

  • 谈论细节:如果参数是'Nothing`,强制的`ArgumentNullException("url1")`怎么办?对不起,只是挑剔;-).请注意,反斜杠在URI中无关(如果它在那里,则不应该进行修剪),因此您可以从TrimXXX中删除它. (4认同)
  • 你可以使用params string []并递归地连接它们以允许超过2种组合 (4认同)
  • 我当然希望这是在基类库中像Path.Combine. (4认同)

MiF*_*vil 39

上面有一个Todd Menier的评论,Flurl包含了一个Url.Combine.

更多细节:

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

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" 
Run Code Online (Sandbox Code Playgroud)

在NuGet上获取Flurl.Http:

PM> Install-Package Flurl.Http

或者获取没有HTTP功能的独立URL构建器:

PM> Install-Package Flurl

  • 嗯,这个问题获得了大量的流量,1000+ upvotes的答案实际上并不适用于所有情况.多年以后,我实际上使用Flurl,所以我接受这个.它似乎适用于我遇到的所有情况.如果人们不想依赖,我发布了一个也很好的答案. (4认同)
  • 如果您不使用“Flurl”并且更喜欢轻量级版本,https://github.com/jean-lourenco/UrlCombine (3认同)

小智 34

根据您提供的示例网址,我将假设您要组合相对于您网站的网址.

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

由于URL框架中存在类似的功能,我建议正确的是:"VirtualPathUtility.Combine"方法.这是MSDN参考链接:VirtualPathUtility.Combine方法

有一点需要注意:我认为这仅适用于相对于您网站的网址(也就是说,您无法使用它来生成指向其他网站的链接.例如,var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).

  • 警告是正确的,它不能用绝对的uris工作,结果总是相对于根.但它有一个额外的好处,它处理波浪号,就像"〜/".这使它成为`Server.MapPath`和组合的快捷方式. (2认同)

Mik*_*chs 30

Path.Combine对我不起作用,因为可能有像"|"这样的字符 在QueryString参数中,因此是URL,这将导致ArgumentException.

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

new Uri(new Uri("http://www.mediawiki.org/wiki/"), "Special:SpecialPages")
Run Code Online (Sandbox Code Playgroud)

将导致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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1:现在我们正在谈论......我要试试这个。这甚至可能最终成为新的公认答案。在尝试新的 Uri() 方法后,我真的不喜欢它。太挑剔了。 (2认同)

Jer*_*eir 22

Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
Run Code Online (Sandbox Code Playgroud)

  • `path.Replace(Path.DirectorySeparatorChar,'/');` (9认同)
  • @SliverNinja这不正确*这个字段的值是UNIX上的反斜杠('\'),在Windows和Macintosh操作系统上是斜杠('/').*在Linux系统上使用Mono时,你会得到错误的分隔符. (7认同)
  • `path.Replace(Path.DirectorySeparatorChar,Path.AltDirectorySeparatorChar)` (4认同)
  • 在目录分隔符上调用的所有yall都忘记了字符串可能来自与现在不同的操作系统.只需用正斜杠替换反斜杠就可以了. (4认同)

小智 17

我只是把一个小的扩展方法放在一起:

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('/');
        }
Run Code Online (Sandbox Code Playgroud)

它可以像这样使用:

"www.example.com/".UriCombine("/images").UriCombine("first.jpeg");
Run Code Online (Sandbox Code Playgroud)


mta*_*zva 12

一个很好的例子,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");
}
Run Code Online (Sandbox Code Playgroud)


小智 9

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

string.Format("{0}/{1}", Url1.Trim('/'), Url2);
Run Code Online (Sandbox Code Playgroud)


Bel*_*014 9

组合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;
    }
Run Code Online (Sandbox Code Playgroud)

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

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

在此输入图像描述

  • 对我来说看起来不错。尽管您可以用foreach循环替换I循环以提高清晰度。 (2认同)

Mah*_*afy 9

正如在其他答案中发现的那样,无论是新的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);
}
Run Code Online (Sandbox Code Playgroud)

并使用它:

var baseUri = new Uri("http://test.com/test/");
var combinedUri =  baseUri.Append("/Do/Something");
Run Code Online (Sandbox Code Playgroud)

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

如果您仍想返回 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);
}
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助。


Gol*_*Age 9

我认为这应该给你更大的灵活性,因为你可以根据需要处理尽可能多的路径段:

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


jav*_*ava 7

我发现UriBuilder这种事情非常好用:

UriBuilder urlb = new UriBuilder("http", _serverAddress, _webPort, _filePath);
Uri url = urlb.Uri;
return url.AbsoluteUri;
Run Code Online (Sandbox Code Playgroud)

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


Tob*_*ger 7

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

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

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

注意:此解决方案会小写主机并附加端口。如果不需要这样做,可以通过利用 的属性来编写字符串表示Uri形式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));
         }
  }
Run Code Online (Sandbox Code Playgroud)

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

为什么这有效?

尽管Path.Combine会返回反斜杠(至少在 Windows 上),但 UriBuilder 在Path.

取自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;
      }
 }
Run Code Online (Sandbox Code Playgroud)

这是最好的方法吗?

当然,这个解决方案是非常自我描述的(至少在我看来)。但是您依赖于 .NET API 中未记录的“功能”(至少我通过快速谷歌搜索没有找到任何内容)。这可能会随着未来的版本而改变,因此请通过测试覆盖该方法。

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

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


Dub*_*Dub 6

对于任何正在寻找单行代码并且只想连接部分路径而不创建新方法或引用新库或构建 URI 值并将其转换为字符串的人,那么...

string urlToImage = String.Join("/", "websiteUrl", "folder1", "folder2", "folder3", "item");
Run Code Online (Sandbox Code Playgroud)

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

string urlToImage = String.Join("/","websiteUrl", String.Join("/", "folder1", "folder2", "folder3", "item").Replace("//","/"));
Run Code Online (Sandbox Code Playgroud)

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

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


小智 6

如果你不想有像 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 /sf/answers/3334522621/

        // 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;
        });
    }
Run Code Online (Sandbox Code Playgroud)


AAA*_*ddd 5

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

  • 抛出空或空白
  • 需要多个 params为多个 Url 段参数
  • 抛出 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);
}
Run Code Online (Sandbox Code Playgroud)

测试

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
Run Code Online (Sandbox Code Playgroud)


Dav*_*ack 5

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

笔记:

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

希望你觉得这个有用!

/// <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;
}
Run Code Online (Sandbox Code Playgroud)

与基准 DotNet 输出:

|                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 |
Run Code Online (Sandbox Code Playgroud)