Schier Coding for fun

In ASP.NET MVC, views are defined by Razor templates. One very useful feature of Razor is that it allows you to generate HTML using simple lambda expressions, strongly-typed to your view model. However, if you have a complex, nested model and want to combine lambda expressions, things stop working. In this post, we’ll see how to solve this problem.

Simple example

Consider the following class that models user-related permissions:

public class UserPermissions
{
    public bool Add { get; set; }
    public bool Edit { get; set; }
    public bool Delete { get; set; }
}

If we set the @model directive of a view to our UserPermissions class, the view’s HTML helper will be bound to this class. We can then specify the property to generate HTML for by passing a lamdba to the HTML helper’s methods:

@model UserPermissions

<dl class="dl-horizontal">
    <dt>@Html.DisplayNameFor(m => m.Add)</dt>
    <dd>@Html.DisplayFor(m => m.Add)</dd>

    <dt>@Html.DisplayNameFor(m => m.Edit)</dt>
    <dd>@Html.DisplayFor(m => m.Edit)</dd>

    <dt>@Html.DisplayNameFor(m => m.Delete)</dt>
    <dd>@Html.DisplayFor(m => m.Delete)</dd>
</dl>

If we assume that Add is true and Edit and Delete are false, the following HTML output is rendered:

<dl class="dl-horizontal">
    <dt>Add</dt>
    <dd>
        <input checked="checked" class="check-box" disabled="disabled" type="checkbox" />
    </dd>

    <dt>Edit</dt>
    <dd>
        <input class="check-box" disabled="disabled" type="checkbox" />
    </dd>

    <dt>Delete</dt>
    <dd>
        <input class="check-box" disabled="disabled" type="checkbox" />
    </dd>

</dl>

Clean and simple. Let’s move to a more complex model.

More complex example

For our more complex example, we’ll create a model that stores the user permissions of several types of users:

public class RolePermissions
{
    public UserPermissions User { get; set; }
    public UserPermissions Moderator { get; set; }
    public UserPermissions Administrator { get; set; }
}

The following view allows us to edit these permissions:

@model RolePermissions

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <table>
        <thead>
        <tr>
            <th></th>
            <th>@Html.DisplayNameFor(m => m.User)</th>
            <th>@Html.DisplayNameFor(m => m.Moderator)</th>
            <th>@Html.DisplayNameFor(m => m.Administrator)</th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <td>@Html.DisplayNameFor(m => m.User.Add)</td>
            <td>@Html.EditorFor(m => m.User.Add)</td>
            <td>@Html.EditorFor(m => m.Moderator.Add)</td>
            <td>@Html.EditorFor(m => m.Administrator.Add)</td>
        </tr>
        <tr>
            <td>@Html.DisplayNameFor(m => m.User.Edit)</td>
            <td>@Html.EditorFor(m => m.User.Edit)</td>
            <td>@Html.EditorFor(m => m.Moderator.Edit)</td>
            <td>@Html.EditorFor(m => m.Administrator.Edit)</td>
        </tr>
        <tr>
            <td>@Html.DisplayNameFor(m => m.User.Delete)</td>
            <td>@Html.EditorFor(m => m.User.Delete)</td>
            <td>@Html.EditorFor(m => m.Moderator.Delete)</td>
            <td>@Html.EditorFor(m => m.Administrator.Delete)</td>
        </tr>
        </tbody>
    </table>

    <button type="submit">Save</button>
}

Although this view is more complex, it’s still quite readable. The rendered view looks something like this:

Edit model view rendered

It’s not the prettiest of GUI’s, but it is fully functional. To verify that our model is bound properly, let’s first examine the view’s controller:

using System.Web.Mvc;

public class PermissionsController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Index(RolePermissions model)
    {
        return View(model);
    }
}

Once again, nothing exciting happening here. If we submit our form and put a breakpoint on the return View(model); line, we can verify that model binding still works perfectly.

Removing duplication

Although our view looks nice, it does have a fair amount of duplication. In particular, the rows in the <tbody> section all have the exact same structure:

  • The first column contains the name of the permission.
  • The second column allows editing of that permission for the User role.
  • The third column allows editing of that permission for the Moderator role.
  • The fourth column allows editing of that permission for the Administrator role.

If one or more user types are added, or new types of permissions introduced, we have to edit our template in multiple places. This is tedious and error-prone, so let’s try to remove this repetition.

In Razor, you can extract shared functionality into reusable components using Razor helpers, which are functions that return HTML. What’s great though, is that you use a Razor template to define the HTML it returns. You can think of Razor helpers as a parameterized Razor view.

A first, naive attempt to create such a helper for our <tbody> rows would look something like this:

@helper PermissionRow(Func<UserPermissions, bool> permission)
{
    <tr>
        <td>@Html.DisplayNameFor(m => permission(m.User))</td>
        <td>@Html.EditorFor(m => permission(m.User))</td>
        <td>@Html.EditorFor(m => permission(m.Moderator))</td>
        <td>@Html.EditorFor(m => permission(m.Administrator))</td>
    </tr>
}

We can then eliminate much of the duplication as follows:

<tbody>
@PermissionRow(permissions => permissions.Add)
@PermissionRow(permissions => permissions.Edit)
@PermissionRow(permissions => permissions.Delete)
</tbody>

Unfortunately, if we execute this code, we get the following runtime exception:

Additional information: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.

What this error message tells us, is that the HTML helper methods only accept a very restricted set of expressions, namely ones that directly reference a property. In our example, we used a function to reference the property, which is not supported.

In our previous example, we did directly reference the property: @Html.EditorFor(m => m.User.Delete), which worked fine. That lambda’s expression is of type Expression<RolePermissions, bool>, so how can we convert the Func<UserPermissions, bool> parameter of our Razor helper to Expression<RolePermissions, bool>? Let’s find out!

Solution

To convert our Func<UserPermissions, bool> expression to Expression<RolePermissions, bool>, we’ll start by wrapping it in an Expression:

@helper PermissionRow(Expression<Func<UserPermissions, bool>> permission)
{
    <tr>
        <td>@Html.DisplayNameFor(m => permission(m.User))</td>
        <td>@Html.EditorFor(m => permission(m.User))</td>
        <td>@Html.EditorFor(m => permission(m.Moderator))</td>
        <td>@Html.EditorFor(m => permission(m.Administrator))</td>
    </tr>
}

The reason we use an Expression<Func<T>> instead of a Func<T>, is that expressions can be transformed into other expressions. We’ll see how we can use this feature shortly.

The next step is to extract the column rendering from our PermissionRow HTML helper to a separate HTML helper: PermissionColumn. This helper will receive two parameters:

  1. An expression describing a function from UserPermission to bool. This expression points to the permission property: Add, Edit or Delete.
  2. An expression describing a function from RolePermissions to UserPermission. This expression points to the user permission property: User, Moderator or Administrator.

The basic structure of this helper looks like this:

@helper PermissionColumn(
    Expression<Func<UserPermissions, bool>> userPermission, 
    Expression<Func<RolePermissions, UserPermissions>> rolePermission)
{
    <td>@Html.EditorFor("TODO: combine the two expressions")</td>
}

At this point, we are almost there. If we could combine the two expressions into one expression, in which we pipe the output of Func<RolePermissions, UserPermissions> into Func<UserPermissions, bool>, we would end up with an expression of type Expression<Func<RolePermissions, bool>>, precisely what we need!

So how can we combine these two expressions into a single expression? Let’s start with the basic skeleton of the combine method:

public static Expression<Func<RolePermissions, UserPermissions>> 
    Combine<RolePermissions, UserPermissions, bool>
        (Expression<Func<RolePermissions, UserPermissions>> outer,
         Expression<Func<UserPermissions, bool>> inner)
{
    return Expression.Lambda<Func<RolePermissions, bool>>(TODO, TODO);
}

In this method, we use the Expression.Lambda method to create a new expression. It takes two parameters:

  1. The body of the expression.
  2. The parameters passed to the expression in the body.

If you think about it, the second parameter must be equal to the outer expression’s parameters. That’s one step completed:

Expression.Lambda<Func<RolePermissions, bool>>(body, outer.Parameters);

For the first parameter, we need to construct an expression that represents the body of the combined expression. What we want is to pass the output of the Func<RolePermissions, UserPermissions> expression to the input parameter of the Func<UserPermissions, bool> expression. If you think about this backwards, this is the same as replacing the input parameter of the Func<UserPermissions, bool> expression with the output of the body of the Func<RolePermissions, UserPermissions> expression.

To transform/rewrite an expression, the ExpressionVisitor class can be used. Surprisingly, we need very little code to create a visitor that replaces one expression with another:

private class SwapVisitor : ExpressionVisitor
{
    private readonly Expression _from;
    private readonly Expression _to;

    public SwapVisitor(Expression from, Expression to)
    {
        _from = from;
        _to = to;
    }

    public override Expression Visit(Expression node) => node == _from ? _to : base.Visit(node);
}

The constructor takes two Expression parameters:

  1. The from parameter is the expression we want to replace.
  2. The to parameter is the expression we want to replace it with.

The actual rewriting happens in the Visit() method. There, we check if the node parameter equals the from expression. If so, we return the to expression; otherwise, we just continue visiting the node.

We can now use this class to create an expression visitor in which we replace the inner expression’s parameter (which has one parameter) with the outer expression’s body:

var swap = new SwapVisitor(inner.Parameters[0], outer.Body);

The final step is to use this swap visitor to create our target expression, which we do by calling its Visit() method with the inner expression’s body:

using System;
using System.Linq.Expressions;

public static class Expressions
{
    public static Expression<Func<RolePermissions, bool>> 
        Combine<RolePermissions, UserPermissions, bool>
            (Expression<Func<RolePermissions, UserPermissions>> outer,
             Expression<Func<UserPermissions, bool>> inner)
    {
        var swap = new SwapVisitor(inner.Parameters[0], outer.Body);
        return Expression.Lambda<Func<TOuter, TProperty>>(
            swap.Visit(inner.Body), outer.Parameters);
    }    
}

When the Visit method is called, the following happens:

  1. The node parameter of the Visit() method will equal the inner expression’s body. As this expression does not equal the inner.Parameters[0] expression, the expression is passed to base.Visit().
  2. Within base.Visit(), the expression’s children are visited. As the expression is a function, at some point it will visit the expression representing the function’s parameters.
  3. The Visit() method is now called with the inner.Parameters[0] expression, which it recognizes to be equal to the from expression, so the to expression (the outer expression’s body) is returned.

Finally, the Visit method will return the transformed expression, which has the type we were looking for: Expression<Func<RolePermissions, bool>>.

With our PermissionColumn helper is finished, we can use it in our PermissionRow helper:

@helper PermissionRow(Expression<Func<UserPermissions, bool>> permission)
{
    var displayName = Expressions.Combine((RolePermissions model) => model.User, permission);
    <tr>
        <td>@Html.DisplayNameFor(displayName)</td>
        <td>@PermissionColumn(permission, m => m.User)</td>
        <td>@PermissionColumn(permission, m => m.Moderator)</td>
        <td>@PermissionColumn(permission, m => m.Administrator)</td>
    </tr>
}

We can now run our application to verify that everything still works, which it does!

Making the extension generic

The final step is to make the Combine method generic, which is just a matter of replacing the concrete types with type parameters:

public static Expression<Func<TOuter, TProperty>> Combine<TOuter, TInner, TProperty>(
    Expression<Func<TOuter, TInner>> outer,
    Expression<Func<TInner, TProperty>> inner)
{
    var swap = new SwapVisitor(inner.Parameters[0], outer.Body);
    return Expression.Lambda<Func<TOuter, TProperty>>(swap.Visit(inner.Body), outer.Parameters);
}

And there we have it: a fully generic method that can combine Func<TOuter, TInner> and Func<TInner, TProperty> expressions into an Expression<Func<TOuter, TProperty>> expression.

Conclusion

HTML helpers in Razor views are a very convenient feature. In this post we showed how to use the ExpressionVisitor class to combine two expressions into a single expression. We used this feature to allow us to write small Razor helpers with which we were able to greatly reduce duplication in our view.

 

Building mobile apps can be a daunting task. One of the biggest problems is that each platform has its own, specific ecosystem, which prevents you from reusing code between platforms. The Ionic 2 platform lets you build apps for multiple platforms from a single code base, using only HTML, CSS and JavaScript. Ionic 2 is built on the powerful Angular 2 framework.

