Want to yank configuration values from your .NET Core apps? Here’s how to store and access them in Azure and AWS.

Creating new .NET apps, or modernizing existing ones? If you’re following the 12-factor criteria, you’re probably keeping your configuration out of the code. That means not stashing feature flags in your web.config file, or hard-coding connection strings inside your classes. So where’s this stuff supposed to go? Environment variables are okay, but not a great choice; no version control or access restrictions. What about an off-box configuration service? Now we’re talking. Fortunately AWS, and now Microsoft Azure, offer one that’s friendly to .NET devs. I’ll show you how to create and access configurations in each cloud, and as a bonus, throw out a third option.

.NET Core has a very nice configuration system that makes it easy to read configuration data from a variety of pluggable sources. That means that for the three demos below, I’ve got virtually identical code even though the back-end configuration stores are wildly different.

AWS

Setting it up

AWS offers a parameter store as part of the AWS Systems Manager service. This service is designed to surface information and automate tasks across your cloud infrastructure. While the parameter store is useful to support infrastructure automation, it’s also a handy little place to cram configuration values. And from what I can tell, it’s free to use.

To start, I went to the AWS Console, found the Systems Manager service, and chose Parameter Store from the left menu. From here, I could see, edit or delete existing parameters, and create new ones.

Each parameter gets a name and value. For the name, I used a “/” to define a hierarchy. The parameter type can be a string, list of strings, or encrypted string.

The UI was smart enough that when I went to go add a second parameter (/seroterdemo/properties/awsvalue2), it detected my existing hierarchy.

Ok, that’s it. Now I was ready to use it my .NET Core web app.

Using from code

Before starting, I installed the AWS CLI. I tried to figure out where to pass credentials into the AWS SDK, and stumbled upon some local introspection that the SDK does. Among other options, it looks for files in a local directory, and those files get created for you when you install the AWS CLI. Just a heads up!

I created a new .NET Core MVC project, and added the Amazon.Extensions.Configuration.SystemsManager package. Then I created a simple “Settings” class that holds the configuration values we’ll get back from AWS.

public class Settings
{
public string awsvalue { get; set; }
public string awsvalue2 { get; set; }
}

In the appsettings.json file, I told my app which AWS region to use.

{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"AWS": {
"Profile": "default",
"Region": "us-west-2"
}
}

In the Program.cs file, I updated the web host to pull configurations from Systems Manager. Here, I’m pulling settings that start with /seroterdemo.

public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
builder.AddSystemsManager("/seroterdemo");
})
.UseStartup<Startup>();
}

Finally, I wanted to make my configuration properties available to my app code. So in the Startup.cs file, I grabbed the configuration properties I wanted, inflated the Settings object, and made it available to the runtime container.

public void ConfigureServices(IServiceCollection services)
{
services.Configure<Settings>(Configuration.GetSection("properties"));

services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
}

Last step? Accessing the configuration properties! In my controller, I defined a private variable that would hold a local reference to the configuration values, pulled them in through the constructor, and then grabbed out the values in the Index() operation.

        private readonly Settings _settings;

public HomeController(IOptions<Settings> settings)
{
_settings = settings.Value;
}

public IActionResult Index()
{
ViewData["configval"] = _settings.awsvalue;
ViewData["configval2"] = _settings.awsvalue2;

return View();
}

After updating my View to show the two properties, I started up my app. As expected, the two configuration values showed up.

What I like

You gotta like that price! AWS Systems Manager is available at no cost, and there appears to be no cost to the parameter store. Wicked.

Also, it’s cool that you have an easily-visible change history. You can see below that the audit trail shows what changed for each version, and who changed it.

The AWS team built this extension for .NET Core, and they added capabilities for reloading parameters automatically. Nice touch!

Microsoft Azure

Setting it up

Microsoft just shared the preview release of the Azure App Configuration service. This managed service is specifically created to help you centralize configurations. It’s brand new, but seems to be in pretty good shape already. Let’s take it for a spin.

From the Microsoft Azure Portal, I searched for “configuration” and found the preview service.

I named my resource seroter-config, picked a region and that was it. After a moment, I had a service instance to mess with. I quickly added two key-value combos.

That was all I needed to do to set this up.

Using from code

I created another new .NET Core MVC project and added the Microsoft.Extensions.Configuration.AzureAppConfiguration package. Once again I created a Settings class to hold the values that I got back from the Azure service.

public class Settings
{
public string azurevalue1 { get; set; }
public string azurevalue2 { get; set; }
}

Next up, I updated my Program.cs file to read the Azure App Configuration. I passed the connection string in here, but there are better ways available.

public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) => {
var settings = config.Build();
config.AddAzureAppConfiguration("[con string]");
})
.UseStartup<Startup>();
}

I also updated the ConfigureServices() operation in my Startup.cs file. Here, I chose to only pull configurations that started with seroterdemo:properties.

 public void ConfigureServices(IServiceCollection services)
{
//added
services.Configure<Settings>(Configuration.GetSection("seroterdemo:properties"));

services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
}

To read those values in my controller, I’ve got just about the same code as in the AWS example. The only difference was what I called my class members!

private readonly Settings _settings;

public HomeController(IOptions<Settings> settings)
{
_settings = settings.Value;
}

public IActionResult Index()
{
ViewData["configval"] = _settings.azurevalue1;
ViewData["configval2"] = _settings.azurevalue2;

return View();
}

I once again updated my View to print out the configuration values, and not shockingly, it worked fine.

What I like

For a new service, there’s a few good things to like here. The concept of labels is handy, as it lets me build keys that serve different environments. See here that I created labels for “qa” and “dev” on the same key.

I saw a “compare” feature which looks handy. There’s also a simple search interface here too, which is valuable.

Pricing isn’t yet available, no I’m not clear as to how I’d have to pay for this.

Spring Cloud Config

Setting it up

Both of the above service are quite nice. And super convenient if you’re running in those clouds. You might also want a portable configuration store that offers its own pluggable backing engines. Spring Cloud Config makes it easy to build a config store backed by a file system, git, GitHub, Hashicorp Vault, and more. It’s accessible via HTTP/S, supports encryption, is fully open source, and much more.

I created a new Spring project from start.spring.io. I chose to include the Spring Cloud Config Server and generate the project.

Literally all the code required is a single annotation (@EnableConfigServer).

 @EnableConfigServer
@SpringBootApplication
public class SpringBlogConfigServerApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBlogConfigServerApplication.class, args);
}
}

In my application properties, I pointed my config server to the location of the configs to read (my GitHub repo), and which port to start up on.

server.port=8888
spring.cloud.config.server.encrypt.enabled=false
spring.cloud.config.server.git.uri=https://github.com/rseroter/spring-demo-configs

My GitHub repo has a configuration file called blogconfig.properties with the following content:

With that, I started up the project, and had a running configuration server.

Using from code

To talk to this configuration store from my .NET app, I used the increasingly-popular Steeltoe library. These packages, created by Pivotal, bring microservices patterns to your .NET (Framework or Core) apps.

For the last time, I created a .NET Core MVC project. This time I added a dependency to Steeltoe.Extensions.Configuration.ConfigServerCore. Again, I added a Settings class to hold these configuration properties.

public class Settings
{
public string property1 { get; set; }
public string property2 { get; set; }
public string property3 { get; set; }
public string property4 { get; set; }
}

In my appsettings.json, I set my application name (to match the config file’s name I want to access) and URI of the config server.

{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"spring": {
"application": {
"name": "blogconfig"
},
"cloud": {
"config": {
"uri": "http://localhost:8888"
}
}
}
}

My Program.cs file has a “using” statement for the Steeltoe.Extensions.Configuration.ConfigServer package, and then used the “AddConfigServer” operation to add the config server as a source.

public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.AddConfigServer()
.UseStartup<Startup>();
}

I once again updated the Startup.cs file to load the target configurations into my typed object.

public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

services.Configure<Settings>(Configuration);
}

My controller pulled the configuration object, and I used it to yank out values to share with the View.

public HomeController(IOptions<Settings> mySettings) {
_mySettings = mySettings.Value;
}
Settings _mySettings {get; set;}

public IActionResult Index()
{
ViewData["configval"] = _mySettings.property1;
return View();
}

Updating the view, and starting the .NET Core app yielded the expected results.

What I like

Spring Cloud Config is a very mature OSS project. You can deliver this sort of microservices machinery along with your apps in your CI/CD pipelines — these components are software that you ship versus services that need to be running — which is powerful. It offers a variety of backends, OAuth2 for security, encryption/decryption of values, and much more. It’s a terrific choice for a consistent configuration store on every infrastructure.

But realistically, I don’t care which of the above you use. Just use something to extract environment-specific configuration settings from your .NET apps. Use these robust external stores to establish some rigor around these values, and make it easier to share configurations, and keep them in sync across all of your application instances.

Author: Richard Seroter

Richard Seroter is currently the Chief Evangelist at Google Cloud and leads the Developer Relations program. He’s also an instructor at Pluralsight, a frequent public speaker, the author of multiple books on software design and development, and a former InfoQ.com editor plus former 12-time Microsoft MVP for cloud. As Chief Evangelist at Google Cloud, Richard leads the team of developer advocates, developer engineers, outbound product managers, and technical writers who ensure that people find, use, and enjoy Google Cloud. Richard maintains a regularly updated blog on topics of architecture and solution design and can be found on Twitter as @rseroter.

8 thoughts

  1. This is awesome. In a way the article mirrors what we’re trying to accomplish. Storing configuration values is an implementation detail separate from the application’s logic (and other implementation details) so it’s nice to have an article that addresses that separately.

  2. Good writeup; I wasn’t aware of the new Azure App Config offering! It sounds like you can accomplish the same kind of thing with AWS Secrets Manager and Azure Key Vault, but those options cost money. I’m unclear on what the other capabilities are related to secrets that you might want to pay for. Do you have any insight on how these compare?

  3. Thanks for the article. It was much helpful. Could you also tell what if we put JSON as a variable in parameter store? How should we bind it to a model in .net?

  4. What would you suggest in scenarios where you want to have different values for development and production? A database connection string is a simple example. It seems that the AWS Secrets Manager Provider is loaded last in to the Configuration Providers … which means that it will always take priority over the local `appsettings.Development.json`, env vars, console parameters, etc … thoughts on that scenario?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.