ASP.NET MVC: Storing application configuration in JSON-file with dependency injection

en-US | created at: 12/13/2017 | published: 12/14/2017 | updated at: 1/2/2018 | number of views: 2001

In this article I'm going to show how to storing application settings in JSON-file instead of using web.config. Also one of tasks is availability dependency injection for application settings.

As an introduction

All my friend often talk to me that I need wrote articles in English. I took advantage of their advice and this is my first post in English. Please, feel free to correct me if you found a grammar mistake or syntax error. I have experience of developing over 20 years and I think the time is come to share my experience with English talking community.

The tasks for the article

As mentioned in description, I have some goals for this article. I want to create an application settings that:

  1. can be injected in a constructor
  2. can be refreshed without restart IIS (pool AppDomain)
  3. can be vary between build configurations (Release, Debug, etc.)
  4. can be choice to store in JSON or XML formats
  5. can be raise an event that configuration already loaded
  6. can be store loaded settings in memory (cache)
  7. can be reset and reload from file
  8. can be simply installed and deploy
  9. be strongly typed

I forgot mentioned is all of tasks should works on any platforms: WPF, ASP.NET MVC, ASP.NET WebForms, WinForms, etc. Let's do it.

Calabonga.Configuration

To solve task number 8 from the task list, I created the nuget-package Calabonga.Configuration. As you know in ASP.NET Core the configuration storing in JSON-format in file appsettings.json. I had use JSON-format for my solutions for any platforms even before ASP.NET Core. Now, I'm going to create a project for demonstration how to use JSON-configiguration on ASP.NET MVC 5.

First of all, after Visual Studio creates solution we should update current nuget-packages.

PM> Update-Package

After that we need to install new package Calabonga.Configuration:

PM> Install-Package Calabonga.Configuration

It's time to create our configuration class for application. I came up with the properties that could be in my application. The name of my class is ApplicationSettings.cs:

/// <summary>
/// System Configuration 
/// </summary>
public class ApplicationSettings
{
    public string AdministrtorEmail { get; set; }
    public int DeafultPageSize { get; set; }
    public string ApplicationName { get; set; }
    public string ApplicationDomain { get; set; }
    public string SecretKey { get; set; }
    public EmailServer EmailServer { get; set; }
}
 
/// <summary>
/// System Configuration: Email Server
/// </summary>
public class EmailServer
{
    public string Host { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public int Port { get; set; }
}

By default, the Calabonga.Configuration search file Config.json in the application root folder. Let's create this file:

{
    "AdministrtorEmail": "admin@yahoo.eu",
    "DeafultPageSize": 10,
    "ApplicationName": "Demonstration",
    "ApplicationDomain": "www.domian.com",
    "SecretKey": "SuperSecretKey",
    "EmailServer": {
        "Host": "smtp.yandex.ru",
        "UserName": "",
        "Password": "SuperSecretPassword",
        "Port": 956
    } 
}

The next step, we should create SettingsManager.cs that will be used for dependency injection. SettingsManager should be derived from Configuration<T> class. As generic parameter I set up our ApplicationSettings class.

/// Configuration manager
/// </summary>
public class SettingsManager : Configuration<ApplicationSettings>
{
    public SettingsManager(IConfigSerializer serializer, ICacheService cacheService) 
        : base(serializer, cacheService)
    {
    }
}

Look at the constructor of the class created. There are two special types IConfigSerializer and ICacheService. These interfaces with their implementations already included to the Calabonga.Configuration library. By the way, there are two implementations for IConfigSerializer interface already exists in Calabonga.Configuration. The first is JsonConfigSerializer. The second one is XmlConfigSerializer. The name of the serializer speaks for itself. If you decide to create your own implementation of the mentioned interfaces, you can do it without any problem. In this case you will need to register your implementations in DI-container. Stop... I forgot about DI-container! Let's create it.

Dependency Injection Container

I prefer to use Autofac container, but you can use any other. First of all, we need to install nuget-package:

PM> Install-Package Autofac.Mvc5

Next, we should create container configuration.

/// <summary>
/// Autofac container configuration
/// </summary>
public static class DependencyContainer
{
    public static void Initialize()
    {
        var builder = new ContainerBuilder();
        builder.RegisterControllers(Assembly.GetExecutingAssembly());
 
        // Calabonga.Configuration injections
        builder.RegisterType<CacheService>().As<ICacheService>();
        builder.RegisterType<JsonConfigSerializer>().As<IConfigSerializer>();
        builder.RegisterType<SettingsManager>().AsSelf();
 
        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
    }
}

In line 12 and 13 we are registering two classes with their default implementations. In line 14 I registered SettingsManager as self. After that we need to initialize DI-container in global.asax.cs file, which is the composition root for ASP.NET MVC project. 

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        DependencyContainer.Initialize();
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

As a matter of fact, all works for integration done. We are ready to inject ApplicationSettings to controller, for example, to the HomeController.

public class HomeController : Controller
{
    private readonly ApplicationSettings _settings;
 
    public HomeController(SettingsManager settings)
    {
        _settings = settings.Config;
    }
    public ActionResult Index()
    {
        return View();
    }
 
    /* cutted for briefly  */
}

Starting the project...

Customization

Look at the previous screenshot. Properties DirectoryName and Filename are customizable. You can set up the folder whatever you want. For example:

You just need to override this properties to set your own place to store the json-configuration. 

Building configuration

Perhaps you want to use different application settings for a few configurations like DEBUG or RELEASE or other thing. You can apply something like that.

Complicate configuration

In conclusion, I would like to propose more advanced solution for DI-container configurations. Let's create the ISettingsManager

/// <summary>
/// Settings manager
/// </summary>
public interface ISettingsManager
{
    ApplicationSettings Config { get; }
}

The next step, apply this interface to SettingsManager

/// <summary>
/// Configuration manager
/// </summary>
public class ProductionSettingsManager : Configuration<ApplicationSettings>, ISettingsManager
{
    public ProductionSettingsManager(IConfigSerializer serializer, ICacheService cacheService)
        : base(serializer, cacheService)
    {
    }
 
    public override string DirectoryName
    {
        get { return HttpContext.Current.Server.MapPath("~/Configurations"); }
    }
 
    public override string FileName
    {
#if !DEBUG
        get { return "appsettings.production.json"; }
#else
        get { return "appsettings.json"; }
#endif
    }
}

After that, we can create another configuration with ISettingsManager implementation (see line 4).

/// <summary>
/// Configuration manager
/// </summary>
public class LocalSettingsManager : Configuration<ApplicationSettings>, ISettingsManager
{
    public LocalSettingsManager(IConfigSerializer serializer, ICacheService cacheService)
        : base(serializer, cacheService)
    {
    }
 
    public override string DirectoryName
    {
        get { return HttpContext.Current.Server.MapPath("~/Configurations"); }
    }
 
    public override string FileName
    {
 
        get { return "appsettings.local.json"; }
    }
}

Now we have two configurations, and they can be in general different assemblies and get into the container when registering modules (plugins). For our case, I will register them in the same container with different names (Autofac easily allows you to do this):

/// <summary>
/// Autofac container configuration
/// </summary>
public static class DependencyContainer
{
    public static void Initialize()
    {
        var builder = new ContainerBuilder();
        builder.RegisterControllers(Assembly.GetExecutingAssembly());
 
        // Calabonga.Configuration injections
        builder.RegisterType<CacheService>().As<ICacheService>();
        builder.RegisterType<JsonConfigSerializer>().As<IConfigSerializer>();
        builder.RegisterType<LocalSettingsManager>().Keyed<ISettingsManager>("LOCAL");
        builder.RegisterType<ProductionSettingsManager>().Keyed<ISettingsManager>("PRODUCTION");
 
        var container = builder.Build();
 
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
    }
}

After registering the configurations in this way, you can add them, for example to the controller, all at once and use the one that you like best:

public class HomeController : Controller
{
    private readonly ApplicationSettings _settingsProd;
    private readonly ApplicationSettings _settingsLocal;
 
    public HomeController(IIndex<string, ISettingsManager> configurations)
    {
        _settingsProd = configurations["PRODUCTION"].Config;
        _settingsLocal = configurations["LOCAL"].Config;
    }
 
    public ActionResult Index()
    {
        var emailServer = _settingsProd.EmailServer;
        var secretKey = _settingsLocal.SecretKey;
 
        return View();
    }
 
    /* cutted for briefly  */
}

Nothing prevents using parameters from different configurations, as you see in line 14 and line 15.

Additional information

I can add the following, the Calabonga.Configuration assembly has in its arsenal the following methods and events:

Reload() - allow "on the fly" to reload the data from the configuration file.
ReadValue <TValue> (Expression <Func <T, TValue >> e) - read the value from the parameter (as lambda expression)
ReadValue <TValue> (string propertyName) - read the value by the name of the parameter.
SaveChanges() - save (serialize) the configuration to a file.
public event ConfigurationLoadedEventHandler <T> ConfigurationLoaded - event that fires when only the data was reloaded from the configuration file.

This set is enough to implement a model of behavior of any complexity. And if you add the capabilities of a DI-container (lifecycle managment, IModule, XML configuration without explicite referrence and other features) to all of this, it allows not only to solve any questions, but also to choose from several solutions.

Links

Calabonga.Configuration nuget-package

Demo solution on the Github

Russian version of this post

No comments yet

What is your name?
for feedback and subscription only
example: https://www.calabonga.com
  1. By sending a comment and providing personal information to the site, you agree with the Privacy Policy , which is used on the site.
  2. All comments are moderated for the presence of idiomatic expressions and obscene words. The link tags will be removed from the message body.