In this post, we’ll build a native app using Ionic 2. To demonstrate that Ionic 2 apps are real, native mobile apps, we’ll be accessing the device’s hardware to record (and playback) audio.

Installation

The first step to start building Ionic 2 apps is to install the Ionic 2 SDK (which is still in beta):

npm install -g ionic@beta

Once this command has completed, we can use the Ionic CLI to scaffold our app:

ionic start ionic2-recorder blank --v2

With this command, the CLI will scaffold an Ionic 2 app named ionic2-recorder using the blank template:

Creating Ionic app in folder /Users/erikschierboom/Programming/ionic2-recorder based on blank project
Downloading: https://github.com/driftyco/ionic2-app-base/archive/master.zip
[=============================]  100%  0.0s
Downloading: https://github.com/driftyco/ionic2-starter-blank/archive/master.zip
[=============================]  100%  0.0s
Installing npm packages...

Adding initial native plugins
[=============================]  100%  0.0s

Note that scaffolding the app can take quite a while, so this might be a good time to check out the getting started documentation.

After some waiting, the CLI will finish and our app has been scaffolded:

Adding in iOS application by default
add to body class: platform-ios
Saving your Ionic app state of platforms and plugins
Saved platform
Saved plugins
Saved package.json

♬ ♫ ♬ ♫  Your Ionic app is ready to go! ♬ ♫ ♬ ♫

Running in the browser

Before we start building our app, let’s see what the scaffolded app looks like. First, we go to the directory into which our app was scaffolded:

cd ionic2-recorder

Then we ask the CLI to show a preview of our app in the browser:

ionic serve

This will result in some Gulp tasks being run:

Running 'serve:before' gulp task before serve
[12:13:47] Starting 'clean'...
[12:13:47] Finished 'clean' after 6.43 ms
[12:13:47] Starting 'watch'...
[12:13:47] Starting 'sass'...
[12:13:47] Starting 'html'...
[12:13:47] Starting 'fonts'...
[12:13:47] Starting 'scripts'...
[12:13:47] Finished 'html' after 70 ms
[12:13:47] Finished 'scripts' after 72 ms
[12:13:47] Finished 'fonts' after 77 ms
[12:13:48] Finished 'sass' after 782 ms
9.1 MB bytes written (3.96 seconds)
[12:13:54] Finished 'watch' after 7.31 s
[12:13:54] Starting 'serve:before'...
[12:13:54] Finished 'serve:before' after 5.46 μs

Running live reload server: http://localhost:35729
Watching: www/**/*, !www/lib/**/*
√ Running dev server:  http://localhost:8100
Ionic server commands, enter:
  restart or r to restart the client app from the root
  goto or g and a url to have the app navigate to the given url
  consolelogs or c to enable/disable console log output
  serverlogs or s to enable/disable server log output
  quit or q to shutdown the server and exit

ionic $ 

When the Gulp tasks have completed, a browser window will open showing a preview of our app:

Browser preview of first version of the app

It may not look much, but consider this: we are previewing our app in a browser! Not on a device or simulator, but a browser! This means that you can develop apps just like you are developing a website. Let’s see how that works.

Building the app - beginning

To start building our app, open the scaffolded app’s directory in your favorite editor. Don’t be daunted by the number of files and directories, we are only interested in the app directory’s contents, which looks like this:

app
├── pages
|   └── home
|       ├── home.html
|       ├── home.scss
|       └── home.ts
├── theme
|   ├── app.core.scss
|   ├── app.ios.scss
|   ├── app.md.scss
|   ├── app.variables.scss
|   └── app.wp.scss
└── app.ts

Interestingly, this directory contains just three types of files:

  1. TypeScript files: the application’s logic. Compiled to JavaScript.
  2. SCSS files: the application’s styling. Compiled to CSS.
  3. HTML files: the application’s interface. Not compiled.

Clearly, as promised, an Ionic 2 app consists of only HTML, JavaScript and CSS, like any regular website.

At the moment, there is only one page in our app: the home page. Let’s see what the (scaffolded) home.html file looks like:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  The world is your oyster.
  <p>
    If you get lost, the <a href="http://ionicframework.com/docs/v2">docs</a> will be your guide.
  </p>
</ion-content>

Although there are some regular HTML tags like the <p> and <a> tags, there are also custom tags like <ion-header> and <ion-navbar>. These tags are specific to the Ionic framework and are known as components (which is an Angular 2 concept). Using these components, you’ll be able to quickly construct an interface for your app, without having to manually create and style them yourselves.

Modifying the app

As our first modification, let’s change the contents of the <ion-title> tag:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic 2 Audio Recorder
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  The world is your oyster.
  <p>
    If you get lost, the <a href="http://ionicframework.com/docs/v2">docs</a> will be your guide.
  </p>
</ion-content>

If we save this file, the CLI will rebuild the app and automatically refresh the browser window to show our app with the modified title:

Browser preview of text modification

As a first step towards implementing audio recording functionality, let’s add a “Start recording” button:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic 2 Audio Recorder
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <p>
    <button>Start recording</button>
  </p>
</ion-content>

Note that we just use a regular <button> tag, as there is no <ion-button> component.

The updated app looks like this:

Added button

Improved device preview

Although our browser preview looks fine, it does have fairly odd dimensions for a mobile device. If you are using Google Chrome, you can do one better. First, right-click in our app preview window and click on Inspect. This will open the Developer Tools window. If you look closely, you’ll see an icon that looks like a mobile phone in front of a tablet next to the Elements tab:

Developer tools button

If we click on that icon, Google Chrome will render the page as if it was rendered on a mobile device, correct screen proportions and all:
Device preview

Note that you can also select other devices, change the dimensions, and much more.

Multi-platform preview

One great feature of Ionic is that it will adjust the look and feel of your app depending on the platform it runs on. To easily see the differences between these platforms, you can start the ionic serve command with the --lab parameter:

ionic serve --lab

This results in the preview window showing all three platforms side-by-side:

Compare platforms

Although the difference is not huge in this case, the buttons and headers do have a different look-and-feel for each platform, even though they all run the same code. Pretty neat, right?

Note: you should disable the device preview in the Developer Tools for this to display correctly.

Preparing for recording audio

Now it’s time to start adding audio recording functionality to our app. Recording audio is a native capability: it requires access to a microphone. Therefore, we can’t test our recording functionality in the browser, we’ll have to use an actual device (or simulator).

To run our app on a device or simulator, Ionic uses Cordova. The first step is thus to install Cordova:

npm install -g cordova

Now that Cordova has been installed, the next step is to install the ionic-native plugin:

npm install ionic-native --save

The purpose of Ionic Native is to provide Ionic-friendly wrappers around Cordova plugins, which in turn allow access to the device’s API’s (such as the audio recording API). Schematically, it looks something like this:

App ==> Ionic Native ==> Cordova Plugin ==> Cordova ==> Device

The next step is to find the Ionic Native component that allows us to record audio, which is the MediaPlugin component. According to its documentation, it depends on the cordova-plugin-media plugin, which we can install using the CLI:

ionic plugin add cordova-plugin-media

We now have all the prerequisites to record audio in our app. But what file should contain our audio recording code? Let’s recall the structure of the app directory:

app
├── pages
|   └── home
|       ├── home.html
|       ├── home.scss
|       └── home.ts
├── theme
|   └── ...
└── app.ts

At the moment, we have only one page: the home page, which is defined by the three files in the home directory:

  • home.html: defines the interface of our home page.
  • home.scss: defines any custom styling of our home page.
  • home.ts: defines the logic of the home page.

Clearly, we’ll have to add our audio recording code to home.ts, which looks like this:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
  templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
  constructor(public navCtrl: NavController) {

  }
}

It turns out that an Ionic 2 page is just a regular Angular 2 component, nothing fancy.

Note that the template URL must be relative to the directory where the app’s compiled HTML/JavaScript/CSS is located: the build directory.

Recording audio

To import the MediaPlugin component in our home.ts file, we add the following import statement:

import { MediaPlugin } from 'ionic-native';

In the MediaPlugin component’s documentation, we noticed it had a startRecord function, exactly what we need!

Note: if you don’t like to read documentation, most editors also have code-completion (due to Ionic 2 being written in TypeScript):

Visual Studio Code code-completion

Let’s add a startRecording function to our HomePage class, in which we’ll start recording audio:

startRecording() {
  let media = new MediaPlugin('../Library/NoCloud/recording.wav');
  media.startRecord();
}

The final step is to call the startRecording function when the “Start recording” button is clicked:

<button (click)="startRecording()">Start recording</button>

You might be tempted to try this new functionality in the browser, but unfortunately, there will be an error:

Browser recording error

If you look at the error message more closely, it states that:

EXCEPTION: Error in build/pages/home/home.html:10:12
ORIGINAL EXCEPTION: ReferenceError: Media is not defined

Hmmm, it appears it cannot find an instance of Media. Although this error message is a bit cryptic, there is a very simple explanation: we are trying to use the native audio recording capability, which is not supported in the browser. The solution is simple: we should test this code on a physical device or simulator.

Silently failing is not very user-friendly though, so let’s gracefully handle this error. First, we import the AlertController component:

import { NavController, AlertController } from 'ionic-angular';

Next, we require an AlertController instance to be injected into our constructor:

constructor(public navCtrl: NavController, 
            public alertCtrl: AlertController) {
}

We can then use this AlertController instance to create a helper method that will show an alert:

showAlert(message) {
  let alert = this.alertCtrl.create({
    title: 'Error',
    subTitle: message,
    buttons: ['OK']
  });
  alert.present();
}

Finally, we’ll add a try/catch block to our startRecording function and use the showAlert function to show an alert:

startRecording() {
  try {
    let media = new MediaPlugin('../Library/NoCloud/recording.wav');
    media.startRecord();
  }
  catch (e) {
    this.showAlert('Could not start recording.');
  }
}

Now, if we run our application in the browser and click on the “Start recording” button, we’ll see a nice alert:

Browser recording error

Running in a simulator

As said, to record audio we’ll need to use a device our simulator. In both cases, we need to ensure our app supports the iOS platform. We can list the platforms our app currently supports using:

ionic platform

For our app, it outputs the following:

Installed platforms: ios 4.1.1
Available platforms: amazon-fireos, android, blackberry10, browser, firefoxos, osx, webos

Clearly, our app already supports iOS. Note: this was done automatically by the CLI due to us scaffolding the app on a MacBook.

If iOS platform support would not have been installed, we could have installed it manually:

ionic platform add ios

To run our app in the iOS simulator, first make sure you have Xcode and an iOS simulator installed. If you have those prerequisites installed, we can run our app in the simulator using:

ionic emulate ios

This time, there is far more going on in the background:

Running 'emulate:before' gulp task before emulate
...
[11:24:12] Starting 'emulate:before'...
[11:24:12] Finished 'emulate:before' after 6.88 μs

Building project  : /Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/ionic2-recorder.xcodeproj

	Configuration : Debug
	Platform      : emulator

CpResource cordova/build-release.xcconfig build/emulator/ionic2-recorder.app/build-release.xcconfig

CompileXIB ionic2-recorder/Classes/MainViewController.xib
CompileAssetCatalog build/emulator/ionic2-recorder.app ionic2-recorder/Images.xcassets

ProcessInfoPlistFile build/emulator/ionic2-recorder.app/Info.plist ionic2-recorder/ionic2-recorder-Info.plist
ProcessPCH build/sharedpch/ionic2-recorder-Prefix-ecagionshlazuyfofvjakcemdkry/ionic2-recorder-Prefix.pch.pch

CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/AppDelegate.o
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/MainViewController.o
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/main.o ionic2-recorder/main.m normal
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/CDVLogger.o 
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/CDVSplashScreen.o
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/CDVDevice.o 
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/CDVViewController+SplashScreen.o
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/CDVStatusBar.o 
CompileC build/ionic2-recorder.build/Debug-iphonesimulator/ionic2-recorder.build/Objects-normal/i386/IonicKeyboard.o.1_0.compiler

Ld build/emulator/ionic2-recorder.app/ionic2-recorder normal i386

GenerateDSYMFile build/emulator/ionic2-recorder.app.dSYM build/emulator/ionic2-recorder.app/ionic2-recorder

Touch build/emulator/ionic2-recorder.app
    cd /Users/erikschierboom/Programming/ionic2-recorder/platforms/ios
    /usr/bin/touch -c /Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/emulator/ionic2-recorder.app

