Introduction to ASP.NET Core part 3: the configuration file

Introduction

In the previous post we looked at how dependencies and dependency injection work in .NET Core. New libraries can be downloaded to the solution in various ways: through the NuGet GUI, The NuGet package manager console, by editing project.json directly or letting Visual Studio figure out if a certain library could solve a missing reference problem. Dependency injection can be achieved through a built-in IoC – Inversion of Control container – where we register the abstractions and the corresponding dependencies in Startup.cs. We can give various lifetimes to the dependencies: transient, scoped and singleton. Then whenever a class requires a dependency the IoC container will serve up the registered concrete implementation automatically.

In this post we’ll see how to add a configuration file to the project and how to read from it.

There’s a lot of documentation on this topic available on the Microsoft .NET Core guide here. We’ll go through some of it in this post.

As usual we’ll be working in our demo app DotNetCoreBookstore.

The new configuration file

The ASP.NET Core template in Visual Studio adds a web.config file to the project. Probably all ASP.NET developers are familiar with web.config and have used it for various configuration purposes. Web.config in ASP.NET Core however plays very little role. The Visual Studio template only adds a couple of lines there meant for IIS only. The web project itself will have no knowledge of this file. If you try to read your application properties from it the usual way, like ConfigurationManager.AppSettings[“setting-name”] it won’t work.

Instead, we’ll need to add a new JSON file called appsettings.json. Right-click the project node and select Add, New Item. The item we need is called ASP.NET Configuration File:

Adding a configuration file to the .NET Core project in Visual Studio

The generated appsettings.json file adds a ConnectionStrings property for us with a sub-property called DefaultConnection. That section is meant to store the database connection strings. We haven’t got that far yet so we’ll ignore it for the time being.

Note that the setting file can have any name, appSettings.json is not mandatory, but it seems to be an accepted convention.

You can probably guess that application configuration is no longer done in XML but JSON. The flexible JSON structure allows us to store entire JSON trees as application settings. We’re no longer tied to simple key-value pairs like in the XML appSettings section of traditional web.config. Reading from a configuration file demonstrates the modularity of .NET Core. We’ll need to bring in two dependencies from NuGet and write some code in Startup.cs so that the application becomes aware of the configuration file.

Let’s enter the following properties into appsettings.json:

"Mood": "Optimist",
"Colours": [ "Red", "Green", "White", "Blue" ],
"Languages": {
".NET": [ "C#", "VB.NET", "F#" ],
"JVM": ["Java", "Scala", "Clojure"]
}

Next we’ll add two new dependencies to the “dependencies” section of project.json. Microsoft.Extensions.Configuration.FileExtensions is a library used for file-based configurations in .NET Core:

“Microsoft.Extensions.Configuration.FileExtensions”: “1.1.0”

If there’s a higher stable version by the time you read this post then feel free to add that instead.

The second dependency we need is specifically to handle JSON based configuration:

“Microsoft.Extensions.Configuration.Json”: “1.1.0”

Save project.json and you’ll see that the packages are downloaded from NuGet in a matter of seconds.

The gateway into reading the application configuration is the ConfigurationBuilder object in the Microsoft.Extensions.Configuration namespace. ConfigurationBuilder has a fluent API that allows us to chain the various configuration sources so that we can read all of them from a joined source. The point is that we don’t need to store all the application settings in a single JSON file. They can come from various sources such as environment variables, other JSON files and whatever source that implements the IConfigurationSource interface which is also located in the Microsoft.Extensions.Configuration.

We can load the configuration values in a constructor in Startup.cs as follows:

public Startup(IHostingEnvironment hostingEnvironment)
{
	ConfigurationBuilder configBuilder = new ConfigurationBuilder();
	configBuilder
		.SetBasePath(hostingEnvironment.ContentRootPath)
		.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
		.AddEnvironmentVariables();
	IConfiguration configRoot = configBuilder.Build();					
}

We saw the IHostingEnvironment interface in the previous post. Its ContentRootPath property is very helpful in our case. The SetBasePath does exactly what the method name suggests and sets the base path for the registered files. AddJsonFile declares the name of the settings file and without the base path the application wouldn’t find it, it needs the full file name. The “optional” property declares whether the registered JSON file is optional or not. If it’s optional and not found when the Build method is called then no exception is thrown. Otherwise if the file is mandatory and is not found then we get a FileNotFoundException. If “reloadOnChange” is set to true then the settings will be reloaded if the file changes during runtime. The AddEnvironmentVariables extension method reads the environment variables that can also be used as application settings. Note that it’s perfectly fine to register other JSON files by calling AddJsonFile multiple times.

