Pass ASP.NET Core Appsettings Values to Angular via an API Call
There have been a few issues opened on the repo I have showing usage of Angular, Identity Server 4, and ASP.NET Core together that related to incompatibilities with the newer versions of Angular. In an effort to fix this issue the plan was to recreate the client application using the new Angular template from Microsoft which from what I read should address the issue.
The code before any changes can be found here, but in this case, the whole client application has been recreated so the starting point may not be super helpful.
The Problem
For the most part, this worked well, but the problem can when I needed to use some configuration values from ASP.NET Core in my new Angular application. The previous versions of the template used server-side rendering which I utilized to pass the configuration values. The new template doesn’t use server-side rendering by default and I wanted to find a way to solve the issue without requiring server-side rendering.
The other issue is that I want to be able to run this application in Azure and set the configuration values as environment variables. While Angular seems to have support for environment files finding a solution that used a systems environment variables turned out too not be simple.
Configuration API Endpoint
Since the configuration values I need to get to the client application are secret I decided to go the route of pulling them via an API call back to the same ASP.NET Core application that is hosting the Angular Application, which is the Client App project in the sample solution.
I added a ConfigurationController.cs
class to the Controller
directory with the following contents.
[Produces("application/json")] [Route("api/Configuration")] public class ConfigurationController : Controller { private readonly IConfiguration _configuration; public ConfigurationController(IConfiguration configuration) { _configuration = configuration; } [HttpGet("[action]")] public IActionResult ConfigurationData() { return Ok(new Dictionary<string, string> { { "IdentityServerAddress", _configuration["IdentityServerAddress"] }, { "ApiAddress", _configuration["ApiAddress"] } }); } }
This controller gets constructed with a reference to the application’s configuration which is then used to populate a dictionary with the values my Angular application needs. For completeness, the following is the contents of the application’s appsettings.json
file.
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "IdentityServerAddress": "http://localhost:5000", "ApiAddress": "http://localhost:5001/api/" }
Angular Changes
This is the part that I really struggled to get right. I needed the configuration values from the API above to be available as soon as possible. Thankfully I came across this blog post by Juri Strumpflohner which covers using Angular’s APP_INITIALIZER
.
The first thing I need was to create a class in Angular to get the configuration values from the API and serve to them the rest of the Angular application. To do this I added a configuration.service.ts
into a new ClientApp/src/app/configuration
directory. The full class follows.
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class ConfigurationService { private configuration: IServerConfiguration; constructor(private http: HttpClient) { } loadConfig() { return this.http.get<IServerConfiguration>('/api/Configuration/ConfigurationData') .toPromise() .then(result => { this.configuration = <IServerConfiguration>(result); }, error => console.error(error)); } get apiAddress() { return this.configuration.ApiAddress; } get identityServerAddress() { return this.configuration.IdentityServerAddress; } } export interface IServerConfiguration { ApiAddress: string; IdentityServerAddress: string; }
This class hits the API to get the configuration values in the loadConfig
function and maps it to a class level field. It also provides properties to get the individual configuration values.
As I mentioned above, getting the application to get these configuration values in a timely matter was something I really struggled to do. The first step to using Angular’s APP_INITIALIZER
to solve this issue is to change the import from @angular/core
to include APP_INITIALIZER
and to import the ConfigurationService
. All these changes are being made in the app.module.ts
file.
import { NgModule, APP_INITIALIZER } from '@angular/core'; import { ConfigurationService } from "./configuration/configuration.service";
Next, we need to define a function that will call the ConfigurationService.loadConfig
function.
const appInitializerFn = (appConfig: ConfigurationService) => { return () => { return appConfig.loadConfig(); }; };
Finally, in the providers
array add an element for the APP_INITIALIZER
and the ConfigurationService
.
providers: [ ConfigurationService, { provide: APP_INITIALIZER, useFactory: appInitializerFn, multi: true, deps: [ConfigurationService] }]
Wrapping Up
This is one of those things that turned out to be way more complicated than I expected. Thankfully with the above changes, I was able to get it working. I hope this saves you all some time. The code with all the changes can be found here.
Pass ASP.NET Core Appsettings Values to Angular via an API Call Read More »