1338

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"

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Brian MacKay
  • 28,374
  • 14
  • 82
  • 116
  • 14
    [Flurl](https://github.com/tmenier/Flurl) includes a `Url.Combine` method that does just that. – Todd Menier Feb 21 '14 at 06:18
  • 2
    Actually, the // is handled by the routing of the website or server and not by the browser. It will send what you put into the address bar. That's why we get problems when we type htp:// instead of http:// So the // can cause major problems on some sites. I am writing a .dll for a crawler which handles a particular website which throws a 404 if you have // in the url. – Dave Gordon Jul 07 '14 at 08:11

41 Answers41

1222

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.

Karel Kral
  • 4,605
  • 4
  • 37
  • 41
Joel Beckham
  • 16,924
  • 3
  • 31
  • 58
  • 7
    As a fan of using as much already built code as you can, I was wondering why no one had suggested this yet until I spotted your answer. This is, IMO, the best answer. – Chris Mar 28 '10 at 00:30
  • 389
    I like the use of the Uri class, unfortunately it will not behave like Path.Combine as the OP asked. For example new Uri(new Uri("http://test.com/mydirectory/"), "/helloworld.aspx").ToString() gives you "http://test.com/helloworld.aspx"; which would be incorrect if we wanted a Path.Combine style result. – Doctor Jones Oct 28 '10 at 15:20
  • 210
    It's all in the slashes. If the relative path part starts with a slash, then it behaves as you described. But, if you leave the slash out, then it works the way you'd expect (note the missing slash on the second parameter): new Uri(new Uri("http://test.com/mydirectory/"), "helloworld.aspx").ToString() results in "http://test.com/mydirectory/helloworld.aspx". Path.Combine behaves similarly. If the relative path parameter starts with a slash, it only returns the relative path and doesn't combine them. – Joel Beckham Oct 28 '10 at 22:11
  • 74
    If your baseUri happened to be "test.com/mydirectory/mysubdirectory" then the result would be "test.com/mydirectory/helloworld.aspx" instead of "test.com/mydirectory/mysubdirectory/helloworld.aspx". The subtle difference is the lack of trailing slash on the first parameter. I'm all for using existing framework methods, if I have to have the trailing slash there already then I think that doing partUrl1 + partUrl2 smells a lot less - I could've potentially been chasing that trailing slash round for quite a while all for the sake of not doing string concat. – Carl Jan 12 '11 at 16:10
  • 70
    The only reason I want a URI combine method is so that I don't have to check for the trailing slash. Request.ApplicationPath is '/' if your application is at the root, but '/foo' if it's not. – nickd Mar 25 '11 at 16:44
  • 25
    I -1 this answer because this doesn't answer the problem. When you want to combine url, like when you want to use Path.Combine, you don't want to care about the trailing /. and with this, you have to care. I prefer solution of Brian MacKay or mdsharpe above – Baptiste Pernet Apr 21 '11 at 15:56
  • @Baptiste Pernet: Good point - in general you are correct. In the OP's specific case, where the baseUri doesn't have any additional path elements, the trailing slash does not have any effect on the result. If there are additional path elements in the baseUri, e.g. "Http://MyUrl.com/some/folder/", then you're right, you do have to pay attention to keep the trailing slash there. – Joel Beckham May 10 '11 at 20:05
  • 3
    This code works wrong if you deploy application in a virtual path, not in the root of web site. If your base URL will be my.website.com/virtualdir and relative pathe will be catalog/page.html, this will return my.website.com/catalog/page.html, vitrualdir is missed – Bogdan_Ch Jul 17 '13 at 12:24
  • 3
    I used to be a big fan of this approach, and was using it everywhere, then it bit me hard when I found out about `Uri` class's nasty habit of URL decoding querystring parameters. Even worse, it is by design. See http://stackoverflow.com/a/7307950. As much as I hate it, I now do all URL concatenation manually. At least it won't mess up my returlUrl's. – Daniel Liuzzi Mar 24 '14 at 20:51
  • 4
    This answer doesn't deliver the goods when the first URI isn't the root of the domain. Shame. – Martin Capodici May 14 '14 at 05:28
  • Awesome! it even work in that `Uri baseUri = new Uri("http://www.foo.com/lol"); Uri myUri = new Uri(baseUri, "../test");` and `myUri.ToString()` give `http://www.foo.com/test` – Jack Dec 18 '14 at 19:43
  • 2
    Slashes or not, this does not seem to work for urls such as `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` – Joshua C Aug 20 '15 at 23:23
  • 4
    -1 `new Uri(new Uri("test.com/mydirectory"), "helloworld.aspx")` returns `test.com/helloworld.aspx`, which is not what anyone anywhere wants. – BlueRaja - Danny Pflughoeft Oct 10 '15 at 21:57
  • 1
    @DoctorJones This is the correct behavior. Path.Combine will take any parameter with leading \ as the new root, overriding any previously specified root. So the Uri constructor actually does the right thing. This 'feature' of Path.Combine has bitten me quite a few times in the past. – Tom Lint Nov 11 '15 at 10:18
  • 1
    @TomLint I understand that it works that way by design. I was stating that it behaves differently to Path.Combine, because OP asked for a Path.Combine equivalent for URLs. The Uri constructor is not an equivalent of Path.Combine. – Doctor Jones Nov 11 '15 at 11:33
  • @DoctorJones I don't think you completely understood what I said; Path.Combine has exactly the same behavior with regard to parameters prefixed by a path separator character. Therefore the combining of the Uris this way _is_ correct, and exactly what the OP asked for. – Tom Lint Nov 11 '15 at 13:13
  • 1
    This doesn't behave as the op asked – Reda Jan 20 '16 at 09:39
  • 1
    Most fundamentally, this doesn't work if base path is a relative one and doesn't have all the necessary info to make it an Absolute URI. So combining `/a/b/c/` with `d/e/f` with throw `ArgumentOutOfRangeException`. Not at all usable for the purpose. – LB2 May 23 '16 at 21:24
  • 3
    URLs are abstractions that MAY translate to a filesystem path, but shouldn't be thought of that way. This will never be able to function like Path.Combine without making some major assumptions. If you have a URL: blah.com/thing1/thing2 whose to say that thing2 is a dir or a file? If you try to combine that with a relative path of "../thing3" should it resolve to blah.com/thing1/thing3 or blah.com/thing3? Now is thing3 a dir or file? Without explicitly adding the trailing slashes MANUALLY- or some other custom defined parsing rule, it won't work. Most webservers won't expose that info. – Adam Plocher Apr 25 '17 at 05:37
  • This solution generates an warning: https://docs.microsoft.com/de-de/visualstudio/code-quality/ca2234-pass-system-uri-objects-instead-of-strings?view=vs-2015 – Simon Mar 14 '19 at 08:04
  • 1
    If we have such a url the result will be wrong Example : http://127.0.0.1:8080/drupal/ – mohammad almasi Dec 14 '19 at 13:51
  • I view this as a correct answer. A URI may not refer to a directory. It always refers to a resource, which is the last part given unless it ends in a /, in which case it refers to the default resource in that path. "http://localhost/directory/subdirectory" is therefore a false assumption because "subdirectory" is actually a resource (think file), not a collection of resources. This is just how URIs work, and combining that with a relative path should always strip the "subdirectory" part out. And that is exactly what the Uri constructor does. – Robert McKee Aug 14 '20 at 19:02
174

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);
}
Matthew Sharpe
  • 3,171
  • 2
  • 21
  • 24
  • 7
    +1: Although this doesn't handle relative-style paths (../../whatever.html), I like this one for its simplicity. I would also add trims for the '\' character. – Brian MacKay May 08 '10 at 15:55
  • 3
    See my answer for a more fully fleshed out version of this. – Brian MacKay May 12 '10 at 13:46
  • @BrianMacKay, OP never asked for relative-style paths... – Mladen B. Mar 22 '21 at 15:49
  • @MladenB. Well, I am the OP. :) Although I did not explicitly ask for it, the need to support relative-style paths is an inherent part of the overarching problem domain... Failing to do so can lead to confusing results if people try to re-use this. – Brian MacKay Mar 23 '21 at 14:19
  • lol, didn't realize that :) sorry :) – Mladen B. Mar 23 '21 at 23:56
150

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

Ryan Cook
  • 8,989
  • 5
  • 36
  • 34
  • 55
    +1: This is good, although I have an irrational problem with the output parameter. ;) – Brian MacKay Oct 29 '09 at 14:24
  • 1
    This is a much better approach, as it will also work for paths of the form "../../something.html" – wsanville Mar 28 '10 at 00:32
  • 12
    @Brian: if it helps, all TryXXX methods (`int.TryParse`, `DateTime.TryParseExact`) have this output param to make it easier to use them in an if-statement. Btw, you don't have to initialize the variable as Ryan did in this example. – Abel Aug 26 '10 at 22:11
  • 43
    This answer suffers the same problem as [Joel's](http://stackoverflow.com/a/1527643/56145): joining `test.com/mydirectory/` and `/helloworld.aspx` will result in `test.com/helloworld.aspx` which is seemingly not what you want. – Matt Kocaj Aug 27 '13 at 02:05
  • 3
    Hi, this failed for following : if (Uri.TryCreate(new Uri("http://localhost/MyService/"), "/Event/SomeMethod?abc=123", out result)) { Console.WriteLine(result); } It is showing me result as : http://localhost/Event/SomeMethod?abc=123 Note: "http://" is replaced from base Uri here by stackoverflow – Faisal Mq Nov 28 '13 at 08:45
  • 3
    @FaisalMq This is the correct behavior, since you passed a root-relative second parameter. If you had left out the leading / on the second parameter, you'd have gotten the result you expected. – Tom Lint Jan 24 '14 at 09:17
  • 1
    Same problem as in the [top](http://stackoverflow.com/a/1527643/56145) answer: `Uri.TryCreate(new Uri("http://test.com/mydirectory"), "helloworld.aspx", out result)` will set result to `http://test.com/helloworld.aspx` – Lu55 Sep 16 '16 at 17:12
  • 1
    @Lu55 if you to put a `/` after the directory it will work. – Florian Fida Jan 27 '17 at 01:51
  • For this to work, the base Uri has to have a trailing slash, and the relative Uri has to NOT have a leading slash. i.e. Base: "http://test.com/test/" Relative: "do/did/done" result: "http://test.com/test/do/did/done" This is also equivalent to u = new Uri(new Uri("http://test.com/test/"), "/do/did/done"); – Mahmoud Hanafy Nov 07 '19 at 21:48
137

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

jdphenix
  • 13,519
  • 3
  • 37
  • 68
Ales Potocnik Hahonina
  • 2,537
  • 1
  • 24
  • 32
  • 3
    This solution makes it trivial to write a UriUtils.Combine("base url", "part1", "part2", ...) static method that is very similar to Path.Combine(). Nice! – angularsen Nov 19 '11 at 15:23
  • To support relative URIs I had to use ToString() instead of AbsoluteUri and UriKind.AbsoluteOrRelative in the Uri constructor. – angularsen Nov 20 '11 at 19:34
  • Thanks for the tip about relative Uris. Unfortunately Uri doesn't make it easy to deal with relative paths as there is always some mucking about with Request.ApplicationPath involved. Perhaps you could also try using new Uri(HttpContext.Current.Request.ApplicationPath) as a base and just call Append on it? This will give you absolute paths but should work anywhere within site structure. – Ales Potocnik Hahonina Nov 21 '11 at 12:03
  • I also added check if any of paths to append are not null nor empty string. – n.podbielski Jan 15 '16 at 10:13
  • As I was looking at all the answers I was like... "Why has no one posted an extension method yet, I'm going to post one"... Never mind. +1 – Arvo Bowen Feb 05 '21 at 22:00
105

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

Get Flurl.Http on NuGet:

PM> Install-Package Flurl.Http

Or get the stand-alone URL builder without the HTTP features:

PM> Install-Package Flurl

Selim Yildiz
  • 4,224
  • 6
  • 14
  • 25
Michael Freidgeim
  • 21,559
  • 15
  • 127
  • 153
  • 5
    Well, this question gets a lot of traffic, and the answer with 1000+ upvotes does not actually work in all cases. Years later, I actually use Flurl for this, so I am accepting this one. It seems to work in all cases I have encountered. If people don't want to take a dependency, I posted an answer that also works fine. – Brian MacKay Nov 28 '18 at 15:33
  • and if you dont use `Flurl` and would perfer a lightweight version, https://github.com/jean-lourenco/UrlCombine – lizzy91 Feb 01 '19 at 19:59
98

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
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Brian MacKay
  • 28,374
  • 14
  • 82
  • 116
  • 4
    Talking of details: what about the mandatory `ArgumentNullException("url1")` if the argument is `Nothing`? Sorry, just being picky ;-). Note that a backslash has nothing to do in a URI (and if it is there, it should not be trimmed), so you can remove that from your TrimXXX. – Abel Aug 26 '10 at 22:21
  • 6
    you can use params string[] and recursively join them to allow more than 2 combinations – Jaider Jun 12 '12 at 22:47
  • It's a good solution but it doesn't address following cases: `Assert.IsTrue(target.UrlCombine("../test1", "/") = "../test1/") Assert.IsTrue(target.UrlCombine("/", "test1/") = "/test1/")` – Vlax Jan 31 '13 at 11:20
  • I see what you mean about the tests. Since the code doesn't actually work in terms of chopping things up into directories and then gluing them back together and instead just uses text, I didn't even think to add them. They would pass as they are basically covered... You can feel free to edit the code if you want. – Brian MacKay Jan 31 '13 at 17:33
  • Sorry I was wrong your code does indeed covers the mentioned case! – Vlax Feb 08 '13 at 12:35
  • @LouisRhys Because I was working on a VB.net project at the time, it happens. ;) But, I updated this with a C# translation. – Brian MacKay May 04 '14 at 11:28
  • 4
    I sure wish this was in the Base Class Library like Path.Combine. – Uriah Blatherwick Aug 18 '14 at 18:30
  • @MarkHurd, I agree. I didn't realize it was Char(), I thought it was a single Char. Makes sense why they did it this way. I'd prefer `New Char() { "/"c, "\"c }` – JJS Jul 07 '16 at 19:21
  • 1
    @MarkHurd I edited the code again, so that it's behaviorally the same as the C#, and syntactically equivalent as well. – JJS Jul 07 '16 at 19:23
  • @JJS Looks like someone broke this and you came back and fixed it -good job. :) – Brian MacKay Jul 11 '16 at 13:21
  • 1
    @BrianMacKay i broke it, markhurd pointed out my mistake and rolled back, i updated again... cheers – JJS Jul 11 '16 at 13:28
