In Part 1, we saw how to retrieve basic application settings from a range of formats available in ASP .NET 5. In this article, we will see techniques used for settings that go beyond simple key-value representation.
Getting a Config Section
Let’s say we have this appsettings.json:
{ "Title": "configuration", "DataSettings": { "ConnectionString": "SomeConnectionString", "AuditDatabaseName": "AuditDB" } }
…and we want to map the DataSettings
to the following C# class:
public class DataSettings { public string ConnectionString { get; set; } public string AuditDatabaseName { get; set; } }
We can start with configuration loading code from the Part 1 article:
public void ConfigureServices(IServiceCollection services) { var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddJsonFile("appsettings.json"); Configuration = configurationBuilder.Build(); // TODO code goes here }
At this point, we need to add the following dependency to our project.json:
"Microsoft.Extensions.OptionsModel": "1.0.0-rc1-final"
Then, in the TODO part in the code above, we can add the following to read the DataSettings
section into its strongly-typed C# class equivalent:
public void ConfigureServices(IServiceCollection services) { var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddJsonFile("appsettings.json"); Configuration = configurationBuilder.Build(); var section = Configuration.GetSection("DataSettings"); services.Configure<DataSettings>(section); }
You may be disappointed to find that this doesn’t give you an object you can use right away. However, what we are doing is putting our config section in the services
collection, which is basically ASP .NET 5’s built-in dependency injection. In the next section, we’ll see how we can actually use this.
Note that by reading a config section, you’re basically pulling out only that section from the config. In the example above, the Title setting is not included in the section we read.
Dependency Injection with the Options Model
One of the most important parts of ASP .NET 5 Configuration is the options model, which is just a fancy way of saying dependency injection. By calling services.Configure()
in the previous section, we have set up our settings class with dependency injection. So how do we use it?
Let’s make a simple Web API controller to see this in action. In ASP .NET 5, Web API and MVC are the same thing, so we’ll need the MVC package. Also make sure you have the OptionsModel package from the previous section:
"Microsoft.Extensions.OptionsModel": "1.0.0-rc1-final", "Microsoft.AspNet.Mvc": "6.0.0-rc1-final"
We’ll need to do some simple setup in Startup.cs for MVC to work:
public void ConfigureServices(IServiceCollection services) { var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddJsonFile("appsettings.json"); Configuration = configurationBuilder.Build(); var section = Configuration.GetSection("DataSettings"); services.Configure<DataSettings>(section); services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseMvc(); }
Finally, we can add our controller:
[Route("api/[controller]")] public class DataController : Controller { IOptions<DataSettings> dataSettings; public DataController(IOptions<DataSettings> dataSettings) { this.dataSettings = dataSettings; } public IActionResult Index() { return new ObjectResult(this.dataSettings.Value.ConnectionString); } }
Notice how by putting an IOptions<DataSettings>
in the constructor, we can get our settings class from the dependency injection framework. Accessing its Value
gives us the DataSettings
instance that we configured earlier.
Sure enough, it works:
Strongly-Typed Configuration from Root
We don’t always need to read just a section. Sometimes, it can be handy to read the whole configuration file.
So let’s say I have these settings classes:
public class MySettings { public string Title { get; set; } public List<string> SupportedFormats { get; set; } public List<Hero> Heroes { get; set; } } public class Hero { public string CharacterName { get; set; } public string ActorName { get; set; } }
Our appsettings.json now contains the following:
{ "Title": "configuration", "SupportedFormats": [ "json", "xml", "ini" ], "Heroes": [ { "CharacterName": "Luke Skywalker", "ActorName": "Mark Hamill" }, { "CharacterName": "Han Solo", "ActorName": "Harrison Ford" } ] }
Then, I can read all this stuff into MySettings
as follows:
public void ConfigureServices(IServiceCollection services) { var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddJsonFile("appsettings.json"); Configuration = configurationBuilder.Build(); var mySettings = new MySettings(); Configuration.Bind(mySettings); }
And… there you go:
If you take a moment to look at the stuff I put into the appsettings.json, you’ll begin to appreciate just how powerful this is. We can read basic strings, arrays, and even lists of entire objects like this. If you’ve experienced what a pain in the ass it is to read a simple list of data from a key in Web.config
with the ASP .NET we’re used to, then this should feel like a breath of fresh air.
Custom Configuration Providers
If none of the default setting file formats match what you need, then you’ll probably need to create your own custom configuration provider. The example in the official docs shows a reasonable example: retrieving settings from a database. Unfortunately, however, that example doesn’t work (at the time of writing this article), because the APIs have changed and the official docs have not been updated.
Just for the sake of example, let’s imagine we want to load a file containing pipe-delimited settings. We’ll work with the following pipeconfig.txt:
key1|value1|key2|value2
Our custom provider class needs to:
- Inherit from
ConfigurationProvider
- Override the
Load()
method - Set the
Data
dictionary
Here’s an example of what our pipe-delimited-setting loader could look like:
public class PipeDelimitedConfigSource : ConfigurationProvider { private string filename; public PipeDelimitedConfigSource(string filename) { this.filename = filename; } public override void Load() { string fileContents = File.ReadAllText(filename); string[] tokens = fileContents.Split(new char[] { '|' }); for (int i = 0; i < tokens.Length; i += 2) { var key = tokens[i]; var value = tokens[i + 1]; this.Data[key] = value; } } }
Then, back in Startup.cs, we can feed it to our ConfigurationBuilder
using its Add()
method:
public void ConfigureServices(IServiceCollection services) { var configurationBuilder = new ConfigurationBuilder(); var configSource = new PipeDelimitedConfigSource("../pipeconfig.txt"); configurationBuilder.Add(configSource); Configuration = configurationBuilder.Build(); } public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.Run(async (context) => { var setting1 = this.Configuration["key1"]; var setting2 = this.Configuration["key2"]; string output = $"<h1>{setting1}</h1><h2>{setting2}</h2>"; await context.Response.WriteAsync(output); }); }
Here’s the output:
Config files and wwwroot
Before ASP .NET 5, configuration resided in Web.config. Having a single file makes it easy to configure web servers not to serve it. The flexibility afforded by having different possible formats and source for configuration in ASP .NET 5 thus begs the question: how do we prevent our config files from being served?
This is easy to deal with if we understand the role of the wwwroot folder. Before ASP .NET 5, the project root and the website root were one and the same. In ASP .NET 5, the website root (called wwwroot by default, but configurable) is a subfolder of the project folder. Thus, to keep files out of reach, it is necessary only to keep them out of wwwroot.
This way, the application code will have access to these files, but they will not be served as static files via simple web requests.
The role of web.config
By reading this article and the previous one, you have hopefully learned that there is no longer a standard web.config file that stores all the web application’s settings. With this in mind, you might be a little surprised to find that standard ASP .NET 5 project templates actually come with a web.config in the wwwroot folder.
At the time of writing this article, the contents of the file (although not important here) are the following:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/> </handlers> <httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" startupTimeLimit="3600"/> </system.webServer> </configuration>
The reason for the presence of the web.config is explained in this StackOverflow answer, which I quote:
“Web.config is strictly for IIS Configuration. It is not needed unless hosting in IIS. It is not used when you run the app from the command line.
“In the past Web.config was used for both IIS configuration and application configuration and settings. But in asp.net 5 it is not used by the application at all, it is only used for IIS configuration.
“This decoupling of the application from IIS is part of what makes cross platform possible.”
Summary
In this article, we have seen more complex ways in which we can read application configuration. The configuration model in ASP .NET 5 is indeed powerful, as we can map settings files to strongly-typed objects, and pass them on to our controllers via dependency injection. Where necessary, we can extend the range of built-in configuration providers by creating our own.
In the final sections, we have also discussed how to protect settings files from being accidentally served over the web, and why there is a web.config file in wwwroot by default despite ASP .NET 5 doing away with web.config.