URI Templates

This topic is a work in progress

If you are calling services over HTTP, chances are that you’ve found generating request URLs to be a relatively common source of bugs or hard-to-maintain code (e.g. complex use of String.Format or string interpolation, forgetting to correctly escape values).

A typical example

string serverName = "foo.com";
string baseUri = String.Format("http://{0}/api/v1/", serverName);
string customerId "AU-SYD/17";
string sortField = "First Name";
DateTime startOfMonth;

string customerRequestUri = String.Format("{0}/customers/{0}/orders?after={1}"
    baseUri,
    customerId,
    startOfMonth
);

Chances are, you’ll have problems trying to inject the date as-is into the “after” parameter, and depending on the format for customerId (e.g. “AU-SYD/27”) you may have even more trouble with escaped slashes in the path (IIS, for one, can get quite picky about that sort of thing). You’ll also wind up with a double slash after the base URI.

A slightly tidier version

Uri baseUri = new Uri(
    String.Format("http://{0}/api/v1", serverName)
);

// ...

Uri customerRequestUri = new Uri(baseUri,
    String.Format("customers/{0}?sortBy={1}"
        Uri.EncodeUriString(customerId),
        Uri.EncodeDataString(sortField)
    )
)

This version takes care of escaping values, and will correctly concatenate the base URI with the rest of the request URI. But it’s still a little awkward because you have to remember to escape each parameter.

Using URI templates

Uri baseUri = new Uri(
    String.Format("http://{0}/api/v1", serverName)
);

UriTemplate template = new UriTemplate("customers/{customerId}?sortBy={sortField}");

var templateParameters = new Dictionary<string, string>
{
    ["customerId"] = customerId,
    ["sortField"] = sortField
};

Uri customerRequestUri = template.Populate(baseUri, templateParameters);

templateParameters["customerId"] = "AU-MEL/4";
customerRequestUri = template.Populate(baseUri, templateParameters);

Now you can declare your template once (and, since it’s immutable, cache it in a static field) then populate it as required to generate the URI for each request.