23

For awhile now, I've been searching for a Path.Combine method that works on URLs. This is similiar to Path.Combine for URLs? with one big difference.

I'll illustrate with an example. Say we have a base url: http://example.com/somefolder and a file: foo.txt. Thus, the full path would be: http://example.com/somefolder/foo.txt. Sounds simple, right? Ha.

I tried the Uri class: Uri.TryCreate(new Uri("http://example.com/somefolder"), "foo.txt", out x); which resulted in "http://example.com/foo.txt".

Then I tried Path: System.IO.Path.Combine("http://example.com/somefolder", "foo.txt"); which resulted in "http://example.com/somefolder\foo.txt"... Closer, but still no.

For kicks, I then tried: System.IO.Path.Combine("http://example.com/somefolder/", "foo.txt") which resulted in "http://example.com/somefolder/foo.txt".

The last one worked, but it's basically doing string concatenation at that point.

So I think I have two options:

  • Use Path.Combine and replace all \ with /
  • Use basic string concatenation

Am I missing a built in framework method for this?

UPDATE: The usage case I have is for downloading a bunch of files. My code looks like this:

    public void Download()
    {
        var folder = "http://example.com/somefolder";
        var filenames = getFileNames(folder);

        foreach (var name in filenames)
        {
            downloadFile(new Uri(folder + "/" + name));
        }
    }

I'm miffed at having to use string concat in the Uri constructor, as well having to check if the slash is needed (which I omitted in the code).

It seems to me that what I'm trying to do would come up a lot, since the Uri class handles a lot of other protocols besides http.

Community
  • 1
  • 1
Xcelled
  • 2,084
  • 3
  • 22
  • 40

5 Answers5

19

Flurl [disclosure: I'm the author] is a tiny URL builder library that can fill the gap with its Url.Combine method:

string url = Url.Combine("http://www.foo.com/", "/too/", "/many/", "/slashes/", "too", "few");
// result: "http://www.foo.com/too/many/slashes/too/few"

You can get it via NuGet: Install-Package Flurl.

I also wanted to point out that you can dramatically improve the efficiency of your code by downloading the files in parallel. There's a couple ways to do that. If you're on .NET 4.5 or above and can rewrite downloadFile as an async method, then your best option would be to replace your for loop with something like this:

var tasks = filenames.Select(f => downloadFileAsync(Url.Combine(folder, f)));
await Task.WhenAll(tasks);

Otherwise, if you're stuck on .NET 4, you can still achieve parallelism easily by using Parallel.ForEach:

Parallel.ForEach(filenames, f => downloadFile(Url.Combine(folder, f)));
Todd Menier
  • 37,557
  • 17
  • 150
  • 173
  • Indeed, I did parallelize the downloads with ForEach. I just cut it because it wasn't relevant for the question. Interesting link, though. I'll check it out and possibly change the accepted answer. – Xcelled Mar 25 '14 at 03:05
  • What's the licence the code under on? can I use it on my proprietary code? – Jack Jan 22 '15 at 20:25
  • 1
    Absolutely. [MIT license](http://choosealicense.com/licenses/mit/), so it's about as permissive as it gets. You just can't sue me. :) – Todd Menier Jan 22 '15 at 20:34
  • Really useful Lib! Thanks for your effort. – rynkadink Jul 10 '15 at 11:42
  • @ToddMenier: I forget to say thank you! Really useful :) – Jack Jul 16 '15 at 21:07
9

This is how the Uri class works.

var otherUri = new Uri("http://example.com/somefolder"));
// somefolder is just a path
var somefolder = otherUri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped);

// example one
var baseUri = new Uri("http://example.com/");   
var relativeUri = new Uri("somefolder/file.txt",UriKind.Relative);
var fullUri = new Uri(baseUri, relativeUri);

// example two
var baseUri = new Uri("http://example.com/somefolder"); 
var relativeUri = new Uri("somefolder/file.txt",UriKind.Relative);
var fullUri = new Uri(baseUri, relativeUri);

