How to use immutable objects in ASP.NET Core MVC 5

Take advantage of init-only setters to make configuration options immutable in ASP.NET Core MVC 5 to avoid unnecessary assignments and eliminate opportunities for errors.

How to use immutable objects in ASP.NET Core MVC 5
Thinkstock

Immutability makes code easier to write, test, and maintain over time. However, immutability is not supported by many programming languages. Until recently, C# did not support immutability out-of-the-box. That changes with C# 9.

You can now take advantage of init-only properties to configure your application options as immutable instances. After all, they are basically application constants. You wouldn’t want them to be changed during the lifetime of the application.

This article discusses immutable objects, why we might want to make our configuration information immutable, and how this can be achieved in ASP.NET Core MVC 5.

To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here.

Create an ASP.NET Core MVC 5 project in Visual Studio 2019

First off, let’s create an ASP.NET Core project in Visual Studio. Following these steps will create a new ASP.NET Core MVC 5 project in Visual Studio 2019.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “ASP.NET Core Web App (Model-View-Controller)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
  7. Click Next.
  8. In the “Additional Information” window shown next, select .NET 5.0 as the target framework from the drop-down list at the top. Leave the “Authentication Type” as “None” (default).
  9. Ensure that the check boxes “Enable Docker,” “Configure for HTTPS,” and “Enable Razor runtime compilation” are unchecked, as we won’t be using any of those features here.
  10. Click Create.

A new ASP.NET Core MVC 5 project will be created. We’ll use this project in the subsequent sections of this article.

What is an immutable object?

An immutable object is defined as an object that cannot be changed after it has been created. A string is an example of an immutable type in C#. You cannot change a string object after it has been created. When you attempt to change a string object, a new string instance is created in memory with the new data inside.

By contrast, an instance of StringBuilder is a mutable object. A StringBuilder in C# is a mutable sequence of characters that can be expanded to store more characters if needed. Unlike changing a string, changing a StringBuilder instance does not create a new instance in memory.

For many use cases, such as a database configuration metadata class, immutability is a desirable feature. Another use case for immutability is a data transfer object (DTO). An instance of a DTO is often serialized so that it can be independent of the technology used at the consumer end. Naturally, when transferring a data object between a database and a client, you would like to ensure that the object cannot be changed — and that is exactly the purpose of a DTO.

You also have immutable collections in C#. Immutable collections are collections of types whose members cannot be changed after they have been created. To work with immutable collections, you should install the System.Collection.Immutable NuGet package in your project. You can read more about immutable collections in my earlier article here.

Make IOptions immutable in ASP.NET Core MVC 5

When working in ASP.NET Core MVC 5 you will often store your application’s settings in a file such as appsettings.json and then read them back whenever the application needs them. To read those settings in your application, you can take advantage of dependency injection in the ConfigureServices method of the Startup class using IOptions<T>.

Now consider the following code snippet that shows a class called DbConfiguration, which is used to store database configuration information.

    public class DbConfiguration
    {
        public string Server { get; init; }
        public string Provider { get; init; }
        public string Database { get; init; }
        public int Port { get; init; }
        public string UserName { get; init; }
        public string Password { get; init; }
    }

Note the declarations of the init-only setters for each of the database configuration properties above. Init-only setters allow you to set these values in the usual way, but these properties become read-only once their initial construction has completed.

You can specify the database configuration information in the appsettings.json file as shown below.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "DbConfiguration": {
    "Server": "Server01",
    "Provider": "Provider01",
    "Database": "Database01",
    "Port": 1433,
    "UserName": "User01",
    "Password": "Password01"
  },
  "AllowedHosts": "*"
}

The following code snippet shows how you can bind the database configuration information specified in the appsettings.json file to an instance of DbConfiguration.

services.Configure<DbConfiguration> (options =>
Configuration.GetSection("DbConfiguration").Bind(options));

Alternatively, you can use the following code to Bind and then add the database configuration instance as a singleton service.

var dbConfiguration = new DbConfiguration();
Configuration.Bind("DbConfiguration", dbConfiguration);
services.AddSingleton(dbConfiguration);

Here is the complete code of the ConfigureServices method for your reference:

public void ConfigureServices(IServiceCollection services)
{
   services.AddControllersWithViews();
   services.Configure<DbConfiguration> (options =>
   Configuration.GetSection("DbConfiguration").Bind(options));
}

Read configuration data in the controller in ASP.NET Core MVC 5

In the HomeController class, declare a read-only instance variable of type DbConfiguration as shown below.

private readonly DbConfiguration _dbConfiguration;

To access the DbConfiguration instance from the controller, we’ll take advantage of construction injection as shown in the following code snippet.

public HomeController(ILogger<HomeController> logger, IOptions<DbConfiguration> dbConfiguration)
{
   _logger = logger;
   _dbConfiguration = dbConfiguration.Value;
}

Note the usage of the IOptions<DbConfiguration> instance in the parameter of the constructor above. The DbConfiguration instance named _dbConfiguration is initialized in the constructor using the Value property.

If you now attempt to assign a value to any of the init-only properties or indexers of the DbConfiguration instance, you will be presented with the compile-time error message shown in Figure 1 below.

aspnet core mvc immutable config IDG

Figure 1.

And that’s exactly what we wanted to achieve. The DbConfiguration instance named _dbConfiguration is immutable. You can read values from its properties but you can’t change the value of any of its properties in any way.

Starting with C# 9, you can use init accessors in lieu of set accessors for properties and indexers. Doing so will ensure that these properties are read-only. You can only assign values to those properties at the time of creating the instance of the class they belong to.

How to do more in ASP.NET Core:

Copyright © 2021 IDG Communications, Inc.