** BUILD SUCCEEDED **


No target specified for emulator. Deploying to iPhone-6s-Plus, 9.3 simulator

The build script doesn’t just compile our app to HTML, JavaScript and CSS files, but this time it compiles our app to a real, native iOS app. The reason for this is simple: you can only run native apps in the simulator.

Once the native app has been compiled, the simulator will pop-up and our app is shown:

Simulator preview

Clicking on the “Start recording” button does not result in an error this time. However, we cannot verify if it actually recorded anything, so let’s fix this.

Adding audio playback support

To allow us to playback the recorded audio, we need to add several functions to the HomePage class:

  • A function to stop recording (which will save the recorded audio to file).
  • A function to playback the saved recording.
  • A function to stop the playback of the saved recording.

As all these functions need access to the same MediaPlugin instance, let’s store it in a private field:

export class HomePage {
  media: MediaPlugin = new MediaPlugin('../Library/NoCloud/recording.wav');
}

Next, we modify the existing startRecording function to use this private field and add the other functions:

startRecording() {
  try {
    this.media.startRecord();
  }
  catch (e) {
    this.showAlert('Could not start recording.');
  }
}

stopRecording() {
  try {
    this.media.stopRecord();
  }
  catch (e) {
    this.showAlert('Could not stop recording.');
  }
}

startPlayback() {
  try {
    this.media.play();
  }
  catch (e) {
    this.showAlert('Could not play recording.');
  }
}

stopPlayback() {
  try {
    this.media.stop();
  }
  catch (e) {
    this.showAlert('Could not stop playing recording.');
  }
}

Finally, we’ll update home.html to have a button for each function:

<p>
  <button (click)="startRecording()">Start recording</button>
</p>
<p>
  <button (click)="stopRecording()">Stop recording</button>
</p>
<p>
  <button (click)="startPlayback()">Start playback</button>
</p>
<p>
  <button (click)="stopPlayback()">Stop playback</button>
</p>

Let’s run our app again:

Simulator playback

Unfortunately, the screen is blank, indicating that an error occured. As it turns out, this error is due to the MediaPlugin instance being created when the class is constructed. At that point, the native media plugin will not yet have been loaded, so creating the MediaPlugin instance throws an error.

So what is the right time to create our shared MediaPlugin instance? Well, let’s look at the lifecycle events for an Ionic component (in order of occurance):

  • ionViewLoaded: runs when the page has loaded.
  • ionViewWillEnter: runs when the page is about to enter and become the active page.
  • ionViewDidEnter: runs when the page has fully entered and is now the active page.
  • ionViewWillLeave: runs when the page is about to leave and no longer be the active page.
  • ionViewDidLeave: runs when the page has finished leaving and is no longer the active page.
  • ionViewWillUnload: runs when the page is about to be destroyed and have its elements removed.
  • ionViewDidUnload: runs after the page has been destroyed and its elements have been removed.

Let’s try to initialize the MediaPlugin in the ionViewLoaded function:

export class HomePage {
  media: MediaPlugin;

  ionViewLoaded() {
    this.media = new MediaPlugin('../Library/NoCloud/recording.wav')
  }

  ...
}

If we run our app, the GUI is shown this time, but clicking on the “Start recording” button still issues an error:

Simulator playback

Moving the initialization to ionViewWillEnter also results in the same error, but moving it to ionViewDidEnter solves our problem:

Simulator playback

This time, everything works (provided we click on the buttons in the right order). We can record and playback audio in our Ionic 2 app, using just a couple of lines of JavaScript code!

As a funny side note, the official iOS simulator documentation explicitly states that the microphone can’t be used in the simulator :)

Running in a specific simulator

Each time we ran our app in the simulator, it opened the default simulator. However, it is also possible to specify the simulator that should be used.

The first step is to get a list of all (iOS) simulator device types. We can do this with the following command:

ios-sim showdevicetypes

This will output a list of device types that can be used in the simulator:

iPhone-5, 8.1
iPhone-5, 9.2
iPhone-5, 9.3
iPhone-6-Plus, 8.1
iPhone-6-Plus, 9.2
iPhone-6-Plus, 9.3
iPad-2, 8.1
iPad-2, 9.2
iPad-2, 9.3
iPad-Air, 8.1
iPad-Air, 9.2
iPad-Air, 9.3
Apple-TV-1080p, tvOS 9.2
Apple-Watch-38mm, watchOS 2.2
Apple-Watch-42mm, watchOS 2.2

We then specify the device type we want to use through the --target parameter:

ionic emulate ios --target="iPad-Air, 8.1"

The emulator will now load our app in an iPad Air simulator running iOS 8.1:

Simulator iPad

Running on a device

Although running our app in a simulator is fine, the ultimate test would be to run our app on a physical device. As I want to run our app on my iPhone, I need to make sure our app supports the iOS platform. Luckily for us, we already allowed our app to run on iOS when we ran our app in the simulator.

After having connected our iPhone to our MacBook, we then run:

ionic run ios

This should run our app on our device, but even though my device is recognized, an error occurs:

[....] Waiting up to 1 seconds for iOS device to be connected

[....] Found iPhone 6 Plus 'iPhone van Erik' (b6cb1e8bc407e8d32823fdf11d41d6e6941ae974) connected through USB.

2016-08-31 17:33:16.977 ios-deploy[3114:31946] [ !! ] Can't access app path '/Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/device/ionic2-recorder.app' : No such file or directory

Error: Error code 253 for command: ios-deploy with args: --justlaunch,--no-wifi,-d,-b,/Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/device/ionic2-recorder.app

After some Googling, I found that this problem can be fixed by appending --device:

ionic run ios --device

Similar to running our app on the simulator, our app is compiled to a native app:

Building project  : /Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/Audio Recorder.xcodeproj

	Configuration : Debug
	Platform      : device

...

ProcessInfoPlistFile build/device/Audio\ Recorder.app/Info.plist Audio\ Recorder/Audio\ Recorder-Info.plist
Packaging application: '/Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/device/Audio Recorder.app'

...

Results at '/Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/device/Audio Recorder.ipa' 

[....] Waiting up to 1 seconds for iOS device to be connected
[....] Found iPhone 6 Plus 'iPhone van Erik' (b6cb1e8bc407e8d32823fdf11d41d6e6941ae974) connected through USB.
[....] Waiting for iOS device to be connected
[....] Using iPhone 6 Plus 'iPhone van Erik' (b6cb1e8bc407e8d32823fdf11d41d6e6941ae974).

------ Install phase ------
[  0%] Found iPhone 6 Plus 'iPhone van Erik' (b6cb1e8bc407e8d32823fdf11d41d6e6941ae974) connected through USB, beginning install
[  5%] Copying /Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/device/Audio Recorder.app/META-INF/ to device

...

[ 49%] Copying /Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/device/Audio Recorder.app/www/plugins/ionic-plugin-keyboard/www/ios/keyboard.js to device

[ 52%] CreatingStagingDirectory
[ 57%] ExtractingPackage
[ 60%] InspectingPackage
[ 60%] TakingInstallLock
[ 65%] PreflightingApplication
[ 65%] InstallingEmbeddedProfile
[ 70%] VerifyingApplication
[ 75%] CreatingContainer
[ 80%] InstallingApplication
[ 85%] PostflightingApplication
[ 90%] SandboxingApplication
[ 95%] GeneratingApplicationMap
[100%] Installed package /Users/erikschierboom/Programming/ionic2-recorder/platforms/ios/build/device/Audio Recorder.app

------ Debug phase ------
Starting debug of iPhone 6 Plus 'iPhone van Erik' (b6cb1e8bc407e8d32823fdf11d41d6e6941ae974) connected through USB...

[  0%] Looking up developer disk image
[ 95%] Developer disk image mounted successfully
[100%] Connecting to remote debug server

After a short while, our app will be displayed on our phone (make sure to have your phone unlocked):

App on iPhone

Clicking on the “Start recording” button now issues the following prompt:

Request microphone access on iPhone

If we click “OK”, we can now verify that our app also works on an iPhone.

Running on a device using Ionic View

Besides viewing our app in a browser, simulator or on a device, there is a fourth option: the Ionic View app. With this option, you install the Ionic View app on your iOS or Android device, which you can then use to test your apps with.

To do so, simply run:

ionic upload

Its output will look like this:

WARN: No 'upload:before' gulp task found!
If your app requires a build step, you may want to ensure it runs before upload.

No previous login existed. Attempting to log in now.

To continue, please login to your Ionic account.
Don't have one? Create one at: https://apps.ionic.io/signup

Email: erik_schierboom@hotmail.com
Password: 
Logged in! :)
Uploading app....
Saved app_id, writing to ionic.io.bundle.min.js...
Successfully uploaded (e128c829)

Share your beautiful app with someone:

$ ionic share EMAIL

Saved api_key, writing to ionic.io.bundle.min.js...

To upload our app, we first had to login to our Ionic account. If you don’t have one, you can create one here. After signing in, the app is uploaded and can be accessed in the Ionic View app.

Before we’ll see what that looks like, notice that the build output also issued a warning:

WARN: No 'upload:before' gulp task found!
If your app requires a build step, you may want to ensure it runs before upload.

This warning indicates that the upload Gulp task does not depend on any other task. If you want to be ensure that you’ll always upload the latest version of your app, you need to make sure that the upload task depends on the build task. Fixing this is easy, just add the following line to gulpfile.js:

gulp.task('upload:before', ['build']);

Having uploaded our app, let’s open the Ionic View app to see what it looks like:

Ionic View

We can see that we have access to one app: the one we just uploaded. If we click on it, we get the following menu:

Ionic View app menu

The available options are fairly straightforward. We’ll use the “View app” button to run our app:

App running in Ionic View

As you can see, running the app from the Ionic View app looks identical to running the app directly on our device. Furthermore, as Ionic View is a native app with access to all native capabilities, our app still perfectly records audio.

Sharing with other people

Up until now, we tested our app ourselves. But what if we want other people to test our app? Well, simple! Just use the ionic share command:

ionic share coworker@test.com

So what does this command do? Let’s examine its output:

Sharing app ionic2-recorder (e128c829) with coworker@test.com.
An invite to view your app was sent.

You can see that an email invite was sent to the specified email address. This email contains instructions on how to preview our app. In short, what the user must do is:

  1. Create an Ionic account.
  2. Install the Ionic View app.
  3. Follow the instructions in the email invite.

Having followed these three steps, the user will have access to our app from within their Ionic View app installation.

Note that the version of the app they will receive, will be the latest version that was uploaded through ionic upload.

Extracting the recording functionality

At the moment, the audio recording and playback functionality was added directly to the HomePage class. As a consequence, if we want to use the audio functionality somewhere else in our app, we’d have to either duplicate the code or extract the audio functionality into a separate class. Let’s try the second option.

First, we’ll create a services directory in our app directory. Within the services directory, create a TypeScript file named audiorecorder.ts, in which we’ll define an AudioRecorder class. Extracting the audio recording functionality from our HomePage class, we end up with the following class:

import { MediaPlugin } from 'ionic-native';

export class AudioRecorder {
  media: MediaPlugin;

  constructor() {
    this.media = new MediaPlugin('../Library/NoCloud/recording.wav');
  }

  startRecording() {
    this.media.startRecord();
  }

  stopRecording() {
    this.media.stopRecord();
  }

  startPlayback() {
    this.media.play();
  }

  stopPlayback() {
    this.media.stop();
  }
}

Looks quite nice, agreed? There is another, obvious refactoring. The MediaPlugin instance only really needs to be created when it is first used, so let’s delay creating the MediaPlugin instance:

export class AudioRecorder {
  mediaPlugin: MediaPlugin = null;

  get MediaPlugin(): MediaPlugin {
    if (this.mediaPlugin == null) {
      this.mediaPlugin = new MediaPlugin('../Library/NoCloud/recording.wav');
    }

    return this.mediaPlugin;
  }

  startRecording() {
    this.MediaPlugin.startRecord();
  }

  stopRecording() {
    this.MediaPlugin.stopRecord();
  }

  startPlayback() {
    this.MediaPlugin.play();
  }

  stopPlayback() {
    this.MediaPlugin.stop();
  }
}

Finally, wouldn’t it be nice if we could inject this class, just like we did with the AlertController? Easy. We just import the Injectable decorator and decorate our class with it:

import { Injectable } from '@angular/core';

@Injectable()
export class AudioRecorder {
  ...
}

With all these changes, our HomePage class becomes a lot easier. First, we can replace the import of the MediaPlugin class with an import of our AudioRecorder class:

import { AudioRecorder } from '../../services/audiorecorder';

If you are wondering how we arrived at this relative import path, consider the current layout of the app directory:

app
├── pages
|   └── home
|       ├── home.html
|       ├── home.scss
|       └── home.ts
├── theme
|   └── ...
├── services
|   └── audiorecorder.ts
└── app.ts

You’ll see that after moving up two directories from the home.ts file, we are in the app directory. Then it’s into to the services directory and its audiorecorder.ts file (note that you should omit the file’s extension).

Next, we’ll add the AudioRecorder to the list of providers for our class (to ensure it can be injected) and add it as a parameter to our constructor:

@Component({
  templateUrl: 'build/pages/home/home.html',
  providers: [AudioRecorder]
})
export class HomePage {
  
  constructor(public navCtrl: NavController, 
              public alertCtrl: AlertController,
              public audioRecorder: AudioRecorder) {
  }

  ...
}

Note that due to the lazy initialization of the MediaPlugin instance in the AudioRecorder class, we no longer have to defer creating the instance to the ionViewLoaded function.

Finally, we’ll update the audio handling functions to use our AudioRecorder instance:

startRecording() {
  try {
    this.audioRecorder.startRecording();
  }
  catch (e) {
    this.showAlert('Could not start recording.');
  }
}

stopRecording() {
  try {
    this.audioRecorder.stopRecording();
  }
  catch (e) {
    this.showAlert('Could not stop recording.');
  }
}

startPlayback() {
  try {
    this.audioRecorder.startPlayback();
  }
  catch (e) {
    this.showAlert('Could not play recording.');
  }
}

stopPlayback() {
  try {
    this.audioRecorder.stopPlayback();
  }
  catch (e) {
    this.showAlert('Could not stop playing recording.');
  }
}  

If we test our refactored app, we’ll find it still works as expected, but our code now has much better separation of concerns.

Updating the user-interface

Although we can record audio in our app, the interface could use some refinement. For example, we should only allow people to click on the “Start playback” button once something has actually been recorded. Similarly, we shouldn’t allow people to stop recording when nothing is being recorded. The status of the buttons thus depends on the “state” of the audio recorder. We’ll model this by adding the following enum to the audiorecorder.ts file:

export enum AudioRecorderState {
    Ready,
    Recording,
    Recorded,
    Playing
}

We then add a RecordingState field to the AudioRecorder and update it in each function:

export class AudioRecorder {
  state: AudioRecorderState = AudioRecorderState.Ready;

  startRecording() {
    this.MediaPlugin.startRecord();
    this.state = AudioRecorderState.Recording;
  }

  stopRecording() {
    this.MediaPlugin.stopRecord();
    this.state = AudioRecorderState.Recorded;
  }

  startPlayback() {
    this.MediaPlugin.play();
    this.state = AudioRecorderState.Playing;
  }

  stopPlayback() {
    this.MediaPlugin.stop();
    this.state = AudioRecorderState.Ready;
  }

  ...
}

The last step is to dynamically enable and disable buttons depending on the audio recorder’s state. For that, we’ll modify the home.html file:

<p>
  <button (click)="startRecording()" [disabled]="audioRecorder.state != AudioRecorderState.Ready">Start recording</button>
</p>
<p>
  <button (click)="stopRecording()" [disabled]="audioRecorder.state != AudioRecorderState.Recording">Stop recording</button>
</p>
<p>
  <button (click)="startPlayback()" [disabled]="audioRecorder.state != AudioRecorderState.Recorded">Start playback</button>
</p>
<p>
  <button (click)="stopPlayback()" [disabled]="audioRecorder.state != AudioRecorderState.Playing">Stop playback</button>
</p>

Unfortunately, when we try to run our app, we get an error:

EXCEPTION: Error in build/pages/home/home.html:10:39
ORIGINAL EXCEPTION: TypeError: Cannot read property 'Ready' of undefined

The problem is that our view doesn’t know about the AudioRecorderState enum. To fix this, we’ll also import the AudioRecorderState in our home.ts file:

import { AudioRecorder, AudioRecorderState } from '../../services/audiorecorder';

Then, we add it as a field to our HomePage class, which allows our view to access its values:

export class HomePage {
  AudioRecorderState = AudioRecorderState;

  ...
}

This time, things work as we expected them to:

Dynamically enabled buttons

At first, only the “Start recording” button is enabled. Once we press that, only the “Stop recording” button is enabled, and so on.

Adding color and icons

The finishing touch is to give each button its own color and icon. Let’s start with the colors. First, we’ll add classes to our buttons in home.html:

<p>
  <button (click)="startRecording()" class="buttton-start-recording" [disabled]="audioRecorder.state != AudioRecorderState.Ready">Start recording</button>
</p>
<p>
  <button (click)="stopRecording()" class="buttton-stop-recording" [disabled]="audioRecorder.state != AudioRecorderState.Recording">Stop recording</button>
</p>
<p>
  <button (click)="startPlayback()" class="buttton-start-playback" [disabled]="audioRecorder.state != AudioRecorderState.Recorded">Start playback</button>
</p>
<p>
  <button (click)="stopPlayback()" class="buttton-stop-playback" [disabled]="audioRecorder.state != AudioRecorderState.Playing">Stop playback</button>
</p>

Next, we’ll create the styles for these newly added classes. We’ll do that in home.scss:

.buttton-start-recording,
.buttton-start-recording.activated {
    background-color: red;
}

.buttton-start-recording:hover:not(.disable-hover) {
    background-color: #ff6666;
}

.buttton-stop-recording,
.buttton-stop-recording.activated {
    background-color: orange;
}

.buttton-stop-recording:hover:not(.disable-hover) {
    background-color: #ffc966;
}

.buttton-start-playback,
.buttton-start-playback.activated {
    background-color: green;
}

.buttton-start-playback:hover:not(.disable-hover) {
    background-color: #80ff80;
}

.buttton-stop-playback,
.buttton-stop-playback.activated {
    background-color: blue;
}

.buttton-stop-playback:hover:not(.disable-hover) {
    background-color: #387ef5;
}

If we preview our app using these changes, the buttons will have different background colors:

Button colors

Our final improvement is to add icons to the buttons. Adding an icon to our buttons is simple: just add an <ion-icon> component with the correct name attribute nested in the <button>:

<p>
  <button (click)="startRecording()" class="buttton-start-recording" [disabled]="audioRecorder.state != AudioRecorderState.Ready">
    <ion-icon name="microphone"></ion-icon>
    Start recording
  </button>
</p>
<p>
  <button (click)="stopRecording()" class="buttton-stop-recording" [disabled]="audioRecorder.state != AudioRecorderState.Recording">
    <ion-icon name="mic-off"></ion-icon>
    Stop recording
  </button>
</p>
<p>
  <button (click)="startPlayback()" class="buttton-start-playback" [disabled]="audioRecorder.state != AudioRecorderState.Recorded">
    <ion-icon name="play"></ion-icon>
    Start playback
  </button>
</p>
<p>
  <button (click)="stopPlayback()" class="buttton-stop-playback" [disabled]="audioRecorder.state != AudioRecorderState.Playing">
    <ion-icon name="square"></ion-icon>
    Stop playback
  </button>
</p>

Let’s preview these changes using the multi-platform preview mode:

Button icons

Looks much better, right? Those of you with a keen eye may have noticed that some icons will look different between platforms, which Ionic does automatically to better match each platform’s look-and-feel.

Source code

If you’d like to test the application yourself, the full source code can be found in my ionic2-audio-recorder repository.

Conclusion

Building native apps can be frustrating due to the lack of code sharing between the various platforms. With Ionic 2, you can build native mobile apps for multiple platforms from a single code base, using just HTML, JavaScript and CSS. This makes building an app quite similar to building a regular website.

Building our audio recorder app using Ionic 2 app turned out to be quite easy. Using just HTML and Ionic’s custom components, we created an app that looks like a proper native app. Even building the audio recording code was not very hard, due to Ionic Native giving us an easy to use API to handle audio with.

With helpful tools such as the Ionic CLI, a browser preview mode and the Ionic View app, building and sharing mobile apps has never been as easy or fun!

 

Writing unit tests helps verify the correctness of code. However, most unit tests only test a limited set of pre-defined input values, often just one. Testing with fixed input values is known as example-based tests.

The problem with example-based tests is that they only verify correctness for the pre-defined input values. This can easily lead to an implementation that passes the test for the pre-defined values, but fails for any other value. The implementation could thus be (largely) incorrect, even though the test passes.

Let’s demonstrate this through an example. Note that we’ll use xUnit.net as the test framework in our examples.

Single input value

Suppose we want to write a method that calculates the MD5 hash of a given string. Let’s start by writing a unit test:

[Fact]
public void MD5ReturnsCorrectHash()
{
    var input = "foo";
    var md5 = MD5(input);
    var expected = "acbd18db4cc2f85cedef654fccc4a4d8";
    Assert.Equal(expected, md5);
}

This test method use a single input value ("foo") to verify the correctness of the MD5() method. Therefore, this is an example-based test. To make this test pass, we can implement the MD5() method as follows:

public static string MD5(string input)
{
    return "acbd18db4cc2f85cedef654fccc4a4d8";
}

Clearly, this implementation does not correctly implement the MD5 algorithm, but it passes our test! The danger of using one input value to verify correctness is that you can make the test pass by just hard-coding the expected result.

Note that in TDD, you actually should write the minimal amount of code to make the test pass, so the above implementation is perfectly reasonable when doing TDD.

In the next section, we’ll see how to strengthen our test by using multiple input values.

Multiple input values

The obvious solution to strengten tests that use one input value, is to use several input values. One way to do this is to create copies of the existing test method, but with different input values:

[Fact]
public void MD5ForFooInputReturnsCorrectHash()
{
    var md5 = MD5("foo");
    Assert.Equal("acbd18db4cc2f85cedef654fccc4a4d8", md5);
}

[Fact]
public void MD5ForBarInputReturnsCorrectHash()
{
    var md5 = MD5("bar");
    Assert.Equal("37b51d194a7513e45b56f6524f2d51f2", md5);
}

[Fact]
public void MD5ForBazInputReturnsCorrectHash()
{
    var md5 = MD5("baz");
    Assert.Equal("73feffa4b7f6bb68e44cf984c85f6e88", md5);
}

Although there is nothing wrong with these three tests, we have some code duplication. Luckily, xUnit has the concept of parameterized tests, which allows us to define a single test with more than one input value.

Here is the parameterized test equivalent of our previous three tests:

[Theory]
[InlineData("foo", "acbd18db4cc2f85cedef654fccc4a4d8")]
[InlineData("bar", "37b51d194a7513e45b56f6524f2d51f2")]
[InlineData("baz", "73feffa4b7f6bb68e44cf984c85f6e88")]
public void MD5ReturnsCorrectHash(string input, string expected)
{
    var md5 = MD5(input);
    Assert.Equal(expected, md5);
}

This test differs from our previous tests in several ways:

  • The [Fact] attribute is replaced with the [Theory] attribute. This marks the test as a parameterized test.
  • The test method has two parameters: input and expected, which replace the hard-coded values in our test.
  • Three [InlineData] attributes have been added, one for each input value/expected hash combination.

When xUnit runs this test, it will actually run it three times, with the [InlineData] attributes’ parameters used as the input and expected parameters. Therefore, running our parameterized test results in our test being called three times with the following arguments:

MD5ReturnsCorrectHash("foo", "acbd18db4cc2f85cedef654fccc4a4d8");
MD5ReturnsCorrectHash("bar", "37b51d194a7513e45b56f6524f2d51f2");
MD5ReturnsCorrectHash("baz", "73feffa4b7f6bb68e44cf984c85f6e88");

If we run our parameterized test, it will fail for the "bar" and "baz" input values. To make our test pass, we could again hard-code the expected values:

public static string MD5(string input)
{
    if (input == "foo")
    {
        return "acbd18db4cc2f85cedef654fccc4a4d8";
    }
    if (input == "bar")
    {
        return "37b51d194a7513e45b56f6524f2d51f2";
    }
    if (input == "baz")
    {
        return "73feffa4b7f6bb68e44cf984c85f6e88";
    }
    
    return input;
}