// example three
var baseUri = new Uri("http://example.com/");   
var fullUri = new Uri(baseUri, "somefolder/file.txt");  

Basically do it via string manipulation simplest and do

var isValid = Uri.TryCreate(..., out myUri);

If you want to find out more. Check out this post C# Url Builder Class

Updated answer

When referring to base uri it will always be http://example.com/ anything to the right is just path.

void Main()
{
    var ub = new UriBuilder("http://example.com/somefolder");
    ub.AddPath("file.txt"); 
            var fullUri = ub.Uri;
}
public static class MyExtensions
{
    public static UriBuilder AddPath(this UriBuilder builder, string pathValue)
    {
    var path = builder.Path;

    if (path.EndsWith("/") == false)
    {
        path = path + "/";
    }

    path += Uri.EscapeDataString(pathValue);

    builder.Path = path;
    }
}
Community
  • 1
  • 1
defong
  • 91
  • 5
  • 1
    Unfortunately, the link (and examples) you posted don't do what I want. I simply want to take a base URI path and append a filename to it. I'm using simple string concatenation, but I cannot believe that such a simple (common) usage isn't supported by the URI class. I'll update the post with an example usage and some mire details. – Xcelled Aug 02 '13 at 00:30
  • The uri class intent is just simply not what you are after. Updated the answers to help you achieve what you want. – defong Aug 02 '13 at 08:25
  • Seems like a large hole missing in the framework... Maybe JonSkeet made something to plug it... – Xcelled Aug 05 '13 at 05:45
2

I have a static method for this purpose:

// Combines urls like System.IO.Path.Combine
// Usage: this.Literal1.Text = CommonCode.UrlCombine("http://stackoverflow.com/", "/questions ", " 372865", "path-combine-for-urls");
public static string UrlCombine(params string[] urls) {
    string retVal = string.Empty;
    foreach (string url in urls)
    {
        var path = url.Trim().TrimEnd('/').TrimStart('/').Trim();
        retVal = string.IsNullOrWhiteSpace(retVal) ? path : new System.Uri(new System.Uri(retVal + "/"), path).ToString();
    }
    return retVal;

}
Roman Marusyk
  • 23,328
  • 24
  • 73
  • 116
Denny Jacob
  • 375
  • 4
  • 9
2

Here is a LINQ version of something close to the above answer.

public static string UrlCombine( this string root, params string[] parts)
{
    return parts
        .Select(part => part.Trim().TrimEnd('/').TrimStart('/').Trim())
        .Aggregate(root, (current, path) => current + ("/" + path));
}

var x = "http://domain.com";

var p = "path";

var u = x.UrlCombine(p, "test.html"); // http://domain.com/path/test.html
Xcelled
  • 2,084
  • 3
  • 22
  • 40
Rex Bloom
  • 364
  • 4
  • 14
  • You don't need all that trimming... `Trim(' ', '/')` will do it. – Dave Williams Mar 24 '17 at 08:38
  • That would work for spaces and / It is not identical to the previous code as Trim deals with all white-space not just spaces. But I like it and for a URL should work well. – Rex Bloom Mar 24 '17 at 15:02
-1

Omg, why all of you write such complex code? It is very simple:

private string CombineUrl(params string[] urls)
{
    string result = "";

    foreach (var url in urls)
    {
        if (result.Length > 0 && url.Length > 0)
            result += '/';

        result += url.Trim('/');
    }

    return result;
}

Example of use:

var methodUrl = CombineUrl("http://something.com", "/task/status/", "dfgd/", "/111", "qqq");

Result url is "http://something.com/task/status/dfgd/111/qqq"

  • 3
    What about if the url ends with a slash? For example, `CombineUrl("http://example.com", "/task/status/");` returns `http://example.com/task/status` instead of `http://example.com/task/status/` as one would expect. – styfle Feb 27 '17 at 17:03