Clean way to build URLs in .NET
Tags: programming, c
Published: 2021-11-11 14:19:54

---
title: Clean way to build URLs in .NET
published: true
date: 2021-11-11 14:13:36 UTC
tags: programming,c
canonical_url: https://siderite.dev/blog/clean-way-to-build-urls-in-net/
---

Original post at: https://siderite.dev/blog/clean-way-to-build-urls-in-net/

Let's start with a simple requirement and the obvious first draft of the code. The requirement is "build a method that receives a string and writes to the console a URL using that string as a parameter".

The obvious first attempt and working in every version of C# would be:

```
private static void ShowUrl(string something)
{
    var url = "https://siderite.dev/blog/search/formattablestring?param1="+something;
    Console.WriteLine(url);
}
```

But then you get some problems. You want to use a parameter that has strange characters in it, like an email, another URL, arithmetic symbols, etc. So you realize that you need to use an URL encode class. Next version is:

```
private static void ShowUrl(string something)
{
    var url = "https://siderite.dev/blog/search/formattablestring?param1="+Uri.EscapeDataString(something);
    Console.WriteLine(url);
}
```

Now that works for a second. [One might argue](https://stackoverflow.com/questions/602642/server-urlencode-vs-httputility-urlencode/1148326#1148326 "I had significant headaches with these methods before, I recommend you avoid any variant of UrlEncode, and instead use Uri.EscapeDataString - at least that one has a comprehensible behavior.") about the difference between [System.Web.HttpUtility.UrlEncode](https://docs.microsoft.com/en-us/dotnet/api/system.web.httputility.urlencode "Encodes a URL string. These method overloads can be used to encode the entire URL, including query-string values."), [System.Net.WebUtility.UrlEncode](https://docs.microsoft.com/en-us/dotnet/api/system.net.webutility.urlencode "Converts a text string into a URL-encoded string.") and [System.Net.Uri.EscapeDataString](https://docs.microsoft.com/en-us/dotnet/api/system.uri.escapedatastring?view=net-5.0 "Converts a string to its escaped representation."). Yet, his is not the post to do that. Follow the above links for more information.

Then someone decides to use a null for the parameter and you get a ArgumentNullException and you rage in frustration "_This is complicated and ugly! The first version looked much better and handled nulls, too. And now I have to add this long Uri.EscapeDataString to **every** URL parameter in **every** URL building code I write and **also** check for null!_".

But... what if you could write the method just like the first version and get rid of every problem?

First of all, let's get rid of that plus sign and use interpolated strings. They've been around for awhile:

```
private static void ShowUrl(string something)
{
    var url = $"https://siderite.dev/blog/search/formattablestring?param1={something}";
    Console.WriteLine(url);
}
```

Now, this has the same problem of the first version: no URL encoding. And you don't want to add an escape function to every parameter (imagine there would be 10 parameters in this URL). How about we use the fact that the URL is an interpolated string?

Check out this code:

```
private static void ShowUrl(string something)
{
    var url = UrlHelper.Url($"https://siderite.dev/blog/search/formattablestring?param1={something}");
    Console.WriteLine(url);
}
```

It uses an UrlHelper class to fix the problems with the URL in the string we generate. Or is it a string? I was writing a long time ago a [post about FormattableString](https://siderite.dev/blog/formattablestring-and-string.html/ "FormattableString and string interpolation") and how I didn't see a real use scenario for it. Well, this is it! Let's see what UrlHelper looks like:

```
public static class UrlHelper
{
    public static string Url(FormattableString url)
    {
        return string.Format(
            url.Format, 
            url.GetArguments()
                .Select(a => Uri.EscapeDataString(a?.ToString()??""))
                .ToArray());
    }
}
```

The Url method receives not a string, but a [FormattableString](https://docs.microsoft.com/en-us/dotnet/api/system.formattablestring "Represents a composite format string, along with the arguments to be formatted."). When an interpolated string is used as a FormattableString, it gives the developer access to a [Format](https://docs.microsoft.com/en-us/dotnet/api/system.formattablestring.format "Returns the composite format string.") string and a list of arguments in [GetArguments](https://docs.microsoft.com/en-us/dotnet/api/system.formattablestring.getarguments "Returns an object array that contains one or more objects to format."). In order to convert it to a string, one must only do _String.Format(s.Format,url.GetArguments())_.  In our case, the Format property will have the value "_https://siderite.dev/blog/search/formattablestring?param1={0}_" and the arguments list will contain one value: the value of the _something_ parameter.

Therefore, the Url method will be able to format the string, but first URL encode every single parameter in it.

I admit, this might not be the most readable code in the world, which is my pet peeve with FormattableString. Reading the ShowUrl method you have no clue that UrlHelper.Url receives a FormattableString as parameter. One might even look at that string and say "Hey, wait a minute! That's a bug waiting to happen!" and then proceed to _fix it_. Perhaps the name of the method could be made more intuitive like EncodeUrlParameters.

But wait! We can do better. I don't know how it might help in the future, but perhaps someone might want to process the parameters of the URL further. By returning a string we eliminate some of the information of the incoming parameter and we don't want to do that.

Also, careful people will notice that using String.Format introduces a subtle bug (or feature): we format the string without specifying a culture. This might be fine, using the current one by default, but it might also cause some issues. The FormattableString class does provide the static CurrentCulture and Invariant methods and, of course, the ToString method would work just fine as well, but then we lose the ability to process the parameters.

So here is the final version of the UrlHelper class:

```
public static class UrlHelper
{
    public static FormattableString EncodeUrlParameters(FormattableString url)
    {
        return FormattableStringFactory.Create(
            url.Format,
            url.GetArguments()
                .Select(a => Uri.EscapeDataString(a?.ToString() ?? ""))
                .ToArray());
    }
}
```

By using [FormattableStringFactory](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.formattablestringfactory "Provides a static method to create a FormattableString object from a composite format string and its arguments.") from System.Runtime.CompilerServices we get a FormattableString as an argument and we return one, with parameters URL encoded. Now, if the result of the method is used as a string, it will be automatically handled by ToString and if it will be used as a FormattableString it will contain all the information provided by the original parameters.

Hope that helps!