Building strongly typed application configuration utility with Roslyn

In this post we will have a look at how, with just several lines of Roslyn code, you can build an extremely cool and powerful utility – a library allowing you to provide configuration for your application as a strongly typed C# script file.

This post was inspired by the ConfigR library, which provides this type of functionality through scriptcs (I also blogged about ConfigR before).

We will, however, deal with marshalling configuration data between the C# configuration and the parent app differently than ConfigR does.

Code based configuration

The idea behind code based configuration is quite simple. Instead of relying on static XML/JSON files (think web.config, app.config or some custom parsed JSONs) for your configuration, like we always have had in .NET, we can use C# script to provide any configuration settings for your application.

This is very cool for the following reasons:

– Scripted configuration means it’s 100% valid C# code. As a result, it becomes strongly typed for free, as you are no longer constrained by the limits of serialization and deserialization. You just write your config file with C# as a C# script, against any types and assemblies (including those coming from your “main app”), and then marshal that back to the “main app”. For example if you instantiate an array in the C# configuration script, the “main app” sees the very same instance of an array. So no more error prone casting or unboxing based on magic strings.

– Configuration is no longer just static container for data. It can now make logical decisions about configuration settings itself – as it is a fully featured C# code after all. So you can use things like switch or if statements – or any other flow control mechanisms – from within configuration. Configuration, since it’s C# driven, can at runtime decide on proper config settings based on, for example, environment variables or process settings

– At the end of the day, what you want from your configuration, is being able to edit it and have the application pick up the changes without you needing to recompile it – and this is exactly the case here. Even though we are dealing with C# code, it’s in the form of a script, so by forcing the app to reload it’s configuration (whether it’s by restarting it, or by watching for file changes or in any other way), the updated configuration script will be re-executed and the new configuration data will be exposed to the application.

Overall this gives us a brilliant use case for C# scripting.

Building a simple Roslyn based configuration provider

The easiest way to build this is to start off with a POCO representing some kind of configuration data for our application.

Let’s take a simple class like the one shown below:

You can easily imagine hydrating the above object and its Number and Text properties with data that comes from AppSettings inside web.config/app.config and is read at runtime using the good old ConfigurationManager. Of course this is also very limiting and error prone.

Let’s now use Roslyn to hydrate the same AppConfiguration object using a C# script as an alternative.

Firstly, let’s add the Roslyn C# scripting package.

Secondly, a little background.

Roslyn scripting APIs have a concept of a host type (also known as globals type). It allows us to seed the C# scripting with an instance of an object, and all of its public members are globally available in the context of the script.

This technique is used by the scriptcs project, which exposes for example a global Require method, which is effectively just defined on the host object). It’s also used by ConfigR, which exposes a global Add method.

In our case we can simply feed an instance of our configuration POCO (AppConfiguration) into the script as a host object, and let the script set the public properties – Number and Text. This makes marshalling the data between the script configuration and the “main app” extremely easy.

Since the instance of the POCO exists outside of the context of the script, once the script executes, and hydrates the properties of our configuration object, we can simply read them and consume however we wish – without any casting or any serialization.

To illustrate this, let’s write a simple class that will take care of seeding a configuration into a C# script as a host type and executing the C# script.

It seems like quite a bit of code, but in reality that’s the entire functionality right here, and it’s really simple. Plus a little bit of extra sugar on top too.

It’s a little builder that allows us to configure what script (by default config.csx) we want to execute, from which path should the script be executed (by default the base directory of the current app domain) and which assemblies and namespaces to import into the context of the script (again several default here for example mscorlib and System.Core.dll).

The configuration POCO is represented by TConfig which can be anything – as long the caller passes its instance in or as long as as it has a parameterless constructor.

Inside the Create method, which is how the caller will read the configuration, we will read the C# script code, create Roslyn’s ScriptOptions based on the configured settings, and invoke the C# script.

When it executes, it will get a chance to set any public members of TConfig which is then returned back to the caller, who can now use it as application configuration object.

So let’s add a C# script file, called config.csx, which I can make as part of the project, and mark as “Build Action: None” and “Copy to Output Directory: Copy Always”.

As you can see I can interact with any public members of the AppConfiguration POCO and set them to whatever I wish. I can also perform calculations or any other complex operations I’d like to use C# for.

So now to use this in a real application, against our AppConfiguration, all I need is the following:

Remember, I don’t even need to point it to a specific CSX file, as long the file has the default name config.csx.

The above script prints, as expected:

Now, to make it even more interesting, I can use some complex types. Let’s consider the following application configuration POCO:

Let’s use this configuration script:

So we are setting the relevant properties of the configuration object using strong typing – Uri, DataTarget and System.Int32.

Now in order to use this, all we need is the following in our application:

And this prints the following:

That’s it! Pretty awesome piece of functionality for some 50 lines of Roslyn code. All the code from this post is available at Github.

Be Sociable, Share!