With this modified implementation, the test passes for all three input values. Unfortunately, having multiple tests still allowed us to easily hard-code the implementation; it did not prevent us from incorrectly implementing the MD5 algorithm.

So although having multiple input values leads to stronger tests, it still remains a limited set of input values. Wouldn’t it be great if we could run our tests using all possible input values? Enter property-based testing.

Property-based testing

In property-based testing, we take a different approach to writing our tests. Instead of testing for specific input and output values, we test the properties of the code we are testing. You can think of properties as rules, invariants or requirements. For example, these are some essential properties of the MD5 algorithm:

  1. The hash is 32 characters long.
  2. The hash only contains hexadecimal characters.
  3. Equal inputs have the same hash.
  4. The hash is different from its input.
  5. Similar inputs have significantly different hashes.

So how do we write tests for these properties? First, notice that these five properties are generic: they must be true for all possible input values. Unfortunately, writing tests that actually check all input values is not feasible, as running them would take ages. But if we can’t test all input values, how can we write tests for our properties? Well, we use the next best thing: random input values.

If you test using random input values, hard-coding the expected values is no longer possible as you don’t know beforehand which input values will be tested. This forces you to write a generic implementation that works for any (valid) input value.

Let’s see how that works by writing property-based tests for all five properties we just defined.

Property 1: hash is 32 characters long

Our first property states that an MD5 hash is a string of length 32. If we would write this as a regular, example-based test, it would look like this:

[Fact]
public void MD5ReturnsStringOfCorrectLength()
{
    var input = "foo";
    var hash = MD5(input);
    Assert.Equal(32, hash.Length);
}

We could strengthen our test by converting it to a parameterized test with several input values:

[Theory]
[InlineData("foo")]
[InlineData("bar")]
[InlineData("baz")]
public void MD5ReturnsStringOfCorrectLength(string input)
{
    var hash = MD5(input);
    Assert.Equal(32, hash.Length);
}

However, as said, property-based tests should work with any input value. A property-based test is thus a parameterized test, but with the pre-defined input values replaced by random input values. Our initial attempt at writing a property-based test might look like this:

[Theory]
public void MD5ReturnsStringOfCorrectLength(string input)
{
    var hash = MD5(input);
    Assert.Equal(32, hash.Length);
}

Unfortunately, if we run this test, we’ll find that xUnit reports an error for parameterized tests without input.

To define a test that works with randomly generated input, we’ll use the FsCheck property-based testing framework. We’ll also install the FsCheck.Xunit package, for easy integration of FsCheck with xUnit.

Having installed these libraries, we can convert our test to a property-based test by decorating it with the [Property] attribute:

[Property]
public void MD5ReturnsStringOfCorrectLength(string input)
{
    var hash = MD5(input);
    Assert.Equal(32, hash.Length);
}

Note that we don’t explicitly specify the input value(s) to use, FsCheck will (randomly) generate those. Let’s run this test to see what happens:

MD5ReturnsStringOfCorrectLength [FAIL]

    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 1 test (0 shrinks) (StdGen (766423555,296119444)):
    Original:
    ""

    ---- Assert.Equal() Failure
    Expected: 32
    Actual:   0

The test report indicates that our property-based test failed after one test, for the randomly generated empty string ("") input value. To make our empty string pass the test, we’ll pad unknown inputs to a length of 32:

public static string MD5(string input)
{
    if (input == "foo")
    {
        return "acbd18db4cc2f85cedef654fccc4a4d8";
    }
    if (input == "bar")
    {
        return "37b51d194a7513e45b56f6524f2d51f2";
    }
    if (input == "baz")
    {
        return "73feffa4b7f6bb68e44cf984c85f6e88";
    }
    
    return input.PadRight(32);
}

This should fix the empty string problem, so let’s run the test again:

MD5ReturnsStringOfCorrectLength [FAIL]

    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 12 tests (0 shrinks) (StdGen (1087984417,296119448)):
    Original:
    <null>
        
    ---- System.NullReferenceException : Object reference not set to an instance of an object

Hmmm, our test still fails. This time though, the first 11 tests passed, so we made some progress. The test now failed when FsCheck generated the null input value. Let’s fix this:

public static string MD5(string input)
{
    if (input == "foo")
    {
        return "acbd18db4cc2f85cedef654fccc4a4d8";
    }
    if (input == "bar")
    {
        return "37b51d194a7513e45b56f6524f2d51f2";
    }
    if (input == "baz")
    {
        return "73feffa4b7f6bb68e44cf984c85f6e88";
    }
    
    return (input ?? string.Empty).PadRight(32);
}

And we run our test again:

MD5ReturnsStringOfCorrectLength [FAIL]

    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 43 tests (34 shrinks) (StdGen (964736467,296119643)):
    Original:
    "#e3n+[TC9[Jlbs,x=3U!f\~J'i u+)-y>4VLg]uA("
                
    Shrunk:
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

    ---- Assert.Equal() Failure
    Expected: 32
    Actual:   33

Again, more progress, as it now took 43 tests before the test failed for the input value "#e3n+[TC9[Jlbs,x=3U!f\~J'i u+)-y>4VLg]uA(". There is something different about the test report this time though, as it mentions “34 shrinks” and a “shrunk” value, what is that about?

Shrinking

In property-based testing, shrinking is used to find a minimal counter-example that proves the property does not hold for all possible input values. This minimal counter example is listed as the “shrunk” in our test report. Before we examine how shrinking works, try to figure out for yourself what’s so special about the “shrunk” "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" input value mentioned in the test report.

If you guessed that the specified “shrunk” input was special for its length, you’d be correct! The "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" string contains 33 characters, which significance becomes apparent if we look at the code in our implementation that processes this input value:

return (input ?? string.Empty).PadRight(32);

If we use "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" as our input, the PadRight(32) call doesn’t do anything as our string is already greater than or equal to 32. Therefore, the input value is returned unmodified, which means that the string that is returned also has length 33, which fails our test.

The interesting thing about an input value of length 33 is that 33 is the minimum length for which the test fails; strings of length 32 or less all pass the test. As such, the listed “shrunk” value of length 33 is a minimal counter-example to our property.

The main benefit of having a minimal counter-example is that is helps you locate precisely where your implementation breaks down (in our case for strings of length 33 and greater). You thus spend less time debugging and more writing code.

Finding the “shrunk” value

So how does FsCheck find this shrunk value? To find out, we’ll have FsCheck output the values it generates. Doing that is easy, we just set the Verbose property of our [Property] attribute to true:

[Property(Verbose = true)]
public void MD5ReturnsStringOfCorrectLength(string input)
{
    var hash = MD5(input);
    Assert.Equal(32, hash.Length);
}

Now if we run our test, we’ll see exactly which values FsCheck generated as test input:

MD5ReturnsStringOfCorrectLength [FAIL]

    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 50 tests (38 shrinks) (StdGen (1153044621,296119646)):
    Original:
    "XqyW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9"
    Shrunk:
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

    ---- Assert.Equal() Failure
    Expected: 32
    Actual:   33

    Output:
    0: "5z"
    1: "Y"
    2: "r"
    3: ""
    4: "t9[Q"
    ...
    45: <null>
    46: "Qlbz?|perK"
    47: "XP3vO$-`l"
    48: Y{q6oevZA7"0R
    49: "XqyW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9"

    shrink: "qyW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9"
    shrink: "yW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9"
    shrink: "yW\O!Lr%ce3]4=H~=6lG,5lT\aDz%n9"
    shrink: "W\O!Lr%ce3]4=H~=6lG,5lT\aDz%n9"
    shrink: "\O!Lr%ce3]4=H~=6lG,5lT\aDz%n9"
    shrink: "O!Lr%ce3]4=H~=6lG,5lT\aDz%n9"
    shrink: "O!Lr%ce3]4=H~=6lG,5lT\aDz%n9a"
    shrink: "O!Lr%ce3]4=H~=6lG,5lT\aDz%naa"
    shrink: "O!Lr%ce3]4=H~=6lG,5lT\aDz%aaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5lT\aDzaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5lT\aDaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5lT\aaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5lTaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5lTaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5laaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5laaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,5aaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,aaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lG,aaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6lGaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6laaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=6aaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~=aaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=H~aaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=Haaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4=aaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]4aaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3]aaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ce3aaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%ceaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%caaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%caaaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lr%aaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Lraaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "O!aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "Oaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    shrink: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

The test report starts with the 50 randomly generated input values (note: we omitted most for brevity). The last input value ("XqyW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9"), listed as input #49, is the first for which the test failed. Having found an input value that fails the test, FsCheck then starts the shrinking process.

To see how shrinking works, let’s list the failing input value and the first six subsequent shrinks:

# Input Length Passes test
0 “XqyW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9” 33 false
1 “qyW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9” 32 true
2 “yW\O!Lr%ce3]4=H~=6lG, 5lT\aDz%n9” 31 true
3 “yW\O!Lr%ce3]4=H~=6lG,5lT\aDz%n9” 30 true
4 “W\O!Lr%ce3]4=H~=6lG,5lT\aDz%n9” 29 true
5 “\O!Lr%ce3]4=H~=6lG,5lT\aDz%n9” 28 true
6 “O!Lr%ce3]4=H~=6lG,5lT\aDz%n9” 27 true

The pattern here is quite obvious: with each shrinking step, the previous value is stripped of its first character, decreasing the length by one. Note that all strings of length 32 or less pass the test. FsCheck will use this fact later on.

The next six shrinks follow a different pattern:

# Input Length Passes test
7 “O!Lr%ce3]4=H~=6lG,5lT\aDz%n9a” 28 true
8 “O!Lr%ce3]4=H~=6lG,5lT\aDz%naa” 28 true
9 “O!Lr%ce3]4=H~=6lG,5lT\aDz%aaa” 28 true
10 “O!Lr%ce3]4=H~=6lG,5lT\aDzaaaa” 28 true
11 “O!Lr%ce3]4=H~=6lG,5lT\aDaaaaa” 28 true
12 “O!Lr%ce3]4=H~=6lG,5lT\aaaaaaa” 28 true

This time, each shrink step replaces one character with the 'a' character. FsCheck uses this shrink strategy to check if replacing specific characters in the input string can make the test fail.

As changing characters in the input values did not make the test fail, FsCheck then uses the fact that the only input value that failed the test had a length of 33. It therefore modifies its shrinking strategy and starts generating longer input values, which will lead to generating a 33 length input. Besides generating longer input values, it will additionaly also use the one character modification shrinking strategy as an extra check. This leads to the following sequence of shrinks:

# Input Length Passes test
13 “O!Lr%ce3]4=H~=6lG,5lTaaaaaaaa” 29 true
14 “O!Lr%ce3]4=H~=6lG,5lTaaaaaaaaa” 30 true
15 “O!Lr%ce3]4=H~=6lG,5laaaaaaaaaa” 30 true
16 “O!Lr%ce3]4=H~=6lG,5laaaaaaaaaaa” 31 true
17 “O!Lr%ce3]4=H~=6lG,5aaaaaaaaaaaa” 31 true
18 “O!Lr%ce3]4=H~=6lG,aaaaaaaaaaaaa” 31 true
19 “O!Lr%ce3]4=H~=6lG,aaaaaaaaaaaaaa” 32 true
20 “O!Lr%ce3]4=H~=6lGaaaaaaaaaaaaaaa” 32 true

The 11 shrinks that follow these shrinks are all 32 character strings with one character replaced, which all pass the test. Things start getting interesting from shrink #32, which is a string of length 33 and thus fails the test:

# Input Length Passes test
32 “O!Lr%caaaaaaaaaaaaaaaaaaaaaaaaaaa” 33 false

At this point, FsCheck rightly infers that strings of length 33 form the minimal set of counter-examples to our property. The final shrinking steps again use the single character replacement shrinking strategy, to see if changing a single character in an input value of length 33 can make the test pass:

# Input Length Passes test
33 “O!Lr%aaaaaaaaaaaaaaaaaaaaaaaaaaaa” 33 false
34 “O!Lraaaaaaaaaaaaaaaaaaaaaaaaaaaaa” 33 false
35 “O!Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaa” 33 false
36 “O!aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa” 33 false
37 “Oaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa” 33 false
38 “aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa” 33 false

Even with all characters changed, the input still fails the test. At this point, FsCheck considers the input value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" to be the minimal counter-example (or “shrunk”) to our property-based test.

