Quantcast
Channel: Kotan Code 枯淡コード
Viewing all articles
Browse latest Browse all 27

Configuration Options and DI with ASP.NET 5

$
0
0

In ASP.NET 5, we now have access to a robust, unified configuration model that finally gives us the configuration flexibility that we’ve spent the past several years hoping and begging for.  Gone are the days of the hideous web.config XML files, and, if we’ve done our jobs right, so too are the days of web-dev.configweb-prod.config, and other such abominations that turn out to be cloud native anti-patterns.

The new configuration system explicitly decouples the concept of a configuration source with the actual value accessible from the Configuration object (yes, IConfiguration is always available via DI). When you use yeoman to create an ASP.NET application from a template, you’ll get some boilerplate code that goes in your Startup.cs file that looks something like this:

 public Startup(IHostingEnvironment env)
        {
            // Set up configuration sources.
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")         
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

What you see in this code is the application setting up two configuration sources: A JSON file called “appsettings.json” and the collection of key-value pairs pulled from environment variables. You can access these configuration settings using the colon (:) separator for arbitrary depth accessors. For example, the default “appsettings.json” file looks like this:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Verbose",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

If you wanted to know the default log level, you could access it using the configuration key string Logging:LogLevel:Default. While this is all well and good, ideally we want to segment our configuration into as many small chunks as possible, so that modules of our app can be configured separately (reducing the surface area of change per configuration modification). One thing that helps us with this is the fact that ASP.NET 5’s configuration system lets us define Options classes (POCOs) that essentially de-serialize arbitrary key-value structures from any configuration source into a C# object.

To continue adding to my previous samples,  let’s say that we’d like to provide some zombie configuration. This configuration will, in theory, be used to configure the zombie controller behavior. First, let’s create a JSON file (this is just for convenience, we could use any number of built-in or third-party config sources):

{	
	"ZombiesDescription" : "zombies",
	"MaxZombieCount" : 22,
	"EnableAdvancedZombieTracking" : true		
}

And now we need a POCO to contain this grouping of options:

using System;

namespace SampleMicroservice
{
	public class ZombieOptions
	{
		public String ZombiesDescription { get; set; }
		public int MaxZombieCount { get; set; }
		public bool EnableAdvancedZombieTracking { get; set; }
	}
}

We’re almost ready to roll. Next, we need to modify our ZombieController class to use these options. We’re going to continue to follow the inversion of control pattern and simply declare that we now depend on these options being injected from an external source:

namespace SampleMicroservice.Controllers
{		
	[Route("api/zombies")]
	public class ZombieController 
	{
		private IZombieRepository repository;
		private IGlobalCounter counter;
                private ZombieOptions options;
		
		public ZombieController(IZombieRepository zombieRepository, 
								IGlobalCounter globalCounter,
								IOptions<ZombieOptions> zombieOptionsAccessor) {
			repository = zombieRepository;	
			counter = globalCounter;
			options = zombieOptionsAccessor.Value;
		}
		
		
		[HttpGet]
        public IEnumerable<Zombie> Get()
        {
			counter.Increment();
			Console.WriteLine("count: " + counter.Count + 
				", advanced zombie tracking: " + options.EnableAdvancedZombieTracking + 
				", zombie name: '" + options.ZombiesDescription + "'");			
            return repository.ListAll();
        }
	}
}

NOTE that there’s a bug in the current RC1 documentation. In my code, I use zombieOptionsAccessor.Value whereas the docs say this property name is called OptionsValue is the one that compiles on RC1, so YMMV. Also note that we’re not injecting a direct instance of ZombieOptions, we’re injecting an IOptions<T> wrapper around it.

Finally we can modify the Startup.cs to add options support and to configure the ZombieOptions class as an options target:

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();            
            services.AddOptions();
            services.Configure<ZombieOptions>(Configuration);                       
            
            services.AddScoped<IZombieRepository, ZombieRepository>();
            services.AddSingleton<IGlobalCounter, GlobalCounter>();
 }

The important parts here are the call to AddOptions(), which enables the options service, and then the call to Configure<ZombieOptions>(Configuration), which uses the options service to pull values from a source (in this case, Configuration) and stuff them into a DI-injected instance of ZombieOptions.

There is one thing that I find a little awkward with the ASP.NET 5 configuration system: all of your sources and values are merged at the top level namespace. After you add all your sources, all of the top-level property names are now peers of each other in the master IConfiguration instance.

This means that while all the properties of ZombieOptions have meaning together, they are floating around loose at the top level with all other values. If you name one of your properties something like Enabled and someone else has a third party options integration that also has a property called Enabled, those will overlap, and the last-loaded source will set the value for both options classes. If you really want to make sure that your options do not interfere with anyone else’s, I suggest using nested properties. So our previous JSON file would be modified to look like this:

{
  "ZombieConfiguration": {
	"ZombiesDescription" : "zombies",
	"MaxZombieCount" : 22,
	"EnableAdvancedZombieTracking" : true
  }
}

Then the ZombieOptions class would move the three properties down one level below a new parent property called ZombieConfiguration. At this point, you would only have to worry about name conflicts with other config sources that contain properties called ZombieConfiguration.

In conclusion, after this relatively long-winded explanation of ASP.NET 5 configuration options, it finally feels like we have the flexibility we have all been craving when it comes to configuring our applications. As I’ll show in a future blog post, this amount of flexibility sets us up quite nicely for the ability to work both locally and deploy our applications to the cloud and still have rich  application configuration without returning to the dark times of web.config.


Viewing all articles
Browse latest Browse all 27

Trending Articles