43

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

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

PRMan
  • 407
  • 7
  • 13
Mike Fuchs
  • 11,421
  • 5
  • 49
  • 66
34

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

Kolappan N
  • 2,178
  • 2
  • 26
  • 31
  • +1 because it's close to what I'm looking for, although it would be ideal if it would work for any old url. I double it will get much more elegant than what mdsharpe proposed. – Brian MacKay Mar 29 '10 at 21:28
  • 2
    The caveat is correct, it cannot work with absolute uris and the result is always relative from the root. But it has an added benefit, it processes the tilde, as with "~/". This makes it a shortcut for `Server.MapPath` and combining. – Abel Aug 26 '10 at 22:18
25
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
JeremyWeir
  • 23,396
  • 10
  • 89
  • 106
  • 12
    `path.Replace(Path.DirectorySeparatorChar, '/');` – Jaider Jun 12 '12 at 22:51
  • 5
    `path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)` – SliverNinja - MSFT Aug 16 '12 at 14:31
  • 1
    To get it to wrk u must remove first / in second arg ie "/Images" - / Path.Combine("Http://MyUrl.com/", "Images/Image.jpg") – Per G Apr 25 '13 at 10:43
  • 9
    @SliverNinja That's not correct *The value of this field is a backslash ('\') on UNIX, and a slash ('/') on Windows and Macintosh operating systems.* When using Mono on a Linux system, you'd get the wrong separator. – user247702 Mar 13 '14 at 10:01
  • Surprising that none of the other massive upvoters mentioned that the other solutions work for *two* directories/names only. – mike Apr 04 '16 at 23:05
  • 6
    All yall that are geeking out on the Directory Separator are forgetting that the strings could have come from a different OS than you are on now. Just replace backslash with forward slash and you're covered. – JeremyWeir Jul 26 '16 at 22:43