Now that we know our test fails for strings of length 33 (and greater), we can fix our implementation as follows:

public static string MD5(string input)
{
    if (input == "foo")
    {
        return "acbd18db4cc2f85cedef654fccc4a4d8";
    }
    if (input == "bar")
    {
        return "37b51d194a7513e45b56f6524f2d51f2";
    }
    if (input == "baz")
    {
        return "73feffa4b7f6bb68e44cf984c85f6e88";
    }
    
    return (input ?? string.Empty).PadRight(32).Substring(0, 32);
}

Now, our test passes all generated inputs, and we have our first, working, property-based test!

Clearly, this single property test did not force us to write a correct implementation, but that is perfectly normal. With property-based testing, you usually need to write several property-based tests before you are finally forced to write a correct implementation, so let’s move on to property two.

Property 2: hash contains only hexadecimal characters

Our second property states that the MD5 hash consists of only hexadecimal characters:

[Property]
public void MD5ReturnsStringWithOnlyAlphaNumericCharacters(string input)
{
    var hash = MD5(input);
    var allowed = "0123456789abcdef".ToCharArray();
    Assert.All(hash, c => Assert.Contains(c, allowed));
}

This test should fail for any input other than the "foo", "bar" or "baz" strings, which indeed it does:

MD5ReturnsStringWithOnlyAlphaNumericCharacters [FAIL]

    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 1 test (0 shrinks) (StdGen (961984695,296121694)):
    Original:
    ""

    ---- Assert.All() Failure: 32 out of 32 items in the collection did not pass.
    [31]: Xunit.Sdk.ContainsException: Assert.Contains() Failure
        Not found: ' '
        In value:  Char[] ['0', '1', '2', '3', '4', ...]

Let’s fix this:

public static string MD5(string input)
{
    if (input == "foo")
    {
        return "acbd18db4cc2f85cedef654fccc4a4d8";
    }
    if (input == "bar")
    {
        return "37b51d194a7513e45b56f6524f2d51f2";
    }
    if (input == "baz")
    {
        return "73feffa4b7f6bb68e44cf984c85f6e88";
    }
    
    return "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
}

This implementation not only passes this test, but also all other tests. On to property three.

Property 3: same hash for same input

This test verifies that when presented with the same input, the same hash is returned:

[Property]
public void MD5ReturnsSameHashForSameInput(string input)
{
    var hash1 = MD5(input);
    var hash2 = MD5(input);
    Assert.Equal(hash1, hash2);
}

Simple enough, and the current implementation passes this test.

Property 4: hash is different from input

Our next property verifies that the hash is different from the input value (which is an essential property of any hashing algorithm):

[Property]
public void MD5ReturnsStringDifferentFromInput(string input)
{
    var hash = MD5(input);
    Assert.NotEqual(input, hash);
}

At the moment, this test will pass for every input string except "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa". That means that unless FsCheck randomly generates the "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" input value, the property-based test will pass and we would think our implementation was correct. Let’s demonstrate this by adding an example-based test:

[Fact]
public void MD5ReturnsStringDifferentFromManyAsInput()
{
    var input = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    var hash = MD5(input);
    Assert.NotEqual(input, hash);
}

If we run both the property-based and the example-based tests, only the example-based test fails:

MD5ReturnsStringDifferentFromManyAsInput [FAIL]

    Assert.NotEqual() Failure
    Expected: Not "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    Actual:   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

This is one of the drawbacks of testing with random data: you can have tests that sometimes fail. In such cases, augmenting a property-based test with an example-based test can be quite useful.

The fix is simple of course:

public static string MD5(string input)
{
    if (input == "foo")
    {
        return "acbd18db4cc2f85cedef654fccc4a4d8";
    }
    if (input == "bar")
    {
        return "37b51d194a7513e45b56f6524f2d51f2";
    }
    if (input == "baz")
    {
        return "73feffa4b7f6bb68e44cf984c85f6e88";
    }
    if (input == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
    {
        return "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
    }
    
    return "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
}

Let’s move on to our last property.

Property 5: similar inputs have non-similar hashes

Our last property states a very important property of MD5 hashes: similar inputs should not return similar hashes. For example, the hash for the string "hello" should be completely different from the hash for the string "hello1".

To write a test for this property, we have to define how we calculate the “difference” between two strings. In this case, we’ll just use a simple algorithm that counts the number of positions where the strings have different characters. Obviously, this simple algorithm would normally not suffice, but it works for our purposes. Note that for previty we’ll omit the actual implementation.

The property test looks like this:

[Property]
public void MD5WithSimilarInputsInputsDoesNotReturnSimilarHashes(string input, char addition)
{
    var similar = input + addition;
    var hash1 = MD5(input);
    var hash2 = MD5(similar);
    var difference = Difference(hash1, hash2);
    Assert.InRange(difference, 5, 32);
}

In this test, we let FsCheck generate two input values: the input value and a character to append to the input value. We then calculate the hashes for the original and modified input, calculate the difference and verify that the difference is at least 5 characters (which is was chosen arbitrarily).

In we run our test again, it fails:

MD5WithSimilarInputsInputsDoesNotReturnSimilarHashes [FAIL]

    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 1 test (2 shrinks) (StdGen (1928700473,296121700)):
    Original:
    ("D", 'F')
    Shrunk:
    ("", 'a')

    ---- Assert.InRange() Failure
    Range:  (5 - 32)
    Actual: 0

Modifying our implementation is still possible, but it becomes increasingly harder. At this point, we’ll give in and correctly implement the MD5 algorithm:

public static string MD5(string input)
{
    using (var md5 = System.Security.Cryptography.MD5.Create()) 
    {
        var inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
        var hash = md5.ComputeHash(inputBytes);

        var sb = new StringBuilder();

        for (var i = 0; i < hash.Length; i++)
        {
            sb.Append(hash[i].ToString("x2"));
        }

        return sb.ToString();    
    }
}

Let’s run all our test to verify they all pass:

MD5WithSimilarInputsInputsDoesNotReturnSimilarHashes [FAIL]
      FsCheck.Xunit.PropertyFailedException : 
      Falsifiable, after 13 tests (1 shrink) (StdGen (1520259769,296121702)):
      Original:
      (null, 'm')
      Shrunk:
      (null, 'a')
      
    ---- System.ArgumentNullException : String reference not set to an instance of a String.

MD5ReturnsStringWithOnlyAlphaNumericCharacters [FAIL]
      FsCheck.Xunit.PropertyFailedException : 
      Falsifiable, after 8 tests (0 shrinks) (StdGen (1521993499,296121702)):
      Original:
      <null>
      
    ---- System.ArgumentNullException : String reference not set to an instance of a String.

MD5ReturnsStringDifferentFromInput [FAIL]
      FsCheck.Xunit.PropertyFailedException : 
      Falsifiable, after 4 tests (0 shrinks) (StdGen (1522075879,296121702)):
      Original:
      <null>
      
    ---- System.ArgumentNullException : String reference not set to an instance of a String.

MD5ReturnsSameHashForSameInput [FAIL]
    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 2 tests (0 shrinks) (StdGen (1522087989,296121702)):
    Original:
    <null>
      
    ---- System.ArgumentNullException : String reference not set to an instance of a String.

MD5ReturnsStringOfCorrectLength [FAIL]
    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 28 tests (0 shrinks) (StdGen (1522097819,296121702)):
    Original:
    <null>
      
    ---- System.ArgumentNullException : String reference not set to an instance of a String.

Oh no, by correctly implementing the MD5 method, we made all our property-based tests fail! What happened? Well, previously, we correctly handled null input values in our implementation, but we don’t anymore. If we think about it, null can be consider an invalid input value and throwing an ArgumentNullException is thus perfectly reasonable. We could write an example-based test to verify this behavior:

[Fact]
public void MD5WithNullInputThrowsArgumentNullException()
{
    Assert.Throws<ArgumentNullException>(() => MD5(null));
}

To fix our failing property-based tests, we should rephrase our properties to state that they only hold for valid (non-null) input values. Our next step is to make FsCheck only generate valid, non-null input values.

Customizing input value generation

FsCheck generates values through a concept known as arbitraries. To fix our failing property tests, we’ll define a function that returns a Arbitrary<string>, which FsCheck will use to generate strings with. Our custom Arbitrary<string> instance can generate any string, expect the null string.

To define our custom arbitrary, we create a class with a static method that returns an Arbitrary<string> instance:

public static class NonNullStringArbitrary
{
    public static Arbitrary<string> Strings()
    {
        return Arb.Default.String().Filter(x => x != null);
    }
} 

The Strings() method returns a modified Arbitrary<string> instance that filters out the null string. Note that the name of the method is arbitrary (pun intended), FsCheck will ignore the name and only look at the return type.

We can then use our custom arbitrary in our tests through the Arbitrary property of the [Property] attribute:

[Property(Arbitrary = new[] { typeof(NonNullStringArbitrary) })]
public void MD5ReturnsStringOfCorrectLength(string input)
{
    var hash = MD5(input);
    Assert.Equal(32, hash.Length);
}

If we now run our test, our custom arbitrary is used, which means the null input will not be generated and all our implementation passes all property-based tests!

To make using our custom arbitrary easier, we can create an attribute that derives from PropertyAttribute that automatically sets the Arbitrary property to our custom arbitrary:

public class MD5PropertyAttribute : PropertyAttribute
{
    public MD5PropertyAttribute()
    {
        Arbitrary = new[] { typeof(NonNullStringArbitrary) };
    }
}

We can now use this attribute instead of using a [Property] attribute:

[MD5Property]
public void MD5ReturnsStringOfCorrectLength(string input)
{
    var hash = MD5(input);
    Assert.Equal(32, hash.Length);
}

Much better. The final step is to use this custom attribute on all our property-based tests. To verify we didn’t break anything, we run all tests one more time and thankfully, they all still pass!

Regular tests or property tests?

Now that we know how to write property-based tests, should we stop writing example-based tests? Well, probably not.

One problem with property-based tests is that it can be hard to identify a set of properties that completely cover the desired functionality. In those cases, it can be very useful to also define some example-based tests. This is what we did in our example. The five properties for which we wrote tests did not completely describe the MD5 algorithm though, which is why we also needed an additional parameterized example-based test to ensure the correctness of our implementation.

Furthermore, as example-based tests are less abstract, they are usually easier to understand. Defining some example-based tests in addition to your property-based tests, can thus help explain what you are testing.

Practicing

Propery-based testing is not easy to start with. Especially in the beginning, you’ll probably struggle to identify properties. A good way to practice your property-based testing skills, is to solve the Diamond kata using property-based testing.

If you’d like to learn more about FsCheck and property-based testing, check out the following links:

FsCheck is the most popular property-based framework for the .NET platform, but other platforms have their own implementation. The most well-known are QuickCheck (Haskell), and ScalaCheck (JVM), which both inspired FsCheck.

Conclusion

Although writing tests is important, the way you write your tests is equally important. To strengthen example-based tests, you should use as many inputs as possible. While multiple inputs are better than one input, property-based testing is even better as it works with any input value. It achieves this by randomly generating a large number of input values.

The random nature of property-based testing forces you to approach your tests differently. You don’t focus on specific use cases, but on the general properties of the code you want to test. This helps you better understand the requirements of the code you want to test. Testing with random inputs also makes hard-coding an implementation using pre-defined input values extremely hard. Furthermore, the clever way in which property-based testing frameworks can provide minimal counter-examples, really helps identifying and fixing issues in your implementation.

The main problem with property-based testing is that it can be hard to get started with. In particular, figuring out what properties to test can be quite hard. Furthermore, property-based tests can fail to completely describe all aspects of the implementation. In those cases, you should augment property-based tests with regular example-based tests.

 

In C# 6, the nameof operator allows you to retrieve the name of a variable, type or member.

Example

The following example shows how you can use the nameof operator to retrieve the name of a namespace, class, method, parameter, property, field or variable:

using System;
                    
public class Program
{
    private static DateTime Today = DateTime.Now;

    public string Name { get; set; }
    
    public static void Main(string[] args)
    {
        var localTime = DateTime.Now.ToLocalTime();
        var åçéñøûß = true;
        
        Console.WriteLine(nameof(localTime));     // "localTime"
        Console.WriteLine(nameof(åçéñøûß));       // "åçéñøûß"
        Console.WriteLine(nameof(args));          // "args"
        Console.WriteLine(nameof(System.IO));     // "IO"
        Console.WriteLine(nameof(Main));          // "Main"
        Console.WriteLine(nameof(Program));       // "Program"
        Console.WriteLine(nameof(Program.Today)); // "Today"
        Console.WriteLine(nameof(Program.Name));  // "Name"
    }
}

Restrictions

Although the nameof operator works with most language constructs, there are some restrictions. For example, you cannot use the nameof operator on open generic types or method return values:

using System;
using System.Collections.Generic;
                    
public class Program
{
    public static int Main()
    {   
        Console.WriteLine(nameof(List<>)); // Compile-time error
        Console.WriteLine(nameof(Main())); // Compile-time error
        
        return 0;
    }
}

Furthermore, if you apply it to a generic type, the generic type parameter will be ignored:

using System;
using System.Collections.Generic;
                    
public class Program
{
    public static void Main()
    {   
        Console.WriteLine(nameof(List<int>));  // "List"
        Console.WriteLine(nameof(List<bool>)); // "List"
    }
}

When to use?

So when should you use the nameof operator? The prime example are exceptions that take a parameter name as an argument. Consider the following code:

public static void DoSomething(string val)
{
    if (val == null) 
    {
        throw new ArgumentNullException("val"); 
    }   
}

If the val parameter is null, an ArgumentNullException is thrown with the parameter name ("val") as a string argument. However, if we do a rename refactoring of the val parameter, the "val" string will not be modified. This results in the wrong parameter name being passed to the ArgumentNullException:

public static void DoSomething(string input)
{
    if (input == null) 
    {
        throw new ArgumentNullException("val"); 
    }   
}

Instead of hard-coding the parameter name as a string, we should use the nameof operator :

public static void DoSomething(string val)
{
    if (val == null) 
    {
        throw new ArgumentNullException(nameof(val));   
    }   
}

As nameof(val) returns the string "val", the functionality is unchanged. However, as the nameof operator references the val parameter, a rename refactoring of that parameter also changes the nameof operator’s argument:

public static void DoSomething(string input)
{
    if (input == null) 
    {
        throw new ArgumentNullException(nameof(input)); 
    }   
}

This time, the refactoring did not break anything - the correct parameter name is still passed to the ArgumentNullException constructor.

Now we know how to use the nameof operator, let’s find out how it is implemented.

Under the hood

One way to find out how a C# feature is implemented, is by looking at the Common Intermediate Language (CIL) code the compiler generates. As a refresher, the following diagram describes how C# code is compiled:

Compilation of .NET code

There are basically two steps:

  1. Compiling C# code to CIL code. This is done by the C# compiler and happens at compile time.
  2. Compiling CIL code to native code. This is done by the CLR and happens at runtime.

As C# code is compiled to CIL code, examining the generated CIL code gives us insight in how C# features are implemented by the compiler. As an example, C# properties are compiled to plain getter and setter methods at the CIL level.

Let’s examine the CIL code that is generated for the aforementioned examples.

CIL code for string-based example

To view the CIL code generated for our string-based example, we first have to compile it. Just as a reminder, this is the C# code for our string-based example:

public static void DoSomething(string val)
{
    if (val == null) 
    {
        throw new ArgumentNullException("val"); 
    }   
}

To examine the CIL code the compiler generates, we compile our code. As this code is part of a console application, the C# compiler writes the CIL code to an executable.

To view the CIL code in the compiled executable, we can use a disassembler. We’ll use ildasm, which is a disassembler that comes pre-installed with Visual Studio. ildasm can be used both as a command-line and GUI tool, but we’ll use the command-line functionality.

To use ildasm on our executable, we open a Visual Studio Command Prompt. Then, we navigate to the directory that contains the executable we just compiled. Finally, we call ildasm with our executable’s name as its argument: ildasm nameof.exe /text.

This command will output all CIL code in the nameof.exe executable to the console (note: omit the /text modifier to open the ildasm GUI). Amongst the CIL code written to the console is the CIL code for our DoSomething method:

.method public hidebysig static void  DoSomething(string val) cil managed
{
  // Code size       22 (0x16)
  .maxstack  2
  .locals init ([0] bool V_0)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldnull
  IL_0003:  ceq
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  brfalse.s  IL_0015
  IL_0009:  nop
  IL_000a:  ldstr      "val"
  IL_000f:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string)
  IL_0014:  throw
  IL_0015:  ret
} // end of method Program::DoSomething

