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 DealerOn Dev

Totally right. Fixed.

Collapse
 
azhe403 profile image
Azhe Kun

Thanks for your this great article!