Accessing Environment Variables in ASP.Net 5 Apps

Here is an update of this article available. The latest released version of .NET Core introduced a lot of changes which render many of the code examples of this article outdated.

This is a quick walk-through of how to access environmental variables when writing applications using the ASP.NET 5 DNX execution environment.
 
Introduction

When writing web or console applications, you often need to access variables like the directory your app runs in, the version of the hosting environment or environment variables like the TEMP folder location or PATH and USERPROFILE variables.

We might want to store security related information such as database passwords or security tokens in a way that it doesn't end up in a configuration file that is checked into source control.

In .NET 4.x, we used static variables such as AppDomain or ConfigurationManager.AppSettings that could cause many kinds of issues depending on the type and environment your app is running on. With .Net portable libraries this was also not very friendly.

For writing new apps targeting the Core CLR, those constructs are not available anymore but more importantly, we don't need them anymore.

With DNX the ASP.NET team wrote a great host environment for many kinds of apps, but it also comes with many new concepts.

For example, there are various ways to access the variables of the environment our app is running in:
  • Injecting strongly typed, host related data via dependency injection.
  • Using string key, string value pairs via configuration.

Dependency Injection

The ASP.NET team introduced a lightweight DI framework that is also used by DNX to inject things into your apps.

Note - The built-in DI framework supports constructor injection only. It can be replaced by a more heavyweight packages such as Autofaq or Ninject. For this article we will use the build in DI.

One nice feature of DNX is the fact that it can inject useful data to our app's entry point. In case of ASP.NET web apps, we can inject into the Startup.cs's constructor, for consoles, we use the constructor of our Program.cs.

Now, the question is, what do we need to inject and how does that work?

The Microsoft.Framework.Runtime.Abstractions NuGet package provides all the interfaces that are available to us. If you add one of those interfaces to the constructor, the host (DNX) will inject the instantiated concrete implementation at runtime.

The concrete implementation may vary based on which environment our app is running on, for example Windows or Linux.

And that is the beauty of dependency injection and the new framework. At development time we don't need to know and more importantly, we don't need to care about the differences. The framework abstracts this away for us and it will just work. And because those interfaces provide us a strongly typed contract, we can expect that those properties are set.

There are several interfaces that provide you with different environmental information coming from the Microsoft.Framework.Runtime.Abstractions NuGet package as in the following.

  • IApplicationEnvironment
    Provides access to common application information.
  • IRuntimeEnvironment
    Provides access to the runtime environment.
  • IRuntimeOptions
    Represents the options passed into the runtime on boot.

Hint

There are more interfaces like ICompilerOptions or IAssemblyLoader that can be very useful but will not be discussed here. Play around with them yourself!

Example

To write an ASP.NET 5 console app, use Visual Studio 2015 (RC or newer) and the new ASP.NET 5 project template for a console application. This should create a project.json targeting the new DNX 4.5.1 and DNX Core 5.0 and a simple Program.cs.

  1. public class Program  
  2. {  
  3.    public void Main(string[] args)  
  4.    {  
  5.    }  

To use the Microsoft.Framework.Runtime.Abstractions NuGet package in our console app, we must add it as dependency to the project.json file:

  1. "dependencies": {  
  2.    "Microsoft.Framework.Runtime.Abstractions""1.0.0-beta6-*"  
  3. }, 

Note -The version may vary. At the time of writing this article, it was beta6.

Until all this gets released, there might be breaking changes across versions. Be sure that all Framework and System packages are of the same milestone. For example, don't mix beta 4 and beta 5 packages.

Also, be sure you run your app with a version of DNX that matches with the version of the packages you have installed.

Now, we add those interfaces as parameters to the constructor of our app. In the following example we use the console output to print some of the information those interfaces provide:

  1. public Program(IApplicationEnvironment app,  
  2.    IRuntimeEnvironment runtime,  
  3.    IRuntimeOptions options)  
  4.    {  
  5.       Console.WriteLine("ApplicationName: {0} {1}", app.ApplicationName, app.Version);  
  6.       Console.WriteLine("ApplicationBasePath: {0}", app.ApplicationBasePath);  
  7.       Console.WriteLine("Framework: {0}", app.RuntimeFramework.FullName);  
  8.       Console.WriteLine("Runtime: {0} {1} {2}", runtime.RuntimeType, runtime.RuntimeArchitecture, runtime.RuntimeVersion);  
  9.       Console.WriteLine("System: {0} {1}", runtime.OperatingSystem, runtime.OperatingSystemVersion);  
  10.    }  
  11. public void Main(string[] args) { ... } 

Configuration

ASP.NET 5 also comes with a new configuration framework. We will not go into too much detail of how this replaces app/web.config. But in general it is collection of string based key value pairs.

The main NuGet package Microsoft.Framework.Configuration comes with a ConfigurationBuilder class that can be used to combine multiple configuration sources into one collection of key value pairs.

The subsequent packages, like Microsoft.Framework.Configuration.EnvironmentVariables and Microsoft.Framework.Configuration.Json, add extension methods to the ConfigurationBuilder to access specific configuration sources.

Example

For our example, we want to retrieve all the environment variables, like PATH or USERPROFILE and print the key and value to the console output.

In addition to the Microsoft.Framework.Runtime.Abstractions above, we add a dependency reference to Microsoft.Framework.Configuration.EnvironmentVariables to the project.json file.

This package already has a dependency to Microsoft.Framework.Configuration, which means we don't need to reference it explicitly.

  1. "dependencies": {  
  2.    "Microsoft.Framework.Runtime.Abstractions""1.0.0-beta6-*",  
  3.    "Microsoft.Framework.Configuration.EnvironmentVariables""1.0.0-beta6-*",  
  4.    ...  
  5. }, 

In the constructor of our app, we can now instantiate a new ConfigurationBuilder and call the extension methods.

  1. var configuration = new ConfigurationBuilder()  
  2. .AddEnvironmentVariables()  
  3. .Build(); 

To print all the variables to the console we can simply iterate over all the available key/value pairs.

  1. foreach(var config in configuration.GetConfigurationSections())  
  2. {  
  3.    Console.WriteLine("{0}={1}", config.Key, configuration.Get(config.Key));  

There is another thing called user secrets. User secrets are retrieved from the profile of the user account the app's context is instantiated for, for example %APPDATA%\microsoft\UserSecrets\<applicationId>\secrets.json on windows.

Read more about how to configure user secrets on the ASP.NET wiki page.

The concept is the same as the preceding environment variables, you can add the configured user secrets to the key value collection by calling the AddUserSecrets extension on the ConfigurationBuilder.

Usage of Configuration

Both concepts, environment variables and user secrets configuration, can be used to keep security related information away from your source code. We never ever want to check in database passwords or Windows Azure tokens and keys to GitHub or any other source control.

This way, we can define security related settings per environment and read them at runtime and never add those to a configuration file that is checked into the source control!

More Resources and Examples