A Source Generator for your appsettings.json

Recently Microsoft introduced a new feature for .NET called ‘Source Generators’. It’s still in preview and will (probably) be released with .NET Core 5.

Source Generators seem to excite a lot of people. So what are Source Generators exactly? A short answer could be: Source Generators can add code during compilation-time. If thats not satisfying, check out the official blog post from Microsoft. Or check one of the samples described in this blog post.

I decided to give it a go and wanted to write a Source Generator that generates POCO’s for your appsettings.json. .NET core introduced stronly typed configuration, but it still required one to write the classes manually. E.g. this piece of config requires this class:

  "RemoteService": {
    "BaseUrl": "https://url/to/service",
    "DisplayName": "My Service"
  },
    public class RemoteService
    {
        public string BaseUrl { get; set; }
        public string DisplayName { get; set; }
    }

This seemed ‘automatable’, so off we go. Our objective is to write a Source Generator that generates these classes for us. Whenever we add a property in the appsettings.json, we want our configuration-pojo’s to update.

In order to write a Source Generator you need to have Visual Studio 2019. (This will change in the next .NET 5 release)

We start by implementing an interface called ISourceGenerator. This interface has 2 methods, but we are only interested in the Execute method:

    [Generator]
    public class GeneratedConfigClasses : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
           // Implementation
        }
    }

In the Execute method we have access to the compilation context which allows us to add code to the current compilation. The compilation can be visualized as follows: (borrowed from https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/):

The real implementation can be found on GitHub. Some things to note:

  • Your generator must be decorated with the [Generator] attribute
  • The actual source-code is added to the compilation with this method: context.AddSource("MyAppConfig", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));

We want our generator to generate code for appsettings, but also for appsettings.Development.json. And possibly for more files. So I implemented a merging-strategy that merges appsettings files. If 2 config-files have the same string/boolean/int key, it’s easy to know which one to choose (it doesn’t matter ;)). But if there are 2 settings with nested settings we choose the setting with the most nested-settings. It’s very basic, but it seems to work ok.

In order for our Source Generator to know which appsettings files to use, we have to specify this when registring our Source Generator. In your target projectfile (.csproj) you have to add an ItemGroup with AdditionalFiles:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>    
    <ProjectReference Include="..\ConfigGenerator\ConfigGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssemly="false" />
    <PackageReference Include="System.Text.Json" Version="5.0.0-rc.2.20475.5" />
  </ItemGroup>

  <ItemGroup>
    <AdditionalFiles Include="appsettings.json" />
    <AdditionalFiles Include="appsettings.Development.json" />
  </ItemGroup>

</Project>

Our SourceGenerator can read these files from the context:

foreach (var configFile in context.AdditionalFiles)
{
   //...
}

After reading and merging the JSON files, we deserialize it into a Dictionary<string, object> and then generate the actual source code. See the source in GitHub for the details. The last line of our Generator adds the generated code to the compilation:

context.AddSource("MyAppConfig", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));

When developing a Source Generator you have to restart Visual Studio to get rid of the red squiggles and some other undefined errors. But once a generator is registered it works pretty ok. As soon as you add a property in your appsettings.json our Source Generator kicks in and generates the new sources. The are almost immediately available in Visual Studio:

The generated configuration classes are available in the ApplicationConfig namespace. The main-class is MyAppConfig. The nested classes are in a different namespace: ApplicationConfigurationSections.

After updating / creating your appsettings.json file you can register your configuration in your Startup with the following code (it could require a restart before intellisense kicks in):

 services.Configure<ApplicationConfig.MyAppConfig>(Configuration);
 services.Configure<ApplicationConfigurationSections.Logging>(Configuration.GetSection(nameof(ApplicationConfigurationSections.Logging)));

You can find the full source code on GitHub.

Some things to take into account:

  • This is an example and I’m not sure yet how useful this is. Consider it pre-alpha.
  • It supports int’s, booleans, arrays and strings (and nested objects)
  • Source Generators are in preview. Things may change.
  • I might turn it into a NuGet package if it turns out to be useful

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: