Have you ever used one of the ASP.NET Core Web Application SPA Templates (angular or react) and wondered how they work?
Today we'll look at both the Angular and React Templates that ship with ASP.NET Core 5.0 projects and de-mystify the magic behind them.
Although we will be unpacking the Angular and React project templates in this video, the learnings from this can be applied other project types too.
The Basics
As a web developer today, we're quite fortunate to have a host of utilities at our disposal to create & bootstrap our projects.
So angular developers have the Angular CLI
, React has Create-React-App
and dotnet of course has dotnet new
.
These utilities produce robust ready-to-run projects that make it easy for us to start with a working set of code.
I think what makes the dotnet SPA templates interesting is how it leverages UI scaffolding tools together with your dotnet projects to essentially create 2 separate yet connected projects. So thats your UI project (running either angular or react as your front end) and your dotnet project as your API backend.
Why bother you ask?
While these SPA templates make getting started easy; they also tend to obfuscate how the 2 project types work with each other.
Aim of this post is to arm you with the knowledge you need to change and customize it to suit your needs.
This should be particularly helpful when it comes time to setting up your build and deploy pipelines.
Assumptions
You've used other Non SPA templates and generally understand how ASP.NET works (familiarity with Program.cs and Startup.cs).
You're also have some familiarity with either Angular or React.
So, let's get into it..
Steps
Today let's create some SPA projects and explore the code generated to understand how they work.
Create a Project
We'll start by creating a solution called MyApp
with 2 projects within it, namely:
MyApp.Angular.App
using theASP.NET Core with Angular
template usingASP.NET Core 5.0
MyApp.React.App
using theASP.NET Core with React.js
template usingASP.NET Core 5.0
Run them & see it work..
Lets look at the things we know
The Project Structure
- The only thing that stands out here is the
ClientApp
folder. Everything else is pretty stock standard with other ASP.NET Core Projects. - Contents of the ClientApp folder are created using cli tools like
@angular/cli
orng
for Angular ANDcreate-react-app
for React.
Project Package Dependency
Microsoft.AspNetCore.SpaServices.Extensions
is the only nuget dependency added.
This package enables the following:
- the
UseSpa
andUseSpaStaticFiles
extension methods on theIApplicationBuilder
- the
AddSpaStaticFiles
extension method on theIServiceCollection
- the
UseAngularCliServer
andUseReactDevelopmentServer
extension methods on theIWebHostEnvironment
The Startup
The Startup.cs
should look similar but a few things stand out
ConfigureServices()
has AddSpaStaticFiles
extension method. In production, the your Angular or React files will be served from this directory.
Configure()
has the UseSpaStaticFiles
which serves files configured from the path specified earlier.
Configure()
also has the UseSpa
extension method. In development this allows us to serve the client via npm
using the UseAngularCliServer
and UseReactDevelopmentServer
extension methods on the IWebHostEnvironment
The Project File
Most of the magic (the fact that things are plumbed together & run) is hidden in the project file. Let's look at it in some detail. Both the Angular and React project files are mostly identical with a few differences based on their individual package.json
files.
This first PropertyGroup
defines a few key things, namely:
- The Root folder for the SPA Project
- A directive to exclude node_modules
- In the Angular project there's also an option to enable server-side pre-rendering.
Note: At the time of writing this post, "Server-side rendering" is not a supported feature of the react template.
The next ItemGroup
ensures we don't publish the SPA source files, but do show them in the project files list.
The next section Target
with the DebugEnsureNodeEnv
identifier instructs dotnet to complete certain activities before running dotnet build. As you can see, it also has an additional condition attached to it. This section only runs when the configuration is Debug & the node modules folder does not exist. So this would have run the first time we created the project and run it from within visual studio. Within this section, the project template ensures a couple of things.
- that you have nodejs installed. If it isn't you'll get an error message.
- it also runs an npm install command which should install all the necessary dependencies of your UI project before building your dotnet project and its dependencies.
The next section Target
with the PublishRunWebpack
identifier ensures that your projects, both the UI project running angular or react as well as your dotnet project is correctly published. Now when you do setup your build and deploy pipelines, this is the section that you are most likely to change to suit your needs.
As you see here there are a couple of things happening:
- It runs an NPM install to get the UI project dependencies
- We also run an npm build in production mode.
- The third thing we notice (and again this is only for the angular template as it has server side rendering, it won't be in the react project) we run an npm build for server side rendering.
Steps 2 and 3 produce the distribution files that need to be hosted on our web server.
- This next section marks those files as valid distribution files. So for the angular project, its the contents within the dist directory. For react its your contents in a directory called build.
- Again only for server side rendering we might copy over the node_modules directory (so this won't appear in the react project file)
- And lastly by adding the built SPA files to our ResolvedFileToPublish, we can tell the dotnet to include them for publish
The configuration in our dotnet project file together with the code in the Startup.cs ensures that your SPA and API projects work seamlessly, weather you're developing locally or if you are publishing it to your azure app service.