Using IOptions and Microsoft.Extensions.Configuration
8 minutes to readIn this lesson we’re going to configure AkkaWordCounter2
using Microsoft.Extensions.Configuration and the IOptions
pattern.
Starts at the appropriate timestamp for this lesson
Configuring AkkaWordCounter2
One thing we have not resolved yet: how are we going to tell AkkaWordCounter2
which documents to count words from?
In this instance, we are going to use Microsoft.Extensions.Configuration to do it.
First, inside our AkkaWordCounter2.App/Config
folder, please add a new file called WordCounterSettings.cs
and then type the following:
public class WordCounterSettings
{
public string[] DocumentUris { get; set; } = [];
}
This is a very simple strongly typed settings class that we’re going to use to configure our application - but the next thing we’re going to do is add a new appSettings.json
file to AkkaWordCounter2.App
:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"WordCounter":
{
"DocumentUris": [
"https://raw.githubusercontent.com/akkadotnet/akka.net/dev/README.md",
"https://getakka.net/"
]
}
}
We’re going to add some code to our IHostBuilder
to parse this appSettings.json
file into our WordCounterSettings
class in a minute - that’s where we’re headed.
Making Sure appSettings.json
Gets Copied
However, one other thing we need to do is edit the AkkaWordCounter2.App.csproj
file and make sure that we always copy the appSettings.json
to our output folder when we run the application. Otherwise none of our settings will be effective.
Add the following XML to AkkaWordCounter2.App.csproj
or set these values via your preferred IDE dialog window:
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
Leveraging the IOptions
Pattern
We’re going to do this using the IOptions
pattern from Microsoft.Extensions.Configuration, since that’s considered to be a generally robust practice for application configuration.
We already have our WordCounterSettings
class defined inside AkkaWordCounter2.App/Config/WordCounterSettings.cs
- that is our base options class. Now we’re going to add some configuration validation code, designed to ensure that our application fails fast at startup if it’s misconfigured - another good habit to get into.
Inside AkkaWordCounter2.App/Config/WordCounterSettings.cs
please type the following:
public sealed class WordCounterSettingsValidator : IValidateOptions<WordCounterSettings>
{
public ValidateOptionsResult Validate(string? name, WordCounterSettings options)
{
var errors = new List<string>();
if (options.DocumentUris.Length == 0)
{
errors.Add("DocumentUris must contain at least one URI");
}
if(options.DocumentUris.Any(uri => !Uri.IsWellFormedUriString(uri, UriKind.Absolute)))
{
errors.Add("DocumentUris must contain only absolute URIs");
}
return errors.Count == 0
? ValidateOptionsResult.Success
: ValidateOptionsResult.Fail(errors);
}
}
public static class WordCounterSettingsExtensions
{
public static IServiceCollection AddWordCounterSettings(this IServiceCollection services)
{
services.AddSingleton<IValidateOptions<WordCounterSettings>, WordCounterSettingsValidator>();
services.AddOptionsWithValidateOnStart<WordCounterSettings>()
.BindConfiguration("WordCounter");
return services;
}
}
If your IDE doesn’t auto-suggest the relevant namespaces for you to include, here’s what the top of the AkkaWordCounter2.App/Config/WordCounterSettings.cs
file should look like after you add this:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
What this code does:
- Declares a
WordCounterSettingsValidator
that will be used to validate the fully-parsed content of ourWordCounterSettings
at startup. - Creates an
IServiceCollection
extension method that registers theWordCounterSettingsValidator
as a singleton; binds theWordCounterSettings
to theWordCounter
section of ourappSettings.json
file1; and then instructs theIHost
to validate ourWordCounterSettings
immediately upon host start.
Wiring Configuration to IHost
We’re almost done configuring AkkaWordCounter2
- the last things we need to do are to add the following to AkkaWordCounter2.App/Program.cs
:
First, we need to add our configuration sources to the host builder:
hostBuilder
.ConfigureAppConfiguration((context, builder) =>
{
builder
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json",
optional: true)
.AddEnvironmentVariables();
})
Next, we need to add our IOptions
to the IServiceCollection
so they can be accessed by our application:
services.AddWordCounterSettings();
Your Program.cs
should look like this:
using Akka.Hosting;
using AkkaWordCounter2.App;
using AkkaWordCounter2.App.Actors;
using AkkaWordCounter2.App.Config;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
var hostBuilder = new HostBuilder();
hostBuilder
.ConfigureAppConfiguration((context, builder) =>
{
builder
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json",
optional: true)
.AddEnvironmentVariables();
})
.ConfigureServices((context, services) =>
{
services.AddWordCounterSettings();
services.AddHttpClient(); // needed for IHttpClientFactory
services.AddAkka("MyActorSystem", (builder, sp) =>
{
builder
.ConfigureLoggers(logConfig =>
{
logConfig.AddLoggerFactory();
});
});
});
var host = hostBuilder.Build();
await host.RunAsync();
We’re all finished with configuration.
Wrapping Up
With configuration out the way, we are onto our final lesson of Unit 1: building the WordCountJobActor
, a small “saga” actor that uses message stashing to orchestrate work between multiple other actors.
Further Reading
- Akka.NET Application Design: Don’t Create Bespoke Frameworks; Use Repeatable Patterns
- No Hocon, No Lighthouse, No Problem: Akka.Hosting, Akka.HealthCheck, and Akka.Management
-
This will also work with any other configuration sources we might specify later, such as environment variables or
appSettings.{ENVIRONMENT}.json
- so long as those follow the Microsoft.Extensions.Configuration conventions. ↩