Injecting Asp.Net Core configuration into Typescript applications

One of the weaknesses of a Typescript application, which just is a Javascript applications, is how to change and maintain the application settings.
A easily configurable system should accomplish the next conditions:

  1. The configuration can be modified at runtime.
  2. The configuration is centralized. That is, that there aren’t multiple configuration files with the same settings.
  3. The changes made are recognizes by the system without the need of compile to apply them.
  4. The changes can be applied without the need of a deployment of all or part of the system.

Some of these requisites already are accomplished by Asp.NET Core, especially in the version 2.1. And others can be resolved easily from server side which is what we are going to see in this article.

There are another possible alternatives: from a static class with the settings hard coded (this would force us to compile), till use a static JSON file which we can modify and it could be read at runtime (though then, we could have more than one file to maintain if we sum the server side configuration file).

Also, with a static JSON file ever we found some issues, like the browser cache and others problems that would prevent we can accomplish with the requisites for a easily maintainable configuration.

In this article, I propose a interesting solution which is very easy to implement, apply to Asp.NET Core applications combine with client applications written in Typescript or Javascript.

Bellow, we are going to see, step by step, how implement this solution.

1. First of all, we have to declare our settings in the configuration file “appSettings.json“, the settings we want to load in our client application. The next sample code show some of the settings we will use in the rest of the steps:

"AppSettings": {
 "ProjectsApiUrl": "",
 "UsersApiUrl": "",
 "EditorSettings": {
 "DefProjectName": "New Project",
 "DefProjectTitle": "Nuevo proyecto",
 "DefUsername": "Anonymous",
 "ZoomSliderMinvalue": "0.1",
 "ZoomSliderMaxValue": "5",
 "ZoomSliderInitialValue": "1.0",
 "ZoomSliderStepValue": "0.1",
 "ScaleIncrementStepValue": "0.2",
 "InitialCanvasWidth": "2000",
 "InitialCanvasHeight": "2000",
 "EnabledDateCalculations": "false",
 "ReleaseVersion": "0.7.4",
 "SelectionLayersLimit": "2"
 }
}

2.In second place, we have to declare the class on which we map the settings will be injected from server side. Logically, this class must have a public property for each “setting” to map and exactly the same name:

export class EditorSettings {
 ProjectName: string;
 ProjectTitle: string;
 Username: string;
 ZoomSliderMinvalue: number;
 ZoomSliderMaxValue: number;
 ZoomSliderInitialValue: number;
 ZoomSliderStepValue: number;
 ScaleIncrementStepValue: number;
 InitialCanvasWidth: number;
 InitialCanvasHeight: number;
 EnabledDateCalculations: boolean;
 ReleaseVersion: number;
 SelectionLayersLimit: number;
}

3.In third place, we configure, on the corresponding controller, the “Action” which launch our client application to it can get injected with the object “Configuration“. We will use the native DI (Dependency Injection) provided by the “Model Binding System” of Asp.NET Core, using the “[FromServices]” attribute:

public class HomeController : Controller
{
 public IActionResult Index([FromServices] IConfiguration configuration)
 {
 return View();
 }
}

This allow us to use later, in the view which do the load and run of our client application (in this case the “_layout.html” shared view), the “@Inject” directive to inject in the view the “Configuration” object, which has also been injected in the server side how we have seen before in the previous code sample.

4. The “@Inject” directive inject in the view the “Configuration” object, it allow us to read the configuration file “appSettings.json” from the view itself. Bellow, taking advantage of we can access to the server side settings, we build a JSON object with the settings trying to keep the hierarchy graph that they have in the “appSettings.json” file. Remember what, inside the “AppSettings” section we have another section named “EditorSettings” which in turn has more settings:

@using Microsoft.Extensions.Configuration;
@inject IConfiguration config;
<script type="text/javascript">
 const appSettings = {
 ProjectsApiUrl: "@config["AppSettings:ProjectsApiUrl"]",
 UsersApiUrl: "@config["AppSettings:UsersApiUrl"]",
 EditorSettings: {
 @foreach(var setting in config.GetSection("AppSettings:EditorSettings").GetChildren()) {
 @Html.Raw(""" + setting.Key + "":"" + setting.Value + "",");
 }
 }
 }
 //Bioreg App EntryPoint.
 $(Start(appSettings));
</script>

5. Once already have a JSON object with our settings on the client side, just we have to map or better said deserialize over the “EditorSettings” class we have declared in the step 2. In the bellow code sample, we can see how it can do using the javascript function “JSON.parse“, which it is part of the native Javascript implementation in the most browsers:

Start(appSettings: any): void {
 //(1) Load application settings.
 this.LoadSettings(appSettings);
 ...
}
//Load appsetings provide by the server into typed settings at runtime.
LoadSettings(appSettings: any) {
 this.Settings = JSON.parse(JSON.stringify(appSettings),
 function (k, v) {
 if (!(typeof v === "object" || isNaN(v))) {
 return parseFloat(v);
 } else {
 switch(v) {
 case "true":
 return true;
 case "false":
 return false;
 default:
 return v;
 }
 
 }
 }
 );
 
}

The function “JSON.parse” can’t do by itself the conversion to numeric or boolean values from strings. But, it allow pass as second argument a function to do that conversion. Reviewing the previous code sample, we can see that the string values which support numeric validation has been converted to primitive  numeric or boolean values:

image

Finally, a code sample that use our injected settings, more or less typed. In the showed sample there’s a “Runtime” class which contains a “Settings” property over which the JSON object with the injected settings is deserialized. The “Runtime” class is accessed from the object “AppRuntime“:

GetProjects(): Projects.Project[] {
 try {
 let projects = new Array<Projects.Project>();
 fetch(AppRuntime.Settings.ProjectsApiUrl + "api/projects") 
 .then(function (response) {
 ...
 })
 .catch(function (error) {
 ...
 })
 }
 catch{
 return null;
 }

}
...
const zoomSlider: any = {
 ...
 value: AppRuntime.Settings.EditorSettings.ZoomSliderInitialValue,
 step: AppRuntime.Settings.EditorSettings.ZoomSliderStepValue,
 min: AppRuntime.Settings.EditorSettings.ZoomSliderMinvalue,
 max: AppRuntime.Settings.EditorSettings.ZoomSliderMaxValue,
 ...
}

This is, for me, a more than interesting alternative for configuring client applications written in Typescript or Javascript. It allow centralize the place where it find our application configuration, both for server side and client side. And allow us to use our settings in a typed mode using object notation.

Note, that you should be careful with the values you include in the settings that will be injected, because all of these values could be visible using the browser developer tools.

I hope sincerely that this solution will be, for you, so interesting like is for me.

Leave a Reply

Your email address will not be published. Required fields are marked *