DEV Community

Eric Damtoft for DealerOn Dev

Posted on • Edited on

Advanced String Templates in C#

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.

Top comments (4)

Collapse
 
jlopez788 profile image
Juan Lopez • Edited

Wow! I learned something new today! Thanks for sharing

Collapse
 
peledzohar profile image
Zohar Peled

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

Collapse
 
edamtoft profile image
Eric Damtoft

Totally right. Fixed.

Collapse
 
azhe403 profile image
Azhe Kun

Thanks for your this great article!