When dealing with the web, you will often require to deal with URLs.

Like so:

// Build a HttpClient object
var client = new HttpClient();
// Download the contentent of the URL as a string asynchronously
var result = await client.GetStringAsync("https://localhost");

Often, you will require to specify the port. You’d typically do it like this:

Note: Much as I am using values for parameters in these examples, interpret them as variables

var result = await client.GetStringAsync("https://localhost:" + 5000.ToString());

Or, more cleanly, using string interpolation, like this:

var result = await client.GetStringAsync($"https://localhost:{5000}");

Things get a bit more complicated if you have to specify a querystring

await client.GetStringAsync($"https://localhost:{5000}/Customers?Height=10&Weight=70");

This is very brittle and prone to errors if you are generating the query string (and the entire URL) for that matter. You have to keep track of a bunch of things:

  • The number and positions of the /
  • The ?
  • The number and positions of the &
  • URL encoding special characters

There is a better way to do this.

If you look at the documentation of most objects that take URL as parameters, they almost all support passing the URL as a string.

Take this for HttpClient GetStringAsync

They also almost all support passing the URL as a URI object, like this

Given it is a URI object, we can safely construct a URL using the full functionality of dedicated, typed objects using the UriBuilder object.

We can do the following:

var builder = new UriBuilder("https", "localhost", 5000, "Customers", $"?Height={10}");

await client.GetStringAsync(builder.Uri);

This is better, but still has a problem: you have to do the hard work to set and encode the query string correctly.

In this case, you are responsible for constructing this segment correctly:

"?Height=10"

Suppose it was more elaborate:

Height of 10
Weight of 70
BirthYear of 2000

You will have to do this:

$"?Height={10}&Weight={70}&BirthYear={200}"

Or better still,

var query = new Dictionary<string, string>()
{
    {"Height","10"},
    {"Weight" , "70"},
    {"SkinColor", "Brown"}
};

// Project the dictionary into a collection of name/value pairs and join them into a string
var queryString = string.Join('&', query.Select(q => $"{q.Key}={q.Value}"));

var builder = new UriBuilder("https", "localhost", 5000, "Customers", $"?{queryString}");

Much better.

The challenge here, as you can see, is you still have to do a lot of heavy lifting to generate the querystring. This can be done as an extension method.

Or, better yet, you an use a nuget library like Flurl.Http that has added a lot of extension methods to support precisely this scenario,

The code above can be rewritten as follows:

// Create an anonmymous type with our values
var queryParameters = new
{
    Height = 10,
    Weight = 70,
    BirthYear = 2000
};

// Build the base url
var url = "https://localhost:5000/"
    // Append the path
    .AppendPathSegment("Customers")
    // Set the querystring
    .SetQueryParams(queryParameters);
// Invoke the request
await client.GetStringAsync(url);

The library is versatile enough to also work with Uri objects, but through a custom Url object. We can reuse our code that sets the various parts of the Uri

// Create an anonymous type with our values
var queryParameters = new
{
    Height = 10,
    Weight = 70,
    BirthYear = 2000
};

// Build the base url
var url = new Url(new UriBuilder("https", "localhost", 5000, "Customers").Uri)
    // Set the querystring
    .SetQueryParams(queryParameters);
// Invoke the request
await client.GetStringAsync(url);

This code is much cleaner, easier to maintain and easier to support.

The code is in my Github.

Happy hacking!