Over the past few weeks we’ve been busy laying the groundwork needed to support migrating Stack Overflow to ASP.NET Core.
Our roadmap for the migration extends out to later this year, but we’ve been tackling a lot of pre-requisites:
- Porting most of our open source and internal libraries to netstandard20 so that they can be used readily across net462 and netcoreapp2x projects
- Migrating from Linq2Sql to EF Core (and bringing down most of the Stack Exchange Network in the process)
- Using the built-in
ManagedWebSocketand Kestrel instead of NetGain for our web socket server.
- Tweaking how we do localization to move from a precompilation model to a runtime-based model.
We’ve also started some greenfield projects that aren’t public-facing but that support critical parts of our business. That’s helped us put together a list of ‘standard’ libraries that we’ll likely use across some of our other applications when we migrate them to .NET Core. One of those has been NFig - a way to manage configuration settings for our applications.
NFig - Some Background
NFig was originally written by Bret Copeland and Bryan Ross for the ad server that powers our job ads. After a while we started using it in other applications such as Stack Overflow Talent and Stack Overflow Jobs as well as a few internal projects. It works by allowing us to define our configuration settings in code and then allowing us to change those settings at runtime by storing overrides in a backing store. Here’s an example configuration class:
Everywhere that we want to use the Whizzbang feature we can check this flag:
NFig surfaces this is in configuration views provided by the NFig.UI package:
We can override this at runtime for a specific data centre or everywhere and that value will be immediately reflected the next time it’s accessed by the code:
This is great and works extremely well - it allows us to do things like deploy features behind a flag and then switch them on at runtime without having to re-deploy the application.
.NET Core & NFig
.NET Core comes with its own set of APIs (known as the
Options pattern) for providing upto date configuration settings. An
IOptions implementation is configured based upon code like this in an application’s startup:
This uses the configuration subsystem to compose the options using anything from a JSON file to a SQL Server or a secure storage mechanism for things like secrets.
Ideally we’d like using NFig to be as “native” as possible, so, our first task is to make sure settings provided by NFig can be resolved as
IOptions implementations and to configure the backing store for NFig. We can do that in our application startup:
You’ll notice that we’ve specified a concrete type for our settings (
Settings from before) and also enum types for the
DataCentre. The latter two allow our application to specify different settings per data centre and tier.
Now, instead of using a static reference to
Settings.FeatureFlags.EnableWhizzbang, we can inject an
IOptions<FeatureFlagSettings> into our consuming class:
Exposing NFig UI
We’ve also added middleware that handles the rendering of the NFig UI views. This is exposed as a static class that handles the HTTP request directly and can be called from an MVC controller or by being hooked up directly during application startup. This allows you to use whatever security model and routing mechanism yohr application is currently using:
We can even go so far as composing our settings classes from other configuration sources. NFig doesn’t currently support encryption of secrets (although it’s being developed for v3) so it isn’t a good idea to store passwords or other secret information within it. In .NET Core, however, we can use the Options framework to compose our settings classes from multiple places; we can fetch our secrets from somewhere secure, using .NET’s configuration APIs. Here’s an example using user secrets (note: user secrets are really intended for use at development time, this is not production-worthy code!):
Some Implementation Details
You might be asking why we didn’t implement .NET Core’s
IConfigurationProvider interfaces and integrate at a lower level into the configuration subsystem… We explicitly decided not to do so because NFig manages the lifetime and persistence of settings itself. If we implemented the configuration interfaces we’d need to expose our configuration values as an
IDictionary<string, string> and allow .NET Core to manage the binding of those values to strongly-typed classes. NFig already does the following for us:
- it instantiates the settings classes and re-creates them whenever they change
- it marshals configuration values from strings to their actual types and back again
In short, we really don’t need to hook into the framework at such a low-level and hooking into
IOptions<T> is sufficient for our needs.
That said, we’ve had to make some adjustments to how the Options framework works by default by implementing some of the interfaces made available to us.
There are two kinds of options that you can inject into your classes -
These are registered as a singleton so the
Value property always resolves to the value that is initially read from a configuration source.
These are registered as a scoped dependency so the
Value property is always the most current value and retains that value throughout the scope (e.g. an HTTP request).
By default these are both implemented by
OptionsFactory<T> in .NET Core and as you can see from the code it tends to be a bit allocatey; an instance of
OptionsCache<T> is created which contains a
ConcurrentDictionary<string, T> containing a lazily initialized mapping of option names to their underlying objects. When using
IOptionsSnapshot<T> that starts to add up because you get a new instance per request. We try to minimise allocations where possible here at Stack Overflow (GC hurts at scale!) so we override the default lifetime for NFig-provided settings to be a singleton that accesses the most recent value of a setting all the time by using the value tracked within an
IOptionsMonitor<T>. This eliminates the allocations to be once per app per option type. To keep the options framework informed of when our NFig settings change we register a
IChangeTokenSource<T> which notifies any listeners (including the
IOptionsMonitor<T> mentioned above) of those changes.