These instructions are the CIL representation of our C# code. If you are not familiar with CIL code, this may be a bit daunting. However, you don’t have to know all CIL instructions to understand what is happening. Let’s walk through each instruction to see what is happening.

  • .maxstack: set the maximum number of items on the stack to 2.
  • .locals: create a local variable of type bool at index 0.
  • IL_0000: do nothing. This instruction allows a breakpoint to be placed on a non-executable piece of code; in this case, the method’s opening curly brace.
  • IL_0001: push the val parameter value (the argument at index 0) on the stack.
  • IL_0002: push the null value on the stack.
  • IL_0003: pop the top two stack parameters from the stack, compare them for equality, and push the comparison result on the stack.
  • IL_0005: pop the top stack value (the equality comparison result) and store it in the local variable with index 0.
  • IL_0006: push the local variable at index 0 (the equality comparison result) on the stack.
  • IL_0007: pop top stack value (the equality comparison result) from stack. If this value is equal to 0 (false), jump to statement IL_0015 (end of method). If not, do nothing.
  • IL_0009: do nothing (allows breakpoint at opening curly brace inside if statement).
  • IL_000a: push the string "val" on the stack.
  • IL_000f: pop the top stack value (the "val" string) from the stack, and push a new ArgumentNullException instance on the stack by calling its constructor that takes the popped "val" string as its argument.
  • IL_0014: pop the ArgumentNullException from the stack and throw it.
  • IL_0015: return from the method.

While there is certainly more CIL code than there was C# code, it is not that hard to grasp what is happening if you are familiar with stacks.

Optimizing

There is something odd about the generated CIL instructions though. For example, instructions IL_0005 and IL_0006 seem to be redundant, as the first instruction pops a value from the stack which the latter instruction then immediately pushes back on the stack. Removing these instructions would not change the behavior of the code and would improve its performance. So how can we instruct the compiler to omit these redundant instructions?

Well, note that we built our code using the default Debug build configuration. If you build code using the default Debug build configuration, the C# compiler will not apply optimizations to the CIL being output. This leads to valid, but often inefficient CIL code. However, if we switch to the Release build configuration and compile our code again, the compiler will optimize the CIL code. Let’s see what the optimized CIL code for our example is.

CIL code for string-based example (optimized)

To see the optimized CIL code for our example, we select the Release build configuration and rebuild our code. This time, the generated CIL code looks remarkably different:

.method public hidebysig static void  DoSomething(string val) cil managed
{
  // Code size       15 (0xf)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_000e
  IL_0003:  ldstr      "val"
  IL_0008:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string)
  IL_000d:  throw
  IL_000e:  ret
} // end of method Example::DoSomething

This optimized CIL code does the following things:

  • .maxstack: set the maximum number of items on the stack to 8.
  • IL_0000: push the val parameter value (the argument at index 0) on the stack.
  • IL_0001: pop the top stack value (the val parameter value) from the stack. If this value is equal to true (which is the same as: not null), jump to statement IL_000e (end of method).
  • IL_0003: push the string "val" on the stack.
  • IL_0008: pop the top stack value (the "val" string) from the stack, and push a new ArgumentNullException instance on the stack by calling its constructor that takes the popped "val" string as its argument.
  • IL_000d: pop the ArgumentNullException from the stack and throw it.
  • IL_000e: return from the method.

As you can see, the functionality is still the same, but the number of CIL instructions has been drastically reduced. This not only leads to improved performance, but also makes it easier to see what is happening in the CIL code.

CIL code for nameof operator

Let’s see what CIL code is generated for our example that uses the nameof operator. The C# code that uses the nameof operator looks like this:

public static void DoSomething(string val)
{
    if (val == null) 
    {
        throw new ArgumentNullException(nameof(val));   
    }   
}

If we compile this code (using a Release build) and run ildasm again, the following optimized CIL is generated for the DoSomething method:

.method public hidebysig static void  DoSomething(string val) cil managed
{
  // Code size       15 (0xf)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_000e
  IL_0003:  ldstr      "val"
  IL_0008:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string)
  IL_000d:  throw
  IL_000e:  ret
} // end of method Example::DoSomething

Interestingly, the CIL generated for the nameof version is exactly the same as the string-based version! The nameof operator is thus just syntactic sugar for a plain CIL string - there is no nameof operator at the CIL level. This means that there is no runtime overhead to using the nameof operator over plain strings.

CIL string optimizations

As the nameof operator compiles to a plain string in CIL, do strings optimizations also apply when the nameof operator is used?

As an example of such an optimization, consider the following code:

public static void DoSomething(string val)
{
    Console.WriteLine ("Parameter name: " + "val");
}

Can you guess what CIL code will be generated for this C# code? If you figured there would be string concatenation, you’d be wrong. Let’s check the optimized CIL code the compiler outputs:

.method public static hidebysig default void DoSomething (string val)  cil managed 
{
    // Method begins at RVA 0x2058
    // Code size 11 (0xb)
    .maxstack 8
    IL_0000:  ldstr "Parameter name: val"
    IL_0005:  call void class [mscorlib]System.Console::WriteLine(string)
    IL_000a:  ret 
} //

You can clearly see that instead of using CIL instructions to concatenate the "Parameter name: " and "val" strings, the compiler just outputs the concatenated strings directly. Obviously, this compiler optimization saves both time and memory.

To find out if this optimization also applies when the nameof operator is used, we modify our example to use the nameof operator:

public static void DoSomething(string val)
{
    Console.WriteLine ("Parameter name: " + nameof(val));
}

We then recompile and inspect the generated CIL code:

.method public static hidebysig default void DoSomething (string val)  cil managed 
{
    // Method begins at RVA 0x2058
    // Code size 11 (0xb)
    .maxstack 8
    IL_0000:  ldstr "Parameter name: val"
    IL_0005:  call void class [mscorlib]System.Console::WriteLine(string)
    IL_000a:  ret 
} //

As the generated CIL is the same as that of our string-based example, we have verified that existing string optimizations also apply when the nameof operator is used.

Backwards compatibility

As we saw earlier, nameof operator calls are converted to strings in the generated CIL code; there is no nameof concept at the CIL level. This allows code that uses the nameof operator to run on older versions of the .NET framework, as far back as .NET framework 2.0 (and probably 1.0). The only restriction, of course, is that the compiler must be recent enough to know how to compile the nameof operator.

To verify this backwards compatibility, create a project that uses the nameof operator. Then, open the project properties page and change the Target framework to .NET Framework 2.0. At this point, you might get compile warnings for features not supported in .NET Framework 2.0. Remove all such features until the code compiles again. Note that the compiler did not complain about the nameof operator! Now recompile the application and run it. Everything should still work just fine.

Compiler

Previously we used a disassembler to see what CIL code was generated by the C# compiler. However, as the C# compiler (nicknamed Roslyn) is open-source, we can also examine its source code to find out how the nameof operator is compiled.

Preparing

To explore Roslyn’s source code, we first get a local copy of the Roslyn repository. We then follow the build instructions to build the master branch. Once the build script has finished (this can take a while), we open the Compilers.sln solution. In the CSharp solution folder, we then select the csc project as the startup project. This console application project builds csc.exe, the command-line version of the C# compiler. To see how csc.exe compiles C# code that uses the nameof operator, we specify the following command line arguments on the Debug tab of the csc project’s properties page:

"nameof.cs" /out:"nameof.exe" /optimize

The first argument is our C# source file that contains the nameof operator code. The compiler will compile this file and write the CIL output to the file specified in the "/out" argument. Finally, the "/optimize" flag will cause the compiler to emit optimized CIL code (note: this flag is set when doing a Release build).

Now, if we press F5 to debug the csc project, the C# compiler will compile the nameof.cs file and write the results to nameof.exe. At this point, we haven’t set any breakpoints, we just let the compiler finish. After finishing, the compiler has created the nameof.exe executable. Using ildasm, we can see that csc.exe has generated the exact same CIL code as we saw in our previous examples.

Compilation pipeline

Before we dive head-first into Roslyn’s source code to see how the nameof operator is compiled, let’s see what the Roslyn compilation pipeline looks like:

Compilation of .NET code in Roslyn

This diagram shows that Roslyn compiles C# source code in various stages. In the next sections, we’ll look at what happens to our nameof source code in each phase.

First steps

We’ll start exploring Roslyn’s internals by debugging the csc project while it tries to compile our C# code. This time though, we set a breakpoint in the project’s Main method (located in the Program.cs file). Once the breakpoint hits, we use Step Into (F11) to dig deeper and deeper into Roslyn’s source code.

At first, the compiler does some pretty mundane stuff, like parsing command-line arguments. However, things start to get interesting when we arrive at the RunCore() method in the CommonCompiler class. This method implements the aforementioned compilation pipeline. Let’s see how the nameof operator is processed in the various phases.

Parse phase

In this phase, the compiler uses the ParseFile() method to parse our source code into a syntax tree. The following is a simplified version of the syntax tree the compiler creates for our C# code:

NamespaceDeclaration   
└── ClassDeclaration   
    └── MethodDeclaration
        └── IfStatement
            └── ThrowStatement
                └── ObjectCreationExpression
                    └── ArgumentList

Clearly, the syntax tree hierarchy directly corresponds to the hierarchy in our C# code. For example, the root node is the namespace declaration, which has a class declaration as its child.

Each syntax element in the syntax tree has a number of properties, such as a reference to its parent. Some syntax elements also have an identifier property, which is a string that contains the name of that syntax element. For example the ClassDeclaration describing the Program class has its identifier property set to "Program".

Let’s zoom in on the syntax tree for the C# code that uses the nameof operator: the new ArgumentNullException(nameof(val)) expression. This time, we’ll also show the syntax element’s identifier in the tree (if it has one):

ObjectCreationExpression
├── NewKeyword
├── IdentifierName ("ArgumentNullException")
└── ArgumentList
    └── Argument
        └── InvocationExpression
            ├── IdentifierName ("nameof")
            └── ArgumentList
                └── Argument
                    └── IdentifierName ("val")

At the root of this part of the syntax tree is the ObjectCreationExpression. The object creation expression has three parts:

  1. The new keyword.
  2. The identifier name ("ArgumentNullException").
  3. The argument list.

The argument list subtree has a single child: an Argument that describes an InvocationExpression, which represents the nameof(val) argument. The syntax tree for this element has two parts:

  1. The identifier of the method to be called ("nameof").
  2. The argument list, which contains a single argument that refers to the "val" identifier.

We can see that the the nameof operator is treated as a regular method call with a single argument. That argument is not a string though, but an IdentifierName element, which is a reference to another element with a matching identifier.

We know that in our code the val argument of the nameof call refers to the DoSomething() method’s val parameter. Therefore, we expect the syntax tree of the DoSomething() method to have a parameter syntax element with its identifier set to "val", which it does:

MethodDeclaration ("DoSomething")
└── ParameterList
|   └── Parameter ("val")
└── Block
    └── IfStatement
        └── ...    

In the next phase, the compiler will match these identifiers.

Declaration and bind phase

In the declaration and bind phase, identifiers are bound to symbols. In the parse phase, we saw that the nameof call was described by an InvocationExpression. The binding of this expression is done in the BindInvocationExpression() method.

The BindInvocationExpression() method starts by calling TryBindNameofOperator(). As our InvocationExpression indeed contains a nameof operator, the BindNameofOperatorInternal() method is called to handle the binding of the nameof operator.

The following, abbreviated code shows the BindNameofOperatorInternal() method:

private BoundExpression BindNameofOperatorInternal(InvocationExpressionSyntax node, DiagnosticBag diagnostics)
{
    var argument = node.ArgumentList.Arguments[0].Expression;
    
    string name = "";
    CheckSyntaxForNameofArgument(argument, out name, diagnostics);

    ...

    return new BoundNameOfOperator(node, boundArgument, ConstantValue.Create(name), Compilation.GetSpecialType(SpecialType.System_String));
}

First, the InvocationExpressionSyntax element’s single argument is retrieved, which is of type IdentifierNameSyntax. Then the CheckSyntaxForNameofArgument() method sets the name parameter to the identifier of the IdentifierNameSyntax element. In our example, the name variable is set to "val".

Finally, the method returns a BoundNameOfOperator instance, which is the bound symbol representation of the InvocationExpressionSyntax. Note that the third constructor argument is a call to ConstantValue.Create(), with the identifier name ("val") as its argument. This method is implemented as follows:

public static ConstantValue Create(string value)
{
    if (value == null)
    {
        return Null;
    }

    return new ConstantValueString(value);
}      

By passing a ConstantValue instance to the BoundNameOfOperator constructor, we indicate that the BoundNameOfOperator symbol can also be represented by a constant value. The importance of this will become clear later.

Lowering

After the binding has finished, there is one thing left to: lowering. In the process of lowering, complex semantic constructs are rewritten in terms of simpler ones. For example, each lock call is lowered to a try/finally block that uses Monitor.Enter and Monitor.Exit.

As it turns out, lowering also applies to the nameof operator. The VisitExpressionImpl() method (in the LocalRewriter class) is called for each BoundExpression instance, including derived classes such as the BoundNameOfOperator class. It looks like this:

private BoundExpression VisitExpressionImpl(BoundExpression node)
{
    ConstantValue constantValue = node.ConstantValue;
    if (constantValue != null)
    {
        ...
        
        return MakeLiteral(node.Syntax, constantValue, type);
    }

    ...
}

This method takes a BoundExpression and also returns a BoundExpression. However, if the ConstantValue property is not null, the returned BoundExpression is of type BoundLiteral, as returned by the MakeLiteral() method:

private BoundExpression MakeLiteral(CSharpSyntaxNode syntax, ConstantValue constantValue, TypeSymbol type, BoundLiteral oldNodeOpt = null)
{
    ...
    
    return new BoundLiteral(syntax, constantValue, type, hasErrors: constantValue.IsBad);
}

Therefore, when the VisitExpressionImpl() method is called to apply lowering to bound expressions, each BoundNameOfOperator instance is replaced by a BoundLiteral instance.

Emit phase

The last phase is the emit phase. Here, the bound symbols created in the previous phase are written to file as CIL code.

From our nameof viewpoint, things start getting interesting when the EmitExpression() method is called with the BoundLiteral instance (representing our nameof call) as its argument.

As we saw earlier, the BoundLiteral instance had its ConstantValue property set to an instance of ConstantValueString (containing the string "val"). This is important because normally, the compiler evaluates an expression to determine what code to emit. However, if the compiler find that an expression’s ConstantValue property is not null, it will skip evaluating the expression but instead emit the constant value.

In the EmitExpression() method, you can clearly see this behavior:

private void EmitExpression(BoundExpression expression, bool used)
{
    ...

    var constantValue = expression.ConstantValue;
    if (constantValue != null)
    {
        ...

        EmitConstantExpression(expression.Type, constantValue, used, expression.Syntax);
    }

    ...
}

As the ConstantValue property of our BoundLiteral instance is not null, the EmitConstantExpression() method is called, which in turn calls EmitConstantValue():

internal void EmitConstantValue(ConstantValue value)
{
    ConstantValueTypeDiscriminator discriminator = value.Discriminator;

    switch (discriminator)
    {
        ...
        
        case ConstantValueTypeDiscriminator.String:
            EmitStringConstant(value.StringValue);
            break;
        
        ...
    }
}

As the ConstantValue property of the BoundLiteral instance is of type ConstantValueString, the EmitStringConstant() method is called with the StringValue property as its argument. Note that for our ConstantValueString instance, the StringValue property returns the string "val".

Having arrived at the EmitStringConstant() method, we are finally able to see how the CIL code for the nameof operator is emitted:

internal void EmitStringConstant(string value)
{
    if (value == null)
    {
        EmitNullConstant();
    }
    else
    {
        EmitOpCode(ILOpCode.Ldstr);
        EmitToken(value);
    }
}

With value not being null, the else branch is executed. First, the CIL code for the ldstr opcode is emitted. Then, the "val" string token is emitted. This will output the CIL code we were expecting:

IL_0003:  ldstr      "val"

Our tour through Roslyn’s internals has shown us how the nameof operator has been parsed to syntax elements, then bound to symbols and finally emitted as CIL code.

Conclusion

The nameof operator is easy to understand and use. While its use is limited, it does make your code more robust. As the nameof operator is just syntactic sugar, there is no runtime performance impact. Furthermore, existing string optimizations also apply to the nameof operator and the compiled code runs on older versions of the .NET framework.

To find out how the nameof operator was implemented, we used ildasm to examine the generated CIL code and stepped through Roslyn’s internals to see how the compiler generated the CIL code for the nameof operator.

 

This is the seventh and last in a series of posts that discuss the steps taken to publish our library. In our previous post, we added TypeScript support to our library. This post will show how we added our library to a Content Delivery Network.

Content Delivery Network

A Content Delivery Network (CDN) is a network of servers that deliver content based on the geographic location of the user. In other words, when you request content from a CDN, the server geographically nearest to you will send the content. The main advantage of this is speed, but another is reliability. If one server goes down, another will automatically take over. Another advantage is that your own servers use less bandwith, very useful to cut down on bandwidth costs.

Some well-known CDN providers are Akamai, CloudFlare and Amazon CloudFront. While most CDN providers are paid services, some offer basic functionality for free.

Hosted JavaScript libraries

For developers, CDN’s are often used to serve JavaScript libraries. For example, jQuery has its own CDN at code.jquery.com which hosts jQuery, jQueryUI and several others. For a larger list of libraries, you can use the Microsoft Ajax Content Delivery Network or Google’s hosted libraries. However, if they don’t host the library you want to use, you’re out of luck, right? Enter cdnjs.

cdnjs

cdnjs is a CDN that hosts many JavaScript libraries, a lot more than the aforementioned CDN’s. The great thing about cdnjs is that if they don’t already host the library you want, you can add it yourelf! Let’s do that for our library.

First we fork the cdnjs repository. In that fork, we create a new directory with our library’s name in the ajax/libs folder. Within the created folder, we add a package.json file using cdnjs’s custom package.json format:

{
  "filename": "knockout-paging.min.js",
  "name": "knockout-paging",
  "version": "0.3.0",
  "description": "Knockout paging",
  "keywords": ["knockout", "paging"],
  "homepage": "https://github.com/ErikSchierboom/knockout-paging",
  "dependencies": { 
    "knockout": "^3.2.0"
  }
}

Although the format is similar to the regular package.json format, the "filename" field is new and required.

At this point, we can start adding the files we want cdnjs to serve. To do so, we create a subfolder for the version of our library which files we want to host. Within that folder, we then put all files we want to be hosted. For our library, this gives us the following files and folders:

ajax
└── libs
    ├── ...  
    └── knockout_paging
        ├── 0.3.0
        |   ├── knockout-paging.js
        |   └── knockout-paging.min.js
        └── package.json

Note that we distribute both the regular and minified versions of our library.

The final step is to commit our changes and send it in a pull request. Once accepted, our library will be available on cdnjs. The full URL for version 0.3.0 of our library’s knockout-paging.min.js file is: https://cdnjs.cloudflare.com/ajax/libs/knockout-paging/0.3.0/knockout-paging.min.js.

Note that most of the steps to create the correct folders and files can also be done automatically using the cdnjs-importer tool.

Updating versions

To add a new version of a library, you used to create a new subfolder with that version’s file(s). Then, you’d commit and send a new pull request. However, the preferred method nowadays is to enable auto-updating. There are two ways libraries can be updated automatically:

  1. Through NPM.
  2. Through Git.

For our library, we’ll use Git. To enable auto-updating from Git, we add the following to our cdnjs library’s package.json file:

"autoupdate": {
  "source": "git",
  "target": "git://github.com/ErikSchierboom/knockout-paging.git",
  "basePath": "/dist/",
  "files": [
    "knockout-paging.min.js",
    "knockout-paging.js"
  ]
}

This will instruct cdnjs to periodically check for new versions at the specified Git repository. It does this by checking the Git tags, which should use semantic versioning. Now, if we commit the updated package.json file and submit it to cdnjs, new versions of our library wil automatically be added. Of course, old versions will remain available.

Conclusion

We made our library available through a CDN by adding it to cdnjs, which was quite simple. Furthermore, we also configured cdnjs to automatically make new versions of our library available through the use of its auto-updating feature.

And that brings us to the end of the last of our series of posts on how we published our knockout-paging plugin. Making our library available through a CDN was easy.