Pass ASP.NET Core Appsettings Values to Angular
As part of getting my set of Identity Server 4 sample applications to run in Azure, I needed a way in the Client Application to pass some configuration values from appsettings.json to the Angular front end that could be used both during server-side rendering and client-side rendering. This application is using JavaScriptServices. This solution may need tweaking if your application isn’t using JavaScriptServices. The code for the client application can be found here.
Settings
In this example, we need to pass the address of our Identity Server and API from appsettings.json to Angular. The following is the settings file for this example.
{ "Logging": { "IncludeScopes": false, "Debug": { "LogLevel": { "Default": "Warning" } }, "Console": { "LogLevel": { "Default": "Warning" } } }, "IdentityServerAddress": "http://localhost:5000", "ApiAddress": "http://localhost:5001/" }
Providing Configuration Data to Angular
In this application, Angular is loaded from the index action of the home controller. This view can be found in the Views/Home folder in the Index.cshtml file. The following is the file before any changes.
@{ ViewData["Title"] = "Home Page"; } <app asp-prerender-module="ClientApp/dist/main-server">Loading...</app> <script src="~/dist/vendor.js" asp-append-version="true"></script> @section scripts { <script src="~/dist/main-client.js" asp-append-version="true"></script> }
The first change needed is to inject the configuration data using ASP.NET Core’s DI system. Add the following two lines at the top of the file.
@using Microsoft.Extensions.Configuration @inject IConfiguration Configuration
Now the configuration data from the application is available to this view. Next, we need to pull a couple of values out of the configuration data and pass it to the Angular application. To do this we are going to use the asp-prerender-data tag helper. You can read more about it in the official docs. The idea is you construct an object which is then serialized and stored in params.data. In our example, we are passing the URLs for the Identity and API Applications.
<app asp-prerender-module="ClientApp/dist/main-server" asp-prerender-data='new { apiUrl = Configuration["ApiAddress"], identityUrl = Configuration["IdentityServerAddress"] }'>Loading...</app>
The above is creating a new object with an apiUrl property and an identityUrl property. The following is the full completed view for reference.
@using Microsoft.Extensions.Configuration @inject IConfiguration Configuration @{ ViewData["Title"] = "Home Page"; } <app asp-prerender-module="ClientApp/dist/main-server" asp-prerender-data='new { apiUrl = Configuration["ApiAddress"], identityUrl = Configuration["IdentityServerAddress"] }'>Loading...</app> <script src="~/dist/vendor.js" asp-append-version="true"></script> @section scripts { <script src="~/dist/main-client.js" asp-append-version="true"></script> }
Angular Server-Side Boot
When Angular gets prerendered on the server-side it runs the code in the boot.server.ts file. This is where we will set up the providers needed on for the server side prerender. This is the bit that I missed for the longest time when trying to get this example going. I kept trying to find a way to add the providers in the app.module.server.ts file. Add any providers you need to the providers constant. For example, the following is passing URLs for an API and Identity Server in addition to the defaults provided by JavaScriptServices.
const providers = [ { provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } }, { provide: APP_BASE_HREF, useValue: params.baseUrl }, { provide: 'BASE_URL', useValue: params.origin + params.baseUrl } { provide: 'API_URL', useValue: params.data.apiUrl }, { provide: 'IDENTITY_URL', useValue: params.data.identityUrl } ];
Lower in the same file we can pass through the configuration values to the client side render as globals on the window object. To do this add a globals property to the object being passed to the resolve call.
return new Promise<RenderResult>((resolve, reject) => { zone.onError.subscribe((errorInfo: any) => reject(errorInfo)); appRef.isStable.first(isStable => isStable).subscribe(() => { // Because 'onStable' fires before 'onError', we have to delay slightly before // completing the request in case there's an error to report setImmediate(() => { resolve({ html: state.renderToString(), globals: {url_Config: params.data} }); moduleRef.destroy(); }); }); });
The above will have the URLs as part of a single object, but you could have each URL as its own property if you prefer.
Angular Client-Side
Now that the server-side has providers for API URL and Identity URL we need to provide the client-side with the same capabilities. These changes will be in the app.module.browser.ts file. The first step is to add providers for each.
providers: [ { provide: 'ORIGIN_URL', useFactory: getBaseUrl }, { provide: 'API_URL', useFactory: apiUrlFactory }, { provide: 'IDENTITY_URL', useFactory: identityUrlFactory }, AppModuleShared ]
Next, we need functions to return the URLs from the url_Config property of the window object which the following two functions do.
export function apiUrlFactory() { return (window as any).url_Config.apiUrl; } export function identityUrlFactory() { return (window as any).url_Config.identityUrl; }
Wrapping Up
With the above, you can now use your configuration values from ASP.NET Core and pass them through to your Angular application. In hindsight, the process is pretty simple, but getting to that point took me much longer to figure out than I would like to admit. I hope this post saves you some time!
Pass ASP.NET Core Appsettings Values to Angular Read More »