loading...
DealerOn Dev

Advanced String Templates in C#

edamtoft profile image Eric Damtoft Updated on ・3 min read

c# 6 introduced string interpolation syntax. This feature allows for formatted text to be easily declared in code. For example:

var name = "World";
var message = $"Hello, {name}"; // "Hello, World"

At it's most basic, this is syntactic sugar on top of c#'s string.Format method, but with a much cleaner syntax. However, simply interpolating strings is often a dangerous proposition. Consider the following:

var url = $"https://api.example.com/sample?arg={arg}";

This will work in most cases, but if the parameter includes non-url-safe characters, this is likely to break. This could even expose a serious security vulnerability, expecially if you're doing string interpolation for HTML, javascript, or SQL (don't ever do this!).

Formattable String

Hidden deep in the c# language spec is a minor note about string interpolation. In general, interpolated strings are compiled to a call to string.Format, but you can also cast it to a FormattableString. This type represents the string template and an array of objects which will be interpolated into it.

var make = "Chrysler";
var model = "Town & Country";
var url = (FormattableString)$"https://api.example.com/vehicles?make={make}&model={model}";

Console.WriteLine(url.Format); // "https://api.example.com/vehicles?make={0}&model={1}";
Console.WriteLine(url.GetArgument(0)); // "Chrysler";
Console.WriteLine(url.GetArgument(1)); // "Town & Country";

This provides some interesting opportunities to create much more powerful string templating tools. For example, if we wanted to automatically encode the URL arguments, we can do the following:

public static class Format
{
  public static Uri Uri(FormattableString template)
  {
    var encodedArgs = new object[template.ArgumentCount];

    for (var i = 0; i < template.ArgumentCount; i++)
    {
      var original = template.GetArgument(i);
      encodedArgs[i] = HttpUtility.UrlEncode(original);
    }

    return new Uri(string.Format(template.Format, encodedArgs));
  }
}

The above code creates a new array and populates it with the url-encoded arguments. It then calls string.Format with the original template and new encoded arguments and returns it as a Uri to indicate that it's been safely encoded.

to use it, we can call

var make = "Chrysler";
var model = "Town & Country";
var url = Format.Uri($"https://api.example.com/vehicles?make={make}&model={model}");

Console.WriteLine(url); // https://api.example.com/vehicles?make=Chrysler&model=Town+%26+Country

Custom Formats

Another interesting feature we can make (ab)use of is custom format strings. In a traditional c# string template, you can specify a format for each argument, I.E.

Console.WriteLine($"Today is {DateTime.Now:yyyy-MM-dd}"); // Today is 2019-12-13

This is effectively the equivelant of calling dateTime.ToString("yyyy-MM-dd"). Any object that implements IFormattable can be used with a custom format string, which gives us an opportunity to define a simple syntax when working with string templates. In this example, we'll set up a simple HTML template that will either html encode a value or format it as markdown.

public static HtmlString Html(FormattableString template)
{
  var encodedArgs = new object[template.ArgumentCount];

  for (var i = 0; i < template.ArgumentCount; i++)
  {
    encodedArgs[i] = new HtmlArgument(template.GetArgument(i));
  }

  return new HtmlString(string.Format(template.Format, encodedArgs));
}

class HtmlArgument : IFormattable
{
  public HtmlArgument(object value)
  {
    Value = value;
  }

  public object Value { get; }

  public string ToString(string format, IFormatProvider formatProvider)
  {
    switch (format)
    {
      case "markdown":
        return new Markdown().Transform(Value.ToString());
      case "dangerous-raw-html":
        return Value.ToString();
      default:
        return HttpUtility.HtmlEncode(Value);
    }
  }
}

We can then use this as follows:

var html = Format.Html($"<article><h1>{title}</h1>{content:markdown}</article>");

title will be safely HTML encoded, and content will be rendered as markdown.

Wrapping Up

String interpolation in c# is convenient, but can lead to some traps. If not used carefully, it can break with edge cases or even introduce vulnerabilities. Formattable strings are a little known, but potentially quite useful feature in c# that can be used to make string interpolation smarter and more context-aware.

DealerOn Dev

On the DealerOn Development Team, we strive to be the industry leader in code quality, innovation, and culture. The author’s views expressed in this publication are endorsed by DealerOn. The author’s views elsewhere are their own.

Discussion

markdown guide
 

Wow! I learned something new today! Thanks for sharing

 

Actually, interpolated strings was introduced in c# 6, not 7. However, I didn't know about FormattableStrings, so thanks!