Using the configuration-binding source generator – Options, Settings, and Configuration

.NET 8 introduces a configuration-binding source generator that provides an alternative to the default reflection-based implementation. In simple terms, the name of the options class properties and the settings keys are now hard-coded, accelerating the configuration retrieval.

Beware, the settings keys are case-sensitive and map one-on-one with the C# class property name, unlike the non-generated code.

Web applications using Native AOT deployment (ahead-of-time compilation to native code) or trimming self-contained deployments to ship only the bits in use now leverage this option by default.

The native AOT deployment model compiles the code to a single runtime environment like Windows x64. It does not need the just-in-time (JIT) compiler since the code is already compiled to the native version of the targetted environment. AOT deployments are self-contained and do not need the .NET runtime to work.

We can use the EnableConfigurationBindingGenerator property in your csproj file to manually activate or deactivate the generator:

<PropertyGroup>
  <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

Now that the generator is enabled, let’ see how this works. The generator looks for a few options, including the Configure and Bind methods. It then generates the binding code.

Project – ConfigurationGenerators: Part 1

In this first part of the project, we create an options class and register it with the IoC container to consume it through an API endpoint.We use the following options class:

namespace ConfigurationGenerators;
public class MyOptions
{
    public string?
Name { get; set; }
}

In the Program.cs file, we can use the source generator like this:

builder.Services
    .AddOptions<MyOptions>()
    .BindConfiguration(“MyOptions”)
;

As you may have noticed, the preceding code is the same as we used before and does what you expect it to do, but the new source generator generates the code under the hood—no functional or usage changes.Let’s explore another source generator next.

Using the options validation source generator

.NET 8 introduces the options validation source generator, which generates the validation code based on data annotations. The idea is similar to the configuration-binding source generator but for the validation code.To leverage the validation generator, we must add a reference on the Microsoft.Extensions.Options.DataAnnotations package.Afterward, we must:

  1. Create an empty validator class.
  2. Ensure the class is partial.
  3. Implement the IValidateOptions<TOptions> interface (but not the methods).
  4. Decorate the validator class with the [OptionsValidator] attribute.
  5. Register the validator class with the container.

This procedure sounds complicated but is way simpler in code; let’s look at that now.

Project – ConfigurationGenerators: Part 2

In this second part of the project, we continue to build on the previous pieces and add validation to our MyOptions class. Of course, we also want to test the new source generator.Here’s the updated MyOptions class:

using System.ComponentModel.DataAnnotations;
namespace ConfigurationGenerators;
public class MyOptions
{
    [Required]
    public string?
Name { get; set; }
}

The highlighted line represents the changes. We want to ensure the Name property is not empty.Now that we updated our options class, let’s create the following validator class:

using Microsoft.Extensions.Options;
namespace ConfigurationGenerators;
[OptionsValidator]
public partial class MyOptionsValidator : IValidateOptions<MyOptions>
{
}

The preceding code is an empty shell that prepares the class for the code generator. The [OptionsValidator] attribute represents the generator hook (a.k.a. that’s the flag the generator is looking for). And with this code, we are done with steps 1 to 4; simpler than English, right?Now, for the last step, we register our validator like normal:

builder.Services.AddSingleton<IValidateOptions<MyOptions>, MyOptionsValidator>();

To test this out, let’s add a valid named options instance bound to the following configuration section in the appsettings.json file:

{
  “MyOptions”: {
    “Name”: “Options name”
  }
}

Here’s how we bind it in the Program.cs file:

builder.Services
    .AddOptions<MyOptions>(“valid”)
    .BindConfiguration(“MyOptions”)
    .ValidateOnStart()
;

The preceding code registers the valid named options, binds it to the configuration section MyOptions, and validates it when the application starts.

Other ways to register the named options also work. I used this one for convenience purposes only.

If we were to inspect the content of the options at runtime, it would be what we expect; nothing is different from what we explored throughout the chapter:

{
  “name”: “Options name”
}

At this point, the program should start.Next, to test this out, let’s add another named options class, but an invalid one this time. We won’t change anything in the appsettings.json file, and add the following registration code:

builder.Services
    .AddOptions<MyOptions>(“invalid”)
    .BindConfiguration(“MissingSection”)
    .ValidateOnStart()
;

The preceding code binds a missing section to the invalid named options, making the Name property equal to null. That object will not pass our validation because the Name property is required.If we run the application now, we get the following message:

Hosting failed to start
Microsoft.Extensions.Options.OptionsValidationException: Name: The invalid.Name field is required.

From that error, we know the validation works as expected. It is not every day that we are happy when our application doesn’t start but this is one of those time.That’s it for the code generation, it behaves the same, but the code under the hood is different, enabling technologies like AOT and trimming that do not support reflection-based mechanisms well. Moreover, code generation should speed up the program execution because the behaviors are hard-coded instead of relying on a dynamic reflection-based approach.Next, let’s dig into another class introduced in .NET 8.

Leave a Reply

Your email address will not be published. Required fields are marked *



         


          Terms of Use | Accessibility Privacy