Introduction
(Where’d my web.config go?)
The first thing you will notice when you begin to set up application configuration in the new ASP.NET 5 framework is that the web.config file is gone. XML has gone the way of the dinosaurs and been replaced by JSON. It may seem a little confusing at first, but once you get it down, things get a lot simpler. No more tedious release vs debug transformations and the new configuration file are much easier to read and understand (for me anyway). Plus… we get the benefits of strongly typed configuration data that can be separated into models. In this article, I’ll walk you through everything you need to know to get started using vNext’s new configuration system.
How It Works
The new configuration file is aptly named appsettings.json, although you can name it anything you want. When you create a new ASP.NET 5 Web Application project using the scaffolding wizard, this file is automatically placed in the root of your project. We can also create separate versions of this file to override values for each environment such as debug and release, but more on that later.
Solution Explorer showing appsettings.json:
The default contents of the appsettings.json file.
{
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=blah-blah-blah..."
}
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Verbose",
"System": "Information",
"Microsoft": "Information"
}
}
}
As you can see above, the default content gives us some basic application settings in JavaScript Object Notation (JSON). Here’s where we’ll find the connection string just as we would have in our old web.config, and there’s some Logging configuration values here too. Notice the hierarchical structure of the key-value-pairs. Let’s break away from the appsettings.json file for a moment (we’ll get back to it later), and look at Startup.cs.
First a Quick Note
You'll also need to make sure you have the correct packages loaded via Nuget's new package system by placing the following lines in project.json.
Note: these are included by default in the scaffold generated project.
"dependencies": {
"Microsoft.Extensions.Configuration.FileProviderExtensions" : "1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0-rc1-final",
}
The default contents of the Startup.cs file.
using Microsoft.AspNet.Hosting;
using Microsoft.Extensions.Configuration;
namespace HelloConfigWorld
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
}
}
In Startup.cs, we have a ConfigurationBuilder
. At its simplest, the "Configuration
" is a series of "Providers
". After we new up a ConfigurationBuilder()
, we simply add providers, i.e., We add the JSON file provider via .AddJsonFile("appsettings.json")
, in the same way, we add the user secrects provider or the environment variables provider via builder.AddUserSecrets()
and builder.AddEnvironmentVariables()
respectively. To illustrate my point, look below at the following simple example from Microsoft's documentation. Here, we've done something totally different, we've added a MemoryConfigurationProvider()
, but the concept is the same. If you miss XML, there's a built-in XML provider, and you can even write your own providers if you need special cases.
var builder = new ConfigurationBuilder();
builder.Add(new MemoryConfigurationProvider());
var config = builder.Build();
config.Set("somekey", "somevalue");
string setting2 = config["somekey"];
Overriding Configuration Values
Now you're thinking to yourself, he said earlier I didn't have to worry about those pesky transformations that came from using web.config. Well, let me show you how to handle overriding configuration based on your current environment. The providers are added in order, and each new provider will override the values set by the previous provider. By adding them in order, we can have base values, overridden by say development or release values, overridden by user secrets (which I'll cover next week in an upcoming post), and finally overridden by environment variables. The order is configurable, but this is a basic best practice. Look back at the default Startup.cs, notice the lines.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
Now supposing we're in our development environment, we would first read appsettings.json, then we would read appsettings.development.json overriding all matching values. Let's add a development file to our project. The values in this file will override any values that previously existed in appsettings.json. To add the new file, right click on your project, and choose Add->New Item. Next choose ASP.NET Configuration File from the menu and name it appsettings.development.json.
You can use the Add New Item wizard to add new configuration files. Do this again to create an appsettings.release.json, and you should see this at the bottom of your Solution Explorer.
Solution Explorer showing all appsettings.json. The code below helps illustrate values being first defined in a base file, then if those values exist in a file added further down the chain, how the values are replaced. Values that do not exist in subsequently loaded providers are maintained in their original state.
{
"myKey": "baseValue",
"anotherKey": "unchangedValue"
}
{
"myKey": "developmentValue"
}
{
"myKey": "releaseValue"
}
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
Configuration = builder.Build();
Configuration.Get("myKey"); Configuration.Get("anotherKey");
Configuration.Get("myKey"); Configuration.Get("anotherKey");
It's as easy as that. You can also override values based on other environments, such as staging. Your file doesn't have to be called appsettings.json, you could just as easily have foo.json and override with bar.json, etc. Much more flexible than the old way, wouldn't you say?
Using the Configuration in Your Application
Okay, so now we have our application values configured, how do we get them so we can use them in say a controller? First, it's worth saying something about strongly typed values. Before, in the web.config days, all we stored were string
s, and then when we consumed them in our application, we parsed them into whatever we needed. Now, in the new system, we store values in the JSON as whatever type we need. Integers as numbers, booleans as booleans, etc. No more coercing our values to use them. We do this by creating POCO classes and "loading" them with our config values on startup. Next, we're going to create some configuration options for an SMTP client and use them in our application. Let's add an SMTP configuration to our application via appsettings.json. We could override them in other files depending on environment. Perhaps we use a different server in production with different credentials, but we already know how to do that, so I'll keep it simple.
{
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=blah-blah-blah..."
}
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Verbose",
"System": "Information",
"Microsoft": "Information"
}
},
"SmtpOptions": {
"UserName": "Troy",
"Password": "Locke",
"FromName": "Troy Locke",
"FromAddress": "Troy@slnzero.com",
"DeliveryMethod": "Network",
"Host": "smtp.slnzero.com",
"Port":25,
"DefaultCredentials": false,
"EnableSsl": false
}
}
Notice that we have string
s, an integer to specify the Port
, and boolean values for DefaultCredentials
and EnableSsl
. Nice! Next, we have to create a class for our application to hold the values so we can use them. We'll create a matching POCO called SmtpOptions
like so.
namespace HelloConfigWorld
{
public class SmtpOptions
{
public string UserName { get; set; }
public string Password { get; set; }
public string FromName { get; set; }
public string FromAddress { get; set; }
public string DeliveryMethod { get; set; }
public string Host { get; set; }
public int Port { get; set; }
public bool DefaultCredentials { get; set; }
public bool EnableSsl { get; set; }
}
}
Again, notice we have strongtly typed values for our configuration options, these map straight out of the JSON configuration file appsettings.json into our SmtpOptions
class by adding a line in Startup.cs, let's look at how to do that now. Look back at your Startup.cs again, you'll notice, just below the area we focused on earlier, a method called ConfigureServices()
Add the line at the bottom as shown below.
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.Configure<SmtpOptions>(Configuration.GetSection("SmtpOptions"));
}
Next, we need to be able to reference these values in our Application. vNext is big on DI (Dependency Injection) and we're going to use it to "inject" our SmtpOptions
class into our app where we need it via the IOptions
interface. In the constructor, we simply pass in an IOptions<SmtpOptions>
, and assign it to a class variable or property. We then have it available with all its values for use in the class. Below is a snippet from the default MessageService.cs that implements our options
class.
public class AuthMessageSender : IEmailSender, ISmsSender
{
private readonly IOptions<SmtpOptions> _smtpOptions;
public AuthMessageSender(IOptions<SmtpOptions> smtpOptions)
{
_smtpOptions = smtpOptions;
}
public Task SendEmailAsync(string email, string subject, string message)
{
var host = _smtpOptions.Value.Host;
var port = _smtpOptions.Value.Port;
var userName = _smtpOptions.Value.UserName;
var password = _smtpOptions.Value.Password;
var fromName = _smtpOptions.Value.FromName;
var fromAddress = _smtpOptions.Value.FromAddress;
var deliverMethod = _smtpOptions.Value.DeliveryMethod;
var defaultCredentials = _smtpOptions.Value.DefaultCredentials;
var enableSsl = _smtpOptions.Value.EnableSsl;
var msg = new MimeMessage();
msg.From.Add(new MailboxAddress(fromName, fromAddress));
msg.To.Add(new MailboxAddress(email, email));
msg.Subject = subject;
}
}
Acknowledgment
You may have noticed AddUserSecrets()
and AddEnvironmentVariables()
in the Startup.cs examples. I have left these out to keep this post from becoming too long. In short, these are methods to “safely” store configuration settings out and away from your applications code. Passwords and such are never in code and thus never stored in version control or passed between developers sharing a code base. By pulling our configuration values from environment variables in production, we greatly reduce the likelihood of them being compromised. I fully intend to cover this topic in an upcoming article. Also, I've stated that there is no web.config, this is only partially true, there does exist a web.config file under the /wwwroot directory of the project that is used to configure certain server values.
Summary
As you can see, the new vNext is vastly improved over the old web.config. It’s much more versatile in that we can take complete control over our application’s configuration. It’s simpler to read, configure, and setup than the old system. And the use of strongly typed values make for a better practice overall. I hope this article helps you better understand and implement configuration in the new ASP.NET 5 vNext framework.