We finally call the Build method to gather all application settings from the registered sources. It returns an IConfiguration object which lets us retrieve configuration values by indexes. We’ll start with the easiest case and read the current mood:

string currentMood = configRoot["Mood"];

Reading the Colours section is a bit more involved. We first need to get a reference to the the Colours section and then read its children that in turn are also individual configuration sections. Each sub-section will have a Value property that holds a member of the string array:

IConfigurationSection coloursSection = configRoot.GetSection("Colours");
IEnumerable<IConfigurationSection> coloursSectionMembers = coloursSection.GetChildren();
List<string> colours = (from c in coloursSectionMembers select c.Value).ToList();

“colours” will have 4 items as expected.

If you know exactly which item you need from the array, i.e. what its index is then you can use the index number as follows:

string secondColour = configRoot["Colours:1"];

The colon is used to separate the various subsections in the JSON structure. “secondColour” will get the value “Green”.

Now let’s load the platforms and languages:

IConfigurationSection languagesSection = configRoot.GetSection("Languages");
IEnumerable<IConfigurationSection> languagesSectionMembers = languagesSection.GetChildren();
Dictionary<string, List<string>> platformLanguages = new Dictionary<string, List<string>>();
foreach (var platform in languagesSectionMembers)
{
	List<string> langs = (from p in platform.GetChildren() select p.Value).ToList();
	platformLanguages[platform.Key] = langs;
}

We get hold of the children of the Languages node first. Then for each child, such as “.NET” there is a string array where each array member is a child so we need to load them separately.

Again, if you know exactly what you are looking for in the JSON object hierarchy then you can use the ‘:’ notation like above:

string secondJvmLanguage = configRoot["Languages:JVM:1"];

secondJvmLanguage will be set to Scala.

Our IConfiguration has a list of the environmental variables as well since we called AddEnvironmentVariables extension method. If you know the key of the environmental variable then you can simply get its value through the same indexing technique as above. E.g. this is how to read the Java home variable:

string javaHome = configRoot["JAVA_HOME"];

…which in my case resolves to C:\Program Files\Java\jdk1.8.0_73 .

So far we’ve only played with some test code in Startup.cs. However, configuration values are usually necessary in various places of an application. Our JsonStringFormatter object might want to extract the current mood as well. One possible solution is to let JsonStringFormatter depend on the IConfiguration object we build in Startup.cs:

using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System;

namespace DotNetCoreBookstore.Dependencies
{
	public class JsonStringFormatter : IStringFormatter
	{
		private readonly IGreeter _greeter;
		private readonly IConfiguration _configuration;

		public JsonStringFormatter(IGreeter greeter, IConfiguration configuration)
		{
			if (greeter == null) throw new ArgumentNullException("Greeter");
			if (configuration == null) throw new ArgumentNullException("Configuration");
			_greeter = greeter;
			_configuration = configuration;
		}

		public string FormatMe(object input)
		{
			return JsonConvert.SerializeObject(new { Greeting = _greeter.SendGreeting(), Content = input, Mood = _configuration["Mood"] });
		}
	}
}

We need to add a couple of things in Startup.cs as well. We’ll store the configuration object in a property:

private IConfiguration Configuration { get; }

We assign the “configRoot” variable to this property within the Startup constructor:

Configuration = configRoot;

Finally we register the service in ConfigureServices:

services.AddSingleton(Configuration);

Run the application and you’ll see the extended JSON in the screen:

{“Greeting”:”Good morning”,”Content”:{“Message”:”Hello World!”},”Mood”:”Optimist”}

Binding settings to objects

There’s a slightly more object oriented way to read application settings. Say we have the following section in appsettings.json:

"Measures": {
"Width": 123,
"Height": 867,
"Length": 456,
"Type" : "3D"
}

We’ll need a matching object to hold these properties. We have complete freedom over what this object looks like, i.e. what properties, constructors and functions it has. The following will be fine:

public class ProductMeasures
{
	public int Width { get; set; }
	public int Height { get; set; }
	public int Length { get; set; }
	public string Type { get; set; }
}

The class name doesn’t have to match the section name in appsettings.json, so it’s fine that the class is called ProductMeasures and the configuration section is called Measures. It’s also fine to have different property names as we have complete freedom over the binding rules in a lambda expression.

We can extend JsonStringFormatter with an extra input parameter through its constructor:

public class JsonStringFormatter : IStringFormatter
{
	private readonly IGreeter _greeter;
	private readonly IConfiguration _configuration;
	private readonly ProductMeasures _productMeasures;

	public JsonStringFormatter(IGreeter greeter, IConfiguration configuration, ProductMeasures productMeasures)
	{
		if (greeter == null) throw new ArgumentNullException("Greeter");
		if (configuration == null) throw new ArgumentNullException("Configuration");
		if (productMeasures == null) throw new ArgumentNullException("Measures");
		_greeter = greeter;
		_configuration = configuration;
		_productMeasures = productMeasures;
	}

	public string FormatMe(object input)
	{
		return JsonConvert.SerializeObject(new { Greeting = _greeter.SendGreeting(), Content = input,
			Mood = _configuration["Mood"], Measures = _productMeasures });
	}
}

We can register a ProductMeasures object in the ConfigureServices method of Startup.cs as follows:

services.AddSingleton((serviceProvider) =>
{
	ProductMeasures opt = new ProductMeasures();
	opt.Height = Convert.ToInt32(Configuration["Measures:Height"]);
	opt.Length = Convert.ToInt32(Configuration["Measures:Length"]);
	opt.Type = Configuration["Measures:Type"];
	opt.Width = Convert.ToInt32(Configuration["Measures:Width"]);
	return opt;
});

Run the application and see the updated JSON message:

{“Greeting”:”Good morning”,”Content”:{“Message”:”Hello World!”},”Mood”:”Optimist”,”Measures”:{“Width”:123,”Height”:867,”Length”:456,”Type”:”3D”}}

There’s an alternative solution using the IOptions interface in the Microsoft.Extensions.Options namespace. The IOptions interface is generic where the type parameter can be any custom object type. Let’s see an example. We add a new section to appsettings.json:

"Favourites": {
"Music" : "Rock",
"Food": "TomatoSoup",
"Technology" : ".NET"
}

We also have a matching C# object:

public class FavOptions
{
	public string Music { get; set; }
	public string Food { get; set; }
	public string Technology { get; set; }
}

We extend JsonStringFormatter yet again. Note how the FavOptions object is wrapped within IOptions. IOptions has a Value property to get hold of the enclosed options object:

public class JsonStringFormatter : IStringFormatter
{
	private readonly IGreeter _greeter;
	private readonly IConfiguration _configuration;
	private readonly ProductMeasures _productMeasures;
	private readonly FavOptions _favOptions;

	public JsonStringFormatter(IGreeter greeter, IConfiguration configuration, ProductMeasures productMeasures
		, IOptions<FavOptions> favourites)
	{
		if (greeter == null) throw new ArgumentNullException("Greeter");
		if (configuration == null) throw new ArgumentNullException("Configuration");
		if (productMeasures == null) throw new ArgumentNullException("Measures");
		if (favourites == null || favourites.Value == null) throw new ArgumentNullException("Favourites");
		_greeter = greeter;
		_configuration = configuration;
		_productMeasures = productMeasures;
		_favOptions = favourites.Value;
	}

	public string FormatMe(object input)
	{
		return JsonConvert.SerializeObject(new { Greeting = _greeter.SendGreeting(), Content = input,
			Mood = _configuration["Mood"], Measures = _productMeasures, Favourites = _favOptions });
	}
}

We need to extend ConfigureServices of Startup.cs again but in a different way. The Configure extension method has been designed to bind options objects. We don’t need to call the Add methods to register the dependency, it will be taken care of for us. Here’s the code:

services.Configure<FavOptions>((opt) =>
{
	opt.Food = Configuration["Favourites:Food"];
	opt.Music = Configuration["Favourites:Music"];
	opt.Technology = Configuration["Favourites:Technology"];
});

Run the application and you’ll see the updated JSON output:

{“Greeting”:”Good morning”,”Content”:{“Message”:”Hello World!”},”Mood”:”Optimist”,”Measures”:{“Width”:123,”Height”:867,”Length”:456,”Type”:”3D”},”Favourites”:{“Music”:”Rock”,”Food”:”TomatoSoup”,”Technology”:”.NET”}}

An important point with the options objects is that we don’t need to pass around the IConfiguration object. We can have dedicated sections in appsettings.json or in other config files and use those sections in dedicated C# POCO objects. Each and every object that requires some configuration values for its work can be handed a specialised options object without sending the entire list of settings.

We’ll stop here now. We’ll continue our journey in the next post.

View the list of MVC and Web API related posts here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

2 Responses to Introduction to ASP.NET Core part 3: the configuration file

  1. Vojtech Cizinsky says:

    Loving this ASP.Net Core series and your blog in general. It should be a mandatory reading for every .Net developer. Keep up the good work!

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 )

Facebook photo

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

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: