Path.Combine for URLs?

410,410

Solution 1

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

More details:

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

var url = Url.Combine(
    "http://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

Solution 2

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

Here's an example:

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

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

Solution 3

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

Solution 4

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

If you want to work with strings instead of Uris then the following will also produce the same result, simply adapt it to suit your needs:

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

Solution 5

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

Share:
410,410
Brian MacKay
Author by

Brian MacKay

Software engineer and entrepreneur with over 20 years of product development, line-of-business, and startup experience. Strong emphasis on execution and hands-on technical leadership. A decade of consulting experience provides a broad range of business knowledge, with industries touched spanning agriculture to yoga. Which is so close to an impressive A-to-Z set, so please let me know if you are aware of any zoos that need an app. :) Currently working in Microsoft's web stack including C#, Blazor, KendoUI, SQL Server, and Azure cloud architecture. Author, father, musician, expert-level chess player, decent kickboxer. I love games of all kinds. Above all, I'm a gigantic nerd and a friendly person.

Updated on July 08, 2022

Comments

  • Brian MacKay
    Brian MacKay almost 2 years

    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"

    • Todd Menier
      Todd Menier about 10 years
      Flurl includes a Url.Combine method that does just that.
    • Dave Gordon
      Dave Gordon almost 10 years
      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.
  • Brian MacKay
    Brian MacKay over 14 years
    +1: This is good, although I have an irrational problem with the output parameter. ;)
  • Chris
    Chris about 14 years
    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.
  • wsanville
    wsanville about 14 years
    This is a much better approach, as it will also work for paths of the form "../../something.html"
  • Brian MacKay
    Brian MacKay about 14 years
    +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
    Brian MacKay about 14 years
    +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
    Brian MacKay about 14 years
    See my answer for a more fully fleshed out version of this.
  • Brian MacKay
    Brian MacKay about 14 years
    +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! :)
  • Aisah Hamzah
    Aisah Hamzah over 13 years
    @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.
  • Aisah Hamzah
    Aisah Hamzah over 13 years
    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.
  • Aisah Hamzah
    Aisah Hamzah over 13 years
    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.
  • Lehto
    Lehto over 13 years
    If you use Path.Combine u will end up with something like this: www.site.com/foo\wrong\icon.png
  • Doctor Jones
    Doctor Jones over 13 years
    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("test.com/mydirectory/"), "/helloworld.aspx").ToString() gives you "test.com/helloworld.aspx"; which would be incorrect if we wanted a Path.Combine style result.
  • Joel Beckham
    Joel Beckham over 13 years
    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("test.com/mydirectory/"), "helloworld.aspx").ToString() results in "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.
  • Carl
    Carl over 13 years
    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.
  • nickd
    nickd about 13 years
    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.
  • Baptiste Pernet
    Baptiste Pernet about 13 years
    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
  • Joel Beckham
    Joel Beckham about 13 years
    @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.
  • Brian MacKay
    Brian MacKay almost 13 years
    +1: Now we're talking... I'm going to try this out. This might even end up being the new accepted answer. After trying to new Uri() method I really don't like it. Too finnicky.
  • angularsen
    angularsen over 12 years
    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
    angularsen over 12 years
    To support relative URIs I had to use ToString() instead of AbsoluteUri and UriKind.AbsoluteOrRelative in the Uri constructor.
  • Ales Potocnik Hahonina
    Ales Potocnik Hahonina over 12 years
    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.
  • Jaider
    Jaider almost 12 years
    you can use params string[] and recursively join them to allow more than 2 combinations
  • Jaider
    Jaider almost 12 years
    path.Replace(Path.DirectorySeparatorChar, '/');
  • Gromer
    Gromer almost 12 years
    This is exactly what I needed! Was not a fan of having to care where I put trailing slashes, etc...
  • SliverNinja - MSFT
    SliverNinja - MSFT almost 12 years
    path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
  • Brian MacKay
    Brian MacKay over 11 years
    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!
  • penguat
    penguat over 11 years
    x.StartsWith("/") && !x.StartsWith("http") - why the http check? what do you gain?
  • Vlax
    Vlax over 11 years
    It's a good solution but it doesn't address following cases: Assert.IsTrue(target.UrlCombine("../test1", "/") = "../test1/") Assert.IsTrue(target.UrlCombine("/", "test1/") = "/test1/")
  • Brian MacKay
    Brian MacKay over 11 years
    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.
  • Vlax
    Vlax over 11 years
    Sorry I was wrong your code does indeed covers the mentioned case!
  • Mark Hurd
    Mark Hurd about 11 years
    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!
  • Martin Murphy
    Martin Murphy about 11 years
    You don't want to try to strip off the slash if it starts with http.
  • Martin Murphy
    Martin Murphy about 11 years
    @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.
  • Per G
    Per G about 11 years
    To get it to wrk u must remove first / in second arg ie "/Images" - / Path.Combine("Http://MyUrl.com", "Images/Image.jpg")
  • Bogdan_Ch
    Bogdan_Ch almost 11 years
    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
  • teashark
    teashark over 10 years
    This answer suffers the same problem as Joel's: joining test.com/mydirectory/ and /helloworld.aspx will result in test.com/helloworld.aspx which is seemingly not what you want.
  • Faisal Mq
    Faisal Mq over 10 years
    Hi, this failed for following : if (Uri.TryCreate(new Uri("localhost/MyService/"), "/Event/SomeMethod?abc=123", out result)) { Console.WriteLine(result); } It is showing me result as : localhost/Event/SomeMethod?abc=123 Note: "http://" is replaced from base Uri here by stackoverflow
  • Tom Lint
    Tom Lint over 10 years
    @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.
  • NightOwl888
    NightOwl888 about 10 years
    +1 for rolling in the null checking so it won't blow up.
  • user247702
    user247702 about 10 years
    @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.
  • Daniel Liuzzi
    Daniel Liuzzi about 10 years
    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 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.
  • Believe2014
    Believe2014 about 10 years
    I have explained and provided a solution to this problem in my answer stackoverflow.com/a/23399048/3481183
  • Believe2014
    Believe2014 about 10 years
    Exactly. I have spent some time implementing the Uri.Combine function for this exact reason: stackoverflow.com/a/23399048/3481183
  • Believe2014
    Believe2014 about 10 years
    You could have used VirtualPathUtiliy class to append and remove trailing slashes safely. Check out my answer: stackoverflow.com/a/23399048/3481183
  • Believe2014
    Believe2014 about 10 years
    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.
  • Amit Bhagat
    Amit Bhagat about 10 years
    I agree to your points. The code is supposed to do just simple combining of two url parts.
  • Brian MacKay
    Brian MacKay about 10 years
    @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
    Brian MacKay about 10 years
    +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. ;)
  • Martin Capodici
    Martin Capodici about 10 years
    This answer doesn't deliver the goods when the first URI isn't the root of the domain. Shame.
  • Uriah Blatherwick
    Uriah Blatherwick almost 10 years
    I sure wish this was in the Base Class Library like Path.Combine.
  • Jack
    Jack over 9 years
    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
  • Joshua C
    Joshua C over 8 years
    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
  • BlueRaja - Danny Pflughoeft
    BlueRaja - Danny Pflughoeft over 8 years
    -1 new Uri(new Uri("test.com/mydirectory"), "helloworld.aspx") returns test.com/helloworld.aspx, which is not what anyone anywhere wants.
  • Brian MacKay
    Brian MacKay over 8 years
    It looks like this might be for paths, rather than URLs.
  • Admin
    Admin over 8 years
    @BrianMacKay Agreed that it looks like it, but it's from the UrlUtility class and used in the context of combining URLs
  • Admin
    Admin over 8 years
    Edited to clarify what class it belongs to
  • Tom Lint
    Tom Lint over 8 years
    @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.
  • Doctor Jones
    Doctor Jones over 8 years
    @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.
  • Tom Lint
    Tom Lint over 8 years
    @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.
  • n.podbielski
    n.podbielski over 8 years
    I also added check if any of paths to append are not null nor empty string.
  • Reda
    Reda over 8 years
    This doesn't behave as the op asked
  • mike
    mike about 8 years
    Surprising that none of the other massive upvoters mentioned that the other solutions work for two directories/names only.
  • LB2
    LB2 almost 8 years
    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.
  • JJS
    JJS almost 8 years
    @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
    JJS almost 8 years
    @MarkHurd I edited the code again, so that it's behaviorally the same as the C#, and syntactically equivalent as well.
  • Brian MacKay
    Brian MacKay almost 8 years
    @JJS Looks like someone broke this and you came back and fixed it -good job. :)
  • JJS
    JJS almost 8 years
    @BrianMacKay i broke it, markhurd pointed out my mistake and rolled back, i updated again... cheers
  • JeremyWeir
    JeremyWeir almost 8 years
    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.
  • Ilya Serbis
    Ilya Serbis over 7 years
    Same problem as in the top answer: Uri.TryCreate(new Uri("http://test.com/mydirectory"), "helloworld.aspx", out result) will set result to http://test.com/helloworld.aspx
  • Florian Fida
    Florian Fida over 7 years
    @Lu55 if you to put a / after the directory it will work.
  • Adam Plocher
    Adam Plocher about 7 years
    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.
  • Shiva
    Shiva almost 7 years
    This helper method is very flexible and works well in many different use cases. Thank you!
  • Harry Berry
    Harry Berry over 6 years
    Take care when using this Class, the rest of the class contains SharePoint specific artifacts.
  • Underverse
    Underverse over 6 years
    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")
  • pholpar
    pholpar about 6 years
    Nice idea, but it fails, when one of the parameter is null.
  • Brian MacKay
    Brian MacKay over 5 years
    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.
  • lizzy91
    lizzy91 over 5 years
    and if you dont use Flurl and would perfer a lightweight version, github.com/jean-lourenco/UrlCombine
  • Simon
    Simon about 5 years
    This solution generates an warning: docs.microsoft.com/de-de/visualstudio/code-quality/…
  • PRMan
    PRMan almost 5 years
    Count() should be Length so that you don't need to include Linq in your library just for that.
  • aggsol
    aggsol over 4 years
    This is a very reliable approach. Thumbs up for the unit test!!
  • Mahmoud Hanafy
    Mahmoud Hanafy over 4 years
    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: "test.com/test" Relative: "do/did/done" result: "test.com/test/do/did/done" This is also equivalent to u = new Uri(new Uri("test.com/test/"), "/do/did/done");
  • Mohammad Almasi
    Mohammad Almasi over 4 years
    If we have such a url the result will be wrong Example : 127.0.0.1:8080/drupal
  • Moriya
    Moriya over 4 years
    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
  • ThePeter
    ThePeter about 4 years
    This was exactly what i was looking for.
  • Robert McKee
    Robert McKee almost 4 years
    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. "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.
  • Arvo Bowen
    Arvo Bowen over 3 years
    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
  • Mladen B.
    Mladen B. about 3 years
    @BrianMacKay, OP never asked for relative-style paths...
  • Brian MacKay
    Brian MacKay about 3 years
    @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.
  • Slate
    Slate almost 3 years
    Note will chop a forward slash from the protocol, if you specify that separate from the host, e.g. ("https://", Host, Endpoint)
  • MickyD
    MickyD about 2 years
    2022: Whilst an OK solution, it's probably inadvisable for use for URLs in the same way string is for file and folder paths (where you would use Path.xxx() instead)
  • vibs2006
    vibs2006 almost 2 years
    Most Clean method!