17

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");
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
urza
  • 187
  • 1
  • 2
13

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");
}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
mtazva
  • 997
  • 8
  • 13
12

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

string.Format("{0}/{1}", Url1.Trim('/'), Url2);
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Alex
  • 136
  • 1
  • 4
  • +1, although this is very similiar to mdsharpe's answer, which I improved upon in my answer. This version works great unless Url2 starts with / or \, or Url1 accidentally ends in \, or either one is empty! :) – Brian MacKay May 12 '10 at 23:57
10

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

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Believe2014
  • 3,664
  • 2
  • 23
  • 42
  • 1
    +1 for all the extra effort. I need to maintain this question a bit for some of the higher voted answers, you have thrown down the gauntlet. ;) – Brian MacKay May 04 '14 at 11:42
9

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.

Mahmoud Hanafy
  • 703
  • 9
  • 11
9

I think this should give you more flexibility as you can deal with as many path segments as you want:

public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));
GoldenAge
  • 2,378
  • 4
  • 15
  • 46
8

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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
javajavajavajavajava
  • 1,153
  • 2
  • 11
  • 19
6

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;
        });
    }
er sd
  • 71
  • 1
  • 4
5

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
TheGeneral
  • 69,477
  • 8
  • 65
  • 107
  • Some issues with the tests: // Result = test1/test2/test3\ for the 4th one and the last of the throws tests gives ArgumentNullException instead of ArgumentException – Moriya Jan 22 '20 at 09:44
4

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;
}
Alex Titarenko
  • 524
  • 6
  • 7
  • This helper method is very flexible and works well in many different use cases. Thank you! – Shiva May 22 '17 at 05:11
4

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

Chris Marisic
  • 30,638
  • 21
  • 158
  • 255
3

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
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
bigworld12
  • 794
  • 1
  • 8
  • 30
3

Why not just use the following.

System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")
Andreas
  • 3,275
  • 2
  • 34
  • 51
  • I was looking for the PowerShell version of this which would be: `[System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")`however this fails with a result of: `/Images/Image.jpg`. Remove the `/` from the second subPath and it works: `[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")` – Underverse Feb 06 '18 at 00:14
  • Nice idea, but it fails, when one of the parameter is null. – pholpar Mar 22 '18 at 14:19
3

If you don't want to add a third-party dependency such as Flurl or create a custom extension method, in ASP.NET Core (also available in Microsoft.Owin), you can use PathString which is intended for the purpose of building up URI paths. You can then create your full URI using a combination of this, Uri and UriBuilder.

In this case, it would be:

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

This gives you all the constituent parts without having to specify the separators in the base URL. Unfortunately, PathString requires that / is prepended to each string otherwise it in fact throws an ArgumentException! But at least you can build up your URI deterministically in a way that is easily unit-testable.

Neo
  • 3,225
  • 5
  • 40
  • 60
3

So I have another approach, similar to everyone who used UriBuilder.

I did not want to split my BaseUrl (which can contain a part of the path - e.g. http://mybaseurl.com/dev/) as javajavajavajavajava did.

The following snippet shows the code + Tests.

Beware: This solution lowercases the host and appends a port. If this is not desired, one can write a string representation by e.g. leveraging the Uri Property of 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));
         }
  }

Tested with .NET Core 2.1 on Windows 10.

Why does this work?

Even though Path.Combine will return Backslashes (on Windows atleast), the UriBuilder handles this case in the Setter of Path.

Taken from https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs (mind the call to string.Replace)

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

Is this the best approach?

Certainly this solution is pretty self describing (at least in my opinion). But you are relying on undocumented (at least I found nothing with a quick google search) "feature" from the .NET API. This may change with a future release so please cover the Method with Tests.

There are tests in https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs (Path_Get_Set) which check, if the \ is correctly transformed.

Side Note: One could also work with the UriBuilder.Uri property directly, if the uri will be used for a System.Uri ctor.

3

For anyone who is looking for a one-liner and simply wants to join parts of a path without creating a new method or referencing a new library or construct a URI value and convert that to a string, then...

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

It's pretty basic, but I don't see what more you need. If you're afraid of doubled '/' then you can simply do a .Replace("//", "/") afterward. If you're afraid of replacing the doubled '//' in 'https://', then instead do one join, replace the doubled '/', then join the website url (however I'm pretty sure most browsers will automatically convert anything with 'https:' in the front of it to read in the correct format). This would look like:

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

There are plenty of answers here that will handle all the above, but in my case, I only needed it once in one location and won't need to heavily rely on it. Also, it's really easy to see what is going on here.

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

DubDub
  • 695
  • 4
  • 16
2

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();
        });
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
baHI
  • 1,167
  • 11
  • 20
2

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);
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
skippy
  • 71
  • 4
2

For what it's worth, here a couple of extension methods. The first one will combine paths and the second one adds parameters to the 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();
    }
LawMan
  • 3,159
  • 24
  • 31
1

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);
    }
}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Martin Murphy
  • 1,727
  • 2
  • 15
  • 24
  • Nice touch with 'WebPath'. :) The code might be unecessarily dense though - it's hard for me to glance at this and say, yes, that's perfect. It makes me want to see unit tests. Maybe that's just me! – Brian MacKay Dec 10 '12 at 21:42
  • 1
    x.StartsWith("/") && !x.StartsWith("http") - why the http check? what do you gain? – penguat Dec 13 '12 at 10:03
  • You don't want to try to strip off the slash if it starts with http. – Martin Murphy Apr 16 '13 at 16:12
  • @BrianMacKay, I'm not sure a two liner warrants a unit test but if you like feel free to provide one. It's not like I'm accepting patches or anything, but feel free to edit the suggestion. – Martin Murphy Apr 16 '13 at 16:23
1

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);
}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Amit Bhagat
  • 3,672
  • 2
  • 20
  • 21
  • This is acceptable only for your case. There are cases which could broke your code. Also, you didn't do proper encoding of the parts of the path. This could be a huge vulnerability when it comes to cross site scripting attack. – Believe2014 May 01 '14 at 15:40
  • I agree to your points. The code is supposed to do just simple combining of two url parts. – Amit Bhagat May 02 '14 at 03:14
1

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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
TruthOf42
  • 1,651
  • 3
  • 16
  • 34
1

A simple one liner:

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

Inspired by @Matt Sharpe's answer.

Nick N.
  • 10,720
  • 4
  • 50
  • 70
1

Recently Combine method was added to Energy.Core package, so you might want to use it to join URL parts.

    string url;
    url = Energy.Base.Url.Combine("https://www.youtube.com", "watch?v=NHCgbs3TcYg");
    Console.WriteLine(url);
    url = Energy.Base.Url.Combine("https://www.youtube.com", "watch?v=NHCgbs3TcYg", "t=150");
    Console.WriteLine(url);

Additionally it will recognize parameter part, so it will work as you might expect (joining path with slash and parameters with ampersand).

https://www.youtube.com/watch?v=NHCgbs3TcYg

https://www.youtube.com/watch?v=NHCgbs3TcYg&t=150

Documentation for Energy.Base.Url class

Package on NuGet gallery

Code sample

Community
  • 1
  • 1
1

// Read all above samples and as result created my self:

static string UrlCombine(params string[] items)
{
    if (items?.Any() != true)
    {
        return string.Empty;
    }

    return string.Join("/", items.Where(u => !string.IsNullOrWhiteSpace(u)).Select(u => u.Trim('/', '\\')));
}

// usage

UrlCombine("https://microsoft.com","en-us")
0

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);
    }
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Per G
  • 347
  • 4
  • 18
  • 1
    You could have used VirtualPathUtiliy class to append and remove trailing slashes safely. Check out my answer: http://stackoverflow.com/a/23399048/3481183 – Believe2014 May 01 '14 at 15:38
0

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"
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Marcio Martins
  • 186
  • 3
  • 10
0

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]);
        }
    }
}
gunr2171
  • 10,315
  • 25
  • 52
  • 75
Joshua Smith
  • 124
  • 10
0

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);
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
DAre G
  • 167
  • 10
0

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!

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
pholpar
  • 1,474
  • 1
  • 12
  • 22
-1

I have to point out that Path.Combine appears to work for this also directly, at least on .NET 4.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Chris Marisic
  • 30,638
  • 21
  • 158
  • 255
  • 11
    If you use Path.Combine u will end up with something like this: www.site.com/foo\wrong\icon.png – Lehto Oct 25 '10 at 13:56
  • Exactly. I have spent some time implementing the Uri.Combine function for this exact reason: http://stackoverflow.com/a/23399048/3481183 – Believe2014 May 01 '14 at 15:37
-2

I haven't used the following code yet, but found it during my internet travels to solve a URL combine problem - hoping it's a succinct (and successful!) answer:

VirtualPathUtility.Combine
  • 3
    Not too useful really. There's a number of Google hits explaining some of its issues, but, as well as not liking "http://..." at the start, it actually removes the last sub path of the first argument if it doesn't end in a "/"! The MSDN description sounds fine though! – Mark Hurd Mar 02 '13 at 07:21
  • I have explained and provided a solution to this problem in my answer http://stackoverflow.com/a/23399048/3481183 – Believe2014 May 01 '14 at 15:36