Server-side rendering in ASP.NET Core Angular (2022 version)

Pieterjan De Clippel
4 min readDec 20, 2021

--

This is an updated version of following walkthrough.

Create the project

Start by creating a new ASP.NET Core project. Choose the Angular template:

If you wish to use the Visual Studio publish wizard, try putting your project as close to the drive root as possible (Yes, you’re right, Visual Studio 2022 still doesn’t support the LongPathApi…)

Off course we choose to use .NET 6

Make sure you have the latest version of angular installed. Run a cmd as administrator, and issue:

npm install --global @angular/cli@latest
ng version
> 13.1.2

Now remove the ClientApp folder from the project, and recreate it by running the following command from the project folder:

ng new ClientApp --style scss --routing --strict

.NET Setup

Now we need to revert the Startup file back to the “old-style” Startup class.

  1. Open the NuGet package manager, and install the latest version of Microsoft.AspNetCore.SpaServices.Extensions into your project.
  2. Also install MintPlayer.AspNetCore.Hsts . This is an improved version of the ASP.NET Core HSTS Middleware, which works correctly for SPA’s. This middleware uses the OnStarting callback, and effectively adds the header to the HTTP response when visiting any page in your angular application.
  3. Also install the MintPlayer.AspNetCore.SpaServices.Routing package into your project. This package contains the RouteBuilder to parse visited urls in your backend. The dependency project Prerendering contains the SpaPrerendering middleware.
  4. Remove Microsoft.AspNetCore.SpaProxy.

Add the following Startup class

Then the Program file should look like this:

The only thing missing now is the SpaPrerenderingService

This service is registered as a scoped service, so you can inject repositories, DbContext through the constructor.

Modify your csproj file:

Items to remove:

  • SpaProxyServerUrl
  • SpaProxyLaunchCommand

Items to add:

  • BuildServerSideRenderer
  • npm run build:ssr

Items to change:

<!-- Old lines -->
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
<!-- New lines -->
<DistFiles Include="$(SpaRoot)dist\**" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'false' " />

My final csproj file:

Angular setup

Navigate to the ClientApp folder and call the following schematic:

ng add @nguniversal/express-engine

Now the output folder is still disconfigured, and the configuration won’t work when DOTNET_ENVIRONMENT = Production. To fix this, open the angular.json file and change the outputPath from dist/ClientApp/browser to dist.

Install aspnet-prerendering and the SERVER_SIDE provider

npm install --save aspnet-prerendering
npm install --save @mintplayer/ng-server-side

Modify the main.ts:

Provide SERVER_SIDE

And modify the main.server.ts:

In angular.json, change the server main to src/main.server.ts

tsconfig.app.json

Add node to the types
Remove “target”, add “module”: “commonjs”

The result is here: https://github.com/PieterjanDeClippel/Ssr/tree/master

EDIT

Since the .NET template was updated by Microsoft, you need to make additional changes before starting the deployment to your live webserver.

Troubleshoot

  1. Important!!!!: Please check the output window. Half of the information needed to solve occasional problems with SSR is logged in the Visual Studio Output window, so the Visual Studio Output window is the most interesting source to diagnose issues here. If you’re experiencing problems, don’t hesitate to write back, but include the contents of your Output window.
  2. In previous walkthroughs I’ve written out some checks you can perform to solve several issues

Possible troubles

NodeInvocationException: Cannot destructure property ‘AppServerModule’ of ‘module2.exports’ as it is undefined.

This link explains everything.

So replacing

const renderPromise = AppServerModuleNgFactory
? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options)
: /* dev */ renderModule(AppServerModule, options);

with

const renderPromise = renderModule(AppServerModule, options);

should be fine.

dotnet templates

I’ve created a dotnet template containing this code in this repository. You can install these templates using the following command:

dotnet new --install MintPlayer.AspNetCore.IdentityServer.Templates

or update them if they’re already installed

dotnet new --update-apply

After this, you should see the templates in Visual Studio

And be able to create them using dotnet

dotnet new id-provider
dotnet new id-application

If you’re starting Visual Studio as Administrator, then you’re best off running

npm i

from the ClientApp folder, before starting the application.

--

--

Pieterjan De Clippel
Pieterjan De Clippel

Responses (21)