DEV Community

Eric Damtoft for DealerOn Dev

Posted on • Edited on

25 8

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.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

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!

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs