Tag Archives: .NET Core

Managing ASP .NET Core Settings in Multiple Environments

One of the many benefits offered by .NET Core over its predecessor (the older .NET Framework) is the way it handles configuration. I wrote about the capabilities of .NET Core configuration back when the new framework was still prerelease and using a different name (see ASP .NET 5 Application Configuration, Part 1 and Part 2), and although some APIs may have changed, most of it should still be relevant today.

It is easy to set up application settings with this recent configuration model. However, when it comes to actually deploying an application into different environments (e.g. development, staging, production, and possibly others), things become complicated. How do we maintain configurations for all these environments, and how do we save ourselves from the tedious and error-prone practice of manually tweaking individual settings on all these different servers? How do we make sure we don’t lose these settings outright if a server experiences a technical failure? These challenges have nothing to do with the configuration model itself, as they are a more general administrative burden.

One option is to use something like Octopus Deploy to store settings for different environments and transform a settings file (such as appsettings.json) at deployment time. However, not everybody has this luxury. In this article, we will see how we can manage configurations for multiple environments using features that .NET Core offers out of the box.

At the time of writing this article, .NET Core 3.1.1 is the latest version.

Stacking Configurations in .NET Core

The .NET Core configuration libraries allow you to combine application settings from different sources, even if these are of different types (e.g. JSON, XML, environment variables, etc). Imagine I have these two JSON files, named appsettings1.json and appsettings2.json:

{
    "ApplicationName": "Some fancy app",
    "Timeout": 5000
}
{
    "Timeout": 3000,
    "ConnectionString": "some connection string"
}

In order to read these files, I’ll need to install the following package:

dotnet add package Microsoft.Extensions.Configuration.Json

We can then use the ConfigurationBuilder to read in both files, combine them, and give us back an IConfigurationRoot object that allows the application code to query the settings that were read:

using System;
using Microsoft.Extensions.Configuration;

namespace netcoreconf1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new ConfigurationBuilder()
                .AddJsonFile("appsettings1.json")
                .AddJsonFile("appsettings2.json")
                .Build();

            Console.WriteLine("Application Name: " + config["ApplicationName"]);
            Console.WriteLine("Timeout:          " + config["Timeout"]);
            Console.WriteLine("Connection String " + config["ConnectionString"]);

            Console.ReadLine();
        }
    }
}

After ensuring that the two JSON files are set to copy to the output directory on build, we can run the simple application to see the result:

The Application Name setting comes from the first JSON file, while the Connection String setting comes from the second. The Timeout setting, on the other hand, exists in both files, but the value was obtained from the second JSON file. In fact, the order in which configuration sources are read is important, and by design, settings read from later sources will overwrite the same settings read from earlier sources.

It follows from this that if we have some variable that defines which environment (e.g. Production) we’re in, then we can do something like this:

            const string environment = "Production";

            var config = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddJsonFile($"appsettings.{environment}.json", optional: true)
                .Build();

In this case we have a core JSON file with the settings that tend to be common across environments, and then we have one or more JSON files specific to the environment that we’re running in, such as appsettings.Development.json or appsettings.Production.json. The settings in the environment-specific JSON file will overwrite those in the core appsettings.json file.

You will notice that we have that optional: true parameter for the environment-specific JSON file. This means that if that file is not found, the ConfigurationBuilder will simply ignore it instead of throwing an exception. This is the default behaviour in ASP .NET Core, which we will explore in the next section. It is debatable whether this is a good idea, because it may be perfectly reasonable to prefer the application to crash rather than run with incorrect configuration settings.

Multiple Environments in ASP .NET Core Using Visual Studio

By default, ASP .NET Core web applications use this same mechanism to combine a core appsettings.json file with an environment-specific appsettings.Environment.json file.

In the previous section we used a constant to supply the name of the current environment. Instead, ASP .NET Core uses an environment variable named ASPNETCORE_ENVIRONMENT to determine the environment.

Let’s create an ASP .NET Core Web API using Visual Studio and run it to see this in action:

Somehow, ASP .NET Core figured out that we’re using the Development environment without us setting anything up. How does it know?

You’ll find the answer in the launchSettings.json file (under Properties in Solution Explorer), which defines the aforementioned environment variable when the application is run either directly or using IIS Express. You’ll also find that there are already separate appsettings.json and appsettings.Development.json files where you can put your settings.

If you remove this environment variable and re-run the application, you’ll find that the default environment is Production.

On the other hand, if we add a different appsettings.Staging.json, and update the environment variable to Staging, then we can run locally while pointing to the Staging environment:

Naturally, connecting locally to different environments isn’t something you should take lightly. Make sure you know what you’re doing, as you can do some real damage on production environments. On the other hand, there are times when this may be necessary, so it is a simple and powerful technique. Just be careful.

“With great power comes great responsibility.”

— Uncle Ben, Spider-Man (2002)

Multiple Environments in Console Apps

While ASP .NET Core handles the configuration plumbing for us, we do not have this luxury in other types of applications. Console apps, for instance those built to run as Windows Services using Topshelf, will need to have this behaviour as part of their code.

In a new console application, we will first need to add the relevant NuGet package:

dotnet add package Microsoft.Extensions.Configuration.Json

Then we can set up a ConfigurationBuilder to read JSON configuration files using the same stacked approach described earlier:

            var config = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddJsonFile($"appsettings.{environment}.json")
                .Build();

We can read the environment from the same ASPNETCORE_ENVIRONMENT environment variable that ASP .NET Core looks for. This way, if we have several applications on a server, they can all determine the environment from the same machine-wide setting.

            string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

            if (environment != null)
            {
                var config = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.json")
                    .AddJsonFile($"appsettings.{environment}.json")
                    .Build();

                // TODO application logic goes here
            }
            else
            {
                Console.WriteLine("Fatal error: environment not found!");
                Environment.Exit(-1);
            }

If we run the application now, we will get that fatal error. That’s because we haven’t actually set up the environment variable yet. See .NET Core Tools Telemetry for instructions on how to permanently set an environment variable on Windows or Linux. Avoid doing this via a terminal or command line window since that setting would only apply to that particular window. I’m doing this in the screenshot below only as a quick demonstration, since I don’t need to maintain this application.

Deploying an ASP .NET Core Web Application to a Windows Server

When developing applications locally, we have a lot of tools that make our lives easy thanks to whichever IDE we use (e.g. Visual Studio or Visual Studio Code). Deploying to a server is different, because we need to set everything up ourselves.

The first thing to do is install .NET Core on the machine. Download the ASP .NET Core Hosting Bundle as shown in the screenshot below. This includes the Runtime (which allows you to run an .exe built with .NET Core) and the ASP .NET Core Module v2 for IIS (which enables you to host ASP .NET Core web applications in IIS). However, it does not include the SDK, so you will not be able to use any of the command-line dotnet tools, and even dotnet --version will not let you know whether it is set up correctly.

Next, we can set up a couple of system environment variables:

The first is ASPNETCORE_ENVIRONMENT which has already been explained ad nauseam earlier in this article. The second is DOTNET_CLI_TELEMETRY_OPTOUT (see .NET Core Tools Telemetry), which can optionally be used to avoid sending usage data to Microsoft since this behaviour is turned on by default.

Another optional preparation step that applies to web applications is to add health checks. This simply means exposing an unprotected endpoint which returns something like “OK”. It is useful to check whether you can reach the web application at a basic level (while eliminating complexities such as authentication), and it can also be used by load balancers to monitor the health of applications. This can be implemented either directly in code, or using ASP .NET Core’s own health checks feature.

Finally, you really should set up logging to file, and log the environment as soon as the application starts. Since ASP .NET Core does not have a file logger out of the box, you can use third party libraries such as NLog or Serilog. Like this, if the application picks up the wrong environment, you can realise very quickly. The log files will also help you monitor the health of your application and troubleshoot issues. Use tools such as baretail to monitor logs locally on the server, or ship them to a central store where you can analyse them in more detail.

With everything prepared, we can publish our web application:

dotnet publish -c Release -r win10-x64

All that is left is to copy the files over to the server (compressing and decompressing them in the process) and run the application.

The above screenshot shows the deployed ASP .NET Core web application running, serving requests, and picking up the correct configuration. All this works despite not having the .NET Core SDK installed, because it is not required simply to run applications.

Deploying an ASP .NET Core Web Application to IIS

In order to host an ASP .NET Core web application in IIS, the instructions in the previous section apply, but there are a few more things to do.

First, if the server does not already have IIS, then it needs to be installed. This can be done by going to:

  1. Server Manager
  2. Add roles and features
  3. Next
  4. Next
  5. Next
  6. Select Web Server (IIS) as shown in the screenshot below.
  7. Click Add Features in the modal that comes up.
  8. Next
  9. Next
  10. Next
  11. Next
  12. Install

In IIS, make sure you have the AspNetCoreModuleV2 module, by clicking on the machine node in the Connections panel (left) and then double-clicking Modules. If you installed IIS after having installed the ASP .NET Core Hosting Bundle, you will need to run the latter installer again (just hit Repair).

Next, go into IIS and set up a website, with the path pointing to the directory where you put the web application’s published files:

Start your website, and then visit the test endpoint. Since you don’t have a console window when running under IIS, the log files come in really handy. We can use them to check that we are loading configuration for the right environment just as before:

It’s working great, and it seems like from .NET Core 3+, it even logs the hosting environment automatically so you don’t need to do that yourself.

Troubleshooting

When running under IIS, an ASP .NET Core application needs a web.config file just like any other. While I’ve had to add this manually in the past, it seems like they are now being created automatically when you publish. If, for any reason, you’re missing a web.config file, you can grab the example in the docs.

I ran into a problem with an IIS-hosted application under .NET Core 2.2 where the environment variable defining the hosting environment wasn’t being picked up correctly by ASP .NET Core. As a workaround, it is actually possible to set environment variables directly in web.config, and they will be passed by IIS to the hosted application.

On the other hand, when running .NET Core applications under Linux, keep in mind that files are case sensitive. Andrew Lock has written about a problem he ran into because of this.

Summary

In this article, we have seen that the old way of transforming config files is no longer necessary. By stacking configuration files, we can have a core appsettings.json file whose settings is overwritten by other environment-specific JSON files.

This setup is done automatically in ASP .NET Core applications, using the ASPNETCORE_ENVIRONMENT environment variable to determine the current environment. In other types of apps, we can read the same environment variable manually to achieve the same effect. Under Visual Studio, this environment variable can easily be changed in launchsettings.json to work under different environments, as long as the necessary level of care is taken.

Deployment of ASP .NET Core applications requires the .NET Core Runtime to be installed on the target server. The ASP .NET Core Hosting Bundle includes this as well as support for hosting ASP .NET Core applications under IIS. The SDK is not required unless the dotnet command-line tools need to be used on the server.

Before deploying, the server should also have the right environment variables, and the application should be fitted with mechanisms to easily check that it is working properly (such as an open endpoint and log files).

AWS Lambda .NET Core 2.1 Support Released

Amazon Web Services (AWS) has just announced that its serverless function offering, AWS Lambda, now supports the .NET Core 2.1 runtime, which was released towards the end of May 2018.

Quoting the official announcement:

“Today we released support for the new .NET Core 2.1.0 runtime in AWS Lambda. You can now take advantage of this version’s more performant HTTP client. This is particularly important when integrating with other AWS services from your AWS Lambda function. You can also start using highly anticipated new language features such as Span<T> and Memory<T>.

“We encourage you to update your .NET Core 2.0 AWS Lambda functions to use .NET Core 2.1 as soon as possible. Microsoft is expected to provide long-term support (LTS) for .NET Core 2.1 starting later this summer, and will continue that support for three years. Microsoft will end its support for .NET Core 2.0 at the beginning of October, 2018[2]. At that time, .NET Core 2.0 AWS Lambda functions will be subject to deprecation per the AWS Lambda Runtime Support Policy. After three months, you will no longer be able to create AWS Lambda functions using .NET Core 2.0, although you will be able to update existing functions. After six months, update functionality will also be disabled.

“[1] See Microsoft Support for .NET Core for the latest details on Microsoft’s .NET Core support.
“[2] See this blog post from Microsoft about .NET Core 2.0’s end of life.”

The choice here seems obvious: upgrade and get faster HttpClient, new language features, and long-term support; or lose support for your functions targeting .NET Core 2.0 (whatever that actually means).

In order to migrate to .NET Core 2.1, you’ll need the latest tooling – either version 1.14.4.0 of the AWS Toolkit for Visual Studio, or version 2.2.0 of the Amazon.Lambda.Tools NuGet package.

Check out the official announcement at the AWS blog for more information, including additional tips on upgrading.

Accessing an ASP .NET Core Web Application Remotely

After setting up an empty ASP .NET Core Web Application, it’s easy to quickly run it and see something working, in the form of the usual “Hello World”:

When trying to deploy this somewhere though, you might be disappointed to notice that you can’t access the web application from another machine:

In fact, you’ll notice that you can’t even access it from the same machine if you use the actual hostname rather than localhost.

This is because by default, Kestrel will listen only on localhost. In order for another machine to access the web application using the server’s hostname, the web application must specify the endpoints on which Kestrel will listen to, using code or command-line arguments.

Note: you may also need to open a port in your firewall.

In code, this can be done by invoking UseUrls() in the webhost builder as follows:

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseUrls("http://myhostname:54691")
                .Build();

Replace “myhostname” with the hostname of the server, and note that the localhost endpoint will still work even though it’s not specified explicitly here.

If you want to pass the the endpoint(s) via command line parameters instead, you can do so via the --urls argument. First, you need to change the BuildWebHost() method generated by the project template as per this GitHub comment, to allow command line parameters to be passed to the WebHostBuilder via configuration:

public static IWebHost BuildWebHost(string[] args)
{
    var configuration = new ConfigurationBuilder().AddCommandLine(args).Build();

    return WebHost.CreateDefaultBuilder(args)
        .UseConfiguration(configuration)
        .UseStartup<Startup>()
        .Build();
}

Then, use the --urls argument when invoking dotnet run:

dotnet run --urls http://banshee:54691/

Either of these methods is fine to allow remote machines to access your ASP .NET Core web application.

.NET Core 3 to Support Desktop Applications… Kind of

A few days ago, Microsoft published a blog post titled “.NET Core 3 and Support for Windows Desktop Applications“. Just by reading the title, I’m pretty sure many of us jumped in their seats as thoughts like “WPF on Linux” became a source of excitement.

Image source: .NET Core 3 and Support for Windows Desktop Applications

Unfortunately however, the excitement turns into a disappointed “Oh. [Awkward silence] OK.” when reading that although .NET Core 3 is planned to support desktop applications built on technologies like Windows Forms and WPF, this support is for Windows only:

“Support for Windows desktop will be added as a set of “Windows Desktop Packs”, which will only work on Windows. .NET Core isn’t changing architecturally with this new version. We’ll continue to offer a great cross-platform product, focused on the cloud. We have lots of improvements planned for those scenarios that we’ll share later.”

The article does mention that this will bring several benefits ranging from performance improvements to deployment options, but this pales in comparison to the prospect of going cross-platform.

But given that they “are planning on releasing a first preview of .NET Core 3 later this year and the final version in 2019”, I can only wonder why they would spend a year doing a huge amount of work that most people won’t even care about, and pass it as a major release of .NET Core.

Time will tell, but one can get an idea of how people feel about this from the comments in the blog post.

My guess is that this could be part of a long-term strategy to retire the full .NET Framework, rather than bringing any real value to .NET Core or desktop applications.

Reading RabbitMQ Settings Using .NET Core Configuration

The .NET Core Configuration system is extremely powerful and flexible. One of the features that I use the most is its capability to bind structured settings from a source (e.g. JSON file) to a C# object.

A very good example of this is obtaining RabbitMQ settings so that you can populate the ConnectionFactory. In the past, I’ve created a DTO (class) for this, and a parser that could populate this class based on a connection string format that I invented on the spot out of necessity. The good news is that you don’t have to do this any more. .NET Core configuration allows you to bind your config to an object, even if it’s coming from a third party library. Let’s see how.

Typical RabbitMQ Configuration

First, in order to use RabbitMQ, we need to install the RabbitMQ Client NuGet package.

Install-Package RabbitMQ.Client

Next, we’ll typically create a connection by means of the ConnectionFactory. We’ll need to populate the necessary fields, whether directly or by reading them from config. Technically, most of the settings below are not necessary because defaults are assumed if not provided, but we’ll include them anyway as we’re not assuming everyone is connecting to RabbitMQ on localhost.

            var connectionFactory = new ConnectionFactory()
            {
                HostName = "localhost",
                UserName = "guest",
                Password = "guest",
                VirtualHost = "/",
                AutomaticRecoveryEnabled = true,
                RequestedHeartbeat = 30
            };

If we use .NET Core configuration, we don’t even need to do this any more.

Connection Settings From JSON File

Let’s start by adding a new text file to the project called appsettings.json. From its properties, change it to copy to the output directory on build (Copy always and Copy if newer are both fine). In the file, we’ll add the JSON equivalent of what we have in ConnectionFactory above:

{
  "RabbitMqConnection": {
    "HostName": "localhost",
    "Username": "guest",
    "Password": "guest",
    "VirtualHost": "/",
    "AutomaticRecoveryEnabled": true,
    "RequestedHeartbeat": 30
  }
}

Now, we need a way to read this JSON file and bind it to a ConnectionFactory object. To do that, we need the following NuGet packages:

Install-Package Microsoft.Extensions.Configuration
Install-Package Microsoft.Extensions.Configuration.Json
Install-Package Microsoft.Extensions.Configuration.Binder

The .NET Core configuration system is split into multiple packages, so you can bring in only what you actually need. The first package is the heart of the framework, and you don’t need to install it directly because the second and third packages will both bring it in as a dependency when you install them. As for the Json and Binder packages, we’ll see what they do in a minute.

.NET Core configuration is loaded by means of a ConfigurationBuilder object. In our case, we’ll have:

            var config = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();

The AddJsonFile() extension method is provided by the Json package (the second one we installed earlier). The result of this is an object which implements IConfigurationRoot, and we can use this to read our settings.

Next, we’ll prepare an empty ConnectionFactory object that the binder will populate from the configuration in the next step.

var connectionFactory = new ConnectionFactory();

Finally, we can bind the entire “RabbitMqConnection” section of the appsettings.json file to our ConnectionFactory object, using the Bind() method (provided via the Binder package we installed earlier):

config.GetSection("RabbitMqConnection").Bind(connectionFactory);

If the key-value pairs in the JSON section match properties on the connectionFactory object, they will be set. You’ll know it worked because RequestedHeartbeat has a default value of 60, but it will be overridden by the value of 30 from appsettings.json.

Testing Connectivity

Now that you are populating the ConnectionFactory, you can connect in the same way as you used to before. This should suffice, as you’ll get an exception if your connection settings are incorrect:

            using (var conn = connectionFactory.CreateConnection())
            {
                Console.WriteLine("Connected!");

                // ...                

                Console.ReadLine();
            }

But if you want to make damn sure that you can actually interact with RabbitMQ, you can write a minimal consumer, and then send it messages via the Management Plugin’s Web UI:

            using (var conn = connectionFactory.CreateConnection())
            using (var channel = conn.CreateModel())
            {
                Console.WriteLine("Connected!");

                const string queueName = "madrid";
                channel.QueueDeclare(queueName, true, false, false, null);

                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += (s, a) => Console.WriteLine("Message received!");
                channel.BasicConsume(queueName, true, consumer);

                Console.ReadLine();
            }

Wondering about the name of the queue? It’s because I found lots of them in Madrid. No kidding:

Summary

The point here was to show you how you can read settings from a section of a JSON file and have it directly deserialized into an object, using the binder feature of .NET Core configuration. The example here is specific to RabbitMQ, but you can use the same approach with any class you like, as long as the properties have public setters.

Also, remember that .NET Core configuration actually targets .NET Standard. That means you can use it not only with .NET Core apps, but also in the full .NET Framework, and any other compatible runtimes.