Now that our project has been created, it's time to take a quick look around and try to understand some of the hard work that the .NET Core SPA template has done to make it work.
...Hey, wait a minute! Shouldn't we skip all these setup technicalities and just jump into coding?
As a matter of fact, yes, we'll definitely be doing that in a little while. However, before doing that, it's wise to highlight a couple of aspects of the code that has been put in place already so that we'll know how to move effectively within our project in advance: where to find the server-side and client-side code, where to put new content, how to change our initialization parameters, and so on. It will also be a good chance to review our basic knowledge of the Visual Studio environment and the packages we will need.
That's precisely what we're going to...
In this chapter, all of the previous technical requirements listed in Chapter 1, Getting Ready, will apply, with no additional resources, libraries, or packages.
The code files for this chapter can be found here: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/tree/master/Chapter_02/.
The first thing that catches the eye is that, as we've already mentioned, the layout of a standard ASP.NET Core solution is quite different from what it used to be in ASP.NET 4 and earlier versions. However, provided that we already have some ASP.NET MVC experience, we should be able to distinguish the .NET Core back-end part from the Angular front-end part, and also figure out how these two aspects can interact.
The .NET Core back-end stack is contained in the following folders:
- The Dependencies virtual folder, which basically replaces the old Resources folder and contains all the internal, external, and third-party references required to build and run our project. All the references to the NuGet packages we'll add to our project will also be put there.
- The /Controllers/ folder, which has been shipped with any MVC-based ASP.NET application since the preceding release of the MVC framework.
- The /Pages/ folder, which contains a single Razor Page—Error...
The .NET Core back-end
If you hail from the ASP.NET MVC framework(s), you might want to know why this template doesn't contain a /Views/ folder: where did our Razor views go?
As a matter of fact, this template doesn't make use of views. If we think about it, the reason is quite obvious: a Single-Page Application (SPA) might as well get rid of them since they are meant to operate within a single HTML page that gets served only once. In this template, such a page is the /ClientApp/src/ folder/index.html file—and, as we can clearly see, it's also a static page. The only server-side-rendered HTML page provided by this template is the /Pages/Error.cshtml Razor Page, which is used to handle runtime and/or server errors that could happen before the Angular Bootstrap phase.
Those who have never heard of Razor Pages should spend 5-10 minutes taking a look at the following guide, which explains what they are and how they work: https://docs.microsoft.com/en-us/aspnet/core/razor-pages/.
In a nutshell, Razor Pages were introduced in .NET Core 2.0 and represent an alternative way to implement the ASP.NET Core MVC pattern. A Razor Page is rather similar to a Razor view, with the same syntax and functionality, but it also contains the controller source code—which is placed in a separate file: such files share the same name as the page with an additional .cs extension.
To better show the dependence between the .cshtml and the .cshtml.cs files of a Razor Page, Visual Studio conveniently nests the latter within the former, as we can see from the following screenshot:
...Hey, wait a minute: where have I seen this movie before?
Yes, this definitely rings a bell: being a slimmer version of the standard MVC Controller + view approach, a Razor Page is...
If a Razor Page includes the controller, why we do still have a /Controller/ folder? The reason is pretty simple: not all controllers are meant to serve server-rendered HTML pages (or views). For example, they can output a JSON output (REST APIs), XML-based response (SOAP web services), a static or dynamically-created resource (JPG, JS, and CSS files), or even a simple HTTP response (such as an HTTP 301 redirect) without the content body.
As a matter of fact, one of the most important benefits of Razor Pages is the fact that they allow a decoupling between what is meant to serve standard HTML content—which we usually call pages— and the rest of the HTTP response, which can be loosely defined as service APIs. Our .NET Core + Angular template fully supports such a division, which offers two main benefits:
- Separation of concerns: Using pages would force a separation between how we load the server-side pages (1%) and how we serve our APIs (99%). The percentages...
Let's now take a look at root-level configuration files and their purpose: Program.cs, Startup.cs, and appsettings.json. These files contain our web application's configuration, including the modules and middlewares, compilation settings, and publishing rules.
As for the WeatherForecast.cs file, it's just a strongly typed class designed to deserialize the JSON objects returned by the WeatherForecastController, which we've seen in action in the previous section: in other words, it's a JSON View Model—a view model specifically made to contain deserialized JSON objects. In our humble opinion, the template authors should have put it within the /ViewModel/ folder (or something like that) instead of leaving it at the root level. Anyway, let's just ignore it for now, since it's not a configuration file, and focus on the rest.
The Program.cs file will most likely intrigue most seasoned ASP.NET programmers, as it's not something we usually see in a web application project. First introduced in ASP.NET Core 1.0, the Program.cs file's main purpose is to create a WebHostBuilder, an object that will be used by the .NET Core framework to set up and build the IWebHost and which will host our web application.
Web host versus web server
That's great to know, but what is a web host? In very few words, a host is the execution context of any ASP.NET Core app. In a web-based application, the host must implement the IWebHost interface, which exposes a collection of web-related features and services and also a Start method. The web host references the server that will handle requests.
The preceding statement can lead to the assumption that the web host and the web server are the same thing; however, it's very important to understand that they're not, as they serve very different purposes. Simply put, the host is responsible for application startup and lifetime management, while the server is responsible for accepting HTTP requests. Part of the host's responsibility includes ensuring that the application's services and the server are available and properly configured.
We can think of the host as being a wrapper around the server: the host is configured to use a particular...
If you're a seasoned .NET developer, you might already be familiar with the Startup.cs file since it was first introduced in OWIN-based applications to replace most of the tasks previously handled by the good old Global.asax file.
However, the similarities end here; the class has been completely rewritten to be as pluggable and lightweight as possible, which means that it will include and load only what's strictly necessary to fulfill our application's tasks.
More specifically, in .NET Core, the Startup.cs file is the place where we can do the following:
- Add and configure Services and Dependency Injection, in the ConfigureServices() method
- Configure an HTTP request pipeline by adding the required...
The appsettings.json file is just a replacement for the good old Web.config file; the XML syntax has been replaced by the more readable and considerably less verbose JSON format. Moreover, the new configuration model is based upon key/value settings that can be retrieved from a wide variety of sources, including—yet not limited to—JSON files, using a centralized interface.
Once retrieved, they can be easily accessed within our code using Dependency Injection via literal strings (using the vanilla IConfiguration class):
public SampleController(IConfiguration configuration)
var myValue = configuration["Logging:IncludeScopes"];
Alternatively, we can achieve the same result with a strongly typed approach using a custom POCO class (we'll get to that later on).
It's worth noting that there's also an appsettings.Development.json file nested below the main one. Such a file serves the same purpose as the old Web.Debug.config file, which...
The Angular front-end
The front-end part of the template will probably be seen as more complex to understand, because Angular—just like most client-side frameworks—has evolved at a dramatic pace, thus experiencing many breaking changes in its core architecture, toolchain management, coding syntax, template, and setup.
For this very reason, it's very important to take our time understanding the role of the various files shipped with the template: this brief overview will start with root-level configuration files, which will also be updated with the latest versions of the Angular packages (and their dependencies) we'll need to use.
The Angular workspace is the filesystem place containing the Angular files: a collection of application files, libraries, assets, and so on. In our template, as in most .NET Core and Angular projects, the workspace is located within the /ClientApp/ folder, which is defined as the workspace root.
The workspace is usually created and initialized by the CLI command used to create the app: do you remember the dotnet new command we used in Chapter 1, Getting Ready? That's what we're talking about: the Angular part of the template was created by that command. We could achieve that same result with the Angular CLI, using the ng new command.
Any CLI commands operating on the app and/or their libraries (such as adding or updating new packages) will be executed from within the workspace folder.
The most important role within the workspace is played by the angular.json file, created by the CLI in the workspace root: this is the workspace configuration file and contains workspace-wide and project-specific configuration defaults for all build and development tools provided by the Angular CLI.
The first few properties at the top of the file define the workspace and project configuration options:
- version: The configuration file version.
- newProjectRoot: The path where new projects are created, relative to the workspace root folder. We can see that this value is set to the projects folder, which doesn't even exist. That's perfectly normal since our workspace is meant to contain two Angular projects in two already defined folders: our HealthCheck Angular app...
The package.json file is the Node Package Manager (npm) configuration file; it basically contains a list of npm packages that the developer wants to be restored before the project starts. Those who already know what npm is and how it works can skip to the next section, while those who don't should definitely keep reading.
Although npm is mostly a command-line tool, the easiest way to use it from Visual Studio is to properly configure a package.json file containing all the npm packages we want to get, restore, and keep up-to-date later...
Upgrading (or downgrading) Angular
As we can see, the Angular SPA template uses fixed version numbers for all Angular-related packages; this is definitely a wise choice since we have no guarantees that newer versions will seamlessly integrate with our existing code without raising some potentially breaking issues and/or compiler errors. Needless to say, the version number will naturally increase with the passage of time, because template developers will definitely try to keep their good work up to date.
That said, here are the most important Angular packages and releases that will be used throughout this book (excluding a small bunch of additional packages that might be added later on):
Upgrading (or downgrading) the other packages
As we might expect, if we upgrade (or downgrade) Angular to 9.0.0, we also need to take care of a series of other npm packages that might need to be updated (or downgraded).
Here's the full package list (including the Angular packages) we'll be using in our package.json file throughout the book, split into dependencies, devDependencies, and optionalDependencies sections: important packages are highlighted in the following snippet—be sure to triple-check them!
"start": "echo Starting... && ng serve",
"build": "ng build",
"build:ssr": "ng run HealthCheck:server:dev",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
The tsconfig.json file is the TypeScript configuration file. Again, those who already know what TypeScript is won't need to read all this, although those who don't should.
In this project, we will definitely use TypeScript for a number of good reasons; the most important ones are as follows:
Other workspace-level files
There are also other notable files created by the CLI in the workspace root. Since we'll not be changing them, we'll just briefly mention them in the following list:
- .editorconfig: Workspace-specific configuration for code editors.
- .gitignore: A text file that tells Git—a version-control system you most likely know quite well—which files or folders to ignore in the workspace: these are intentionally untracked files that shouldn't be added to the version control repository.
- README.md: Introductory documentation for the workspace. The .md extension stands for Markdown, a lightweight markup language created by John Gruber and Aaron Swartz in 2004.
- package-lock.json: Provides version information for all packages installed in the /node_modules/ folder by the npm client. If you plan to replace npm with Yarn, you can safely delete this file (the yarn.lock file will be created instead).
- /node_modules/: A folder containing all the npm packages...
The /ClientApp/src/ folder
It's now time to pay a visit to our sample Angular app and see how it works. Rest assured, we won't stay for long; we just want to get a glimpse of what's under the hood.
By expanding the /ClientApp/src/ directory, we can see that there are the following sub-folders:
- The /ClientApp/src/app/ folder, along with all its subfolders, contains all the TypeScript files related to our Angular app: in other words, the whole client-side application source code is meant to be put here.
- The /ClientApp/src/app/assets/ folder is meant to store all the application's images and other asset files: these files will be copied and/or updated as-is in the /wwwroot/ folder whenever the application is built.
- The /ClientApp/src/app/environment/ folder contains build configuration options that target specific environments: this template, just like any Angular new project default, includes an environment.ts file (for development) and an environment.prod.ts file...
The /app/ folder
Our template's /ClientApp/src/app/ folder follows Angular folder structure best practices and contains our project's logic and data, thus including all Angular Modules, Services, and Components, as well as templates and styles. It's also the only sub-folder worth investigating, at least for the time being.
As we briefly anticipated in Chapter 1, Getting Ready, the basic building blocks of an Angular application are NgModules, which provide a compilation context for Components. The role of NgModules is to collect related code into functional sets: therefore, the whole Angular app is defined by a set of one or more NgModules.
An Angular app requires a root module—conventionally called AppModule—that tells Angular how to assemble the application, thus enabling bootstrapping and starting the initialization life cycle (see the diagram that follows). The remaining modules are known as feature modules and serve a different purpose. The root module also contains a reference list of all available Components.
The following is a schema of the standard Angular Initialization Cycle, which will help us better visualize how it works:
As we can see, the main.ts file bootstraps app.module.ts (AppModule), which then loads the app.component.ts file (AppComponent); the latter, as we...
Server-side AppModule for SSR
As we can see, the /ClientApp/src/app/ folder also contains an app.server.module.ts file, which will be used to enable the Angular Universal Server-Side Rendering (SSR)—a technology that renders Angular applications on the server, provided that the back-end framework supports it. The template generated this file because .NET Core natively supports such convenient features.
The following is the improved Angular initialization schema when using SSR:
That's about it, at least for now. If you feel like you're still missing something here, don't worry—we'll come back to this soon enough to help you understand all of this better.
If NgModules are Angular building blocks, Components can be defined as the bricks used to put the app together, to the extent that we can say that an Angular app is basically a tree of Components working together.
Components define views, which are sets of screen elements that Angular can choose among and modify according to your program logic and data, and use services, which provide specific functionality not directly related to views. Service providers can also be injected into Components as dependencies, thus making the app code modular, reusable, and efficient.
The cornerstone of these Components is conventionally called AppComponent, which is also the only Component that—according to Angular folder structure conventions—should be placed in the /app/ root folder. All other Components should be put in a sub-folder, which will act as a dedicated namespace.
As we can see, our sample AppComponent consists of two files:
- app.component.ts: Defines the Component...
Other than AppComponent, our template contains four more Components, each one in a dedicated folder, as follows:
- CounterComponent: Placed in the counter subfolder
- FetchDataComponent: Placed in the fetch-data subfolder
- HomeComponent: Placed in the home subfolder
- NavMenuComponent: Placed in the nav-menu subfolder
As we can see by looking at the source files within their respective subfolders, only one of them has some defined tests: CounterComponent, which comes with a counter.component.spec.ts file containing two tests. It might be useful to run them to see if the Karma + Jasmine testing framework that has been set up by our template actually works. However, before doing that, it might be wise to take a look at those Components to see how they are meant to function within the Angular app.
In the next sections, we'll take care of both of these tasks.
Testing the app
Let's start by taking a look at those Components to see how they are meant to work.
As soon as we hit F5 to run the app in debug mode, we'll be greeted by HomeComponent, as seen in the following screenshot:
As the name clearly suggests, the HomeComponent could be considered the home page of our app; however, since the page concept might be rather misleading when dealing with single-page apps, we'll call them views instead of pages throughout the book. The word view basically refers to the combined HTML template generated by the Angular Component (including all sub-Components) that corresponds to a given navigation route.
Do we have sub-Components already? Yes, we do. The NavMenuComponent is a perfect example of that, since it doesn't have a dedicated route for itself but is rendered as part of other Components within their corresponding view.
More precisely, it's the top portion of each view, as we can see from the following screenshot:
The main purpose of the NavMenuComponent is to let users navigate through the main views of the app. In other words, it's where we implement all first-level navigation routes defined in AppModule, all pointing to a given Angular Component.
First-level navigation routes are those that we want our users to reach with a single click, that is, without having to navigate through other Components first. In the sample app we're reviewing now, there are three of them:
- /home: Pointing to the HomeComponent
- /counter: Pointing to the CounterComponent
- /fetch-data: Pointing to the FetchDataComponent
As we can see, these navigation routes have...
The CounterComponent shows an incrementing counter that we can increase by pressing an Increment button:
The FetchDataComponent is an interactive table populated with the JSON array generated by the server-side Web API via WeatherForecastController, which we saw a while ago when we were examining the back-end part of our project:
The specs.ts file(s)
Angular Unit Testing:
While we're there, it could be useful to give them a run them to see if the Jasmine + Karma testing framework that has been set up by our template actually works.
Our first app test
In this quick test, we'll basically launch Karma to execute the source code of our sample Angular app against the Jasmine tests defined by the template in the counter.component.spec.ts file: this is actually a much easier task than it might seem.
Open a Command Prompt, navigate to the <project>/ClientApp/ folder, then execute the following command:
> npm run ng test
This will call the Angular CLI using npm.
Alternatively, we can install the Angular CLI globally...
Getting to work
Now that we've got a general picture of our new project, it's time to do something. Let's start with two simple exercises that will also come in handy in the future: the first one of them will involve the server-side aspects of our application, while the second will be performed on the client side. Both will help us discover whether we have really understood everything there is to know before proceeding to subsequent chapters.
Static file caching
Let's start with the server-side task. Do you remember the /wwwroot/test.html file we added when we wanted to check how the StaticFiles middleware works? We will use it to do a quick demonstration of how our application will internally cache static files.
The first thing we have to do is to run the application in debug mode (by clicking on the Run button or pressing the F5 key) and put the following URL in the address line, so we can have another good look at it: http://localhost:<port>/test.html
Right after that, without stopping the application, open the test.html file and add the following lines to the existing content (new lines are highlighted):
<meta charset="utf-8" />
<title>Time for a test!</title>
<br /><br />
This is a test to see if the StaticFiles middleware is working
<br /><br ...
A blast from the past
Back in ASP.NET 4, we can easily disable static file caching by adding some lines to our main application's Web.config file, such as the following:
<caching enabled="false" />
<clientCache cacheControlMode="DisableCache" />
<add name="Cache-Control" value="no-cache, no-store" />
<add name="Pragma" value="no-cache" />
<add name="Expires" value="-1" />
That's it; we could even restrict such behavior to the debug environment by adding these lines to the Web.debug.config file.
We can't use the same approach in .NET Core, as the configuration system has been redesigned from scratch and is now quite different from the previous versions; as we said earlier, the Web.config and Web.debug.config files...
Back to the future
The first thing to do is to understand how we can modify default HTTP headers for static files. As a matter of fact, we can do that by adding a custom set of configuration options to the app.UseStaticFiles() method call in the Startup.cs file that adds the StaticFiles middleware to the HTTP request pipeline.
In order to do that, open Startup.cs, scroll down to the Configure method, and replace that single line with the following code (new/modified lines are highlighted):
OnPrepareResponse = (context) =>
// Disable caching for all static files.
That wasn't hard at all; we just added some additional...
Testing it out
Let's see whether our new caching strategy works as expected. Run the application in debug mode, and then issue a request to the test.html page by typing the following URL in the browser address bar http://localhost:/test.html
We should be able to see the updated contents with the phrase we wrote earlier; if not, press F5 in the browser to force page retrieval from the server:
Now, without stopping the application, edit the test.html page and update its contents in the following way (updated lines are highlighted):
<meta charset="utf-8" />
<title>Time for a test!</title>
<br /><br />
This is a test to see if the StaticFiles middleware is working
<br /><br />
What about the client-side cache? Does it work or not?
<br /><br />
It seems like we can configure it: we disabled it during...
The strongly-typed approach(es)
The approach that we chose to retrieve the appsettings.json configuration values makes use of the generic IConfiguration object, which can be queried using the preceding string-based syntax. This approach is rather practical; however, if we want to retrieve this data in a more robust way, for example, in a strongly typed fashion, we can—and should—implement something better. Although we won't cover that in more depth in this book, we suggest you read the following great articles, showing three different approaches to achieving this result:
The first one, written by Rick Strahl, explains how to do that using the IOptions<T> provider interface:
The second, by Filip W, explains how to do that with a simple POCO class, thus avoiding the IOptions<T> interface and the extra dependencies required by the preceding approach:
Client app cleanup
Now that our server-side journey has come to an end, it's time to challenge ourselves with a quick client-side exercise. Don't worry—it will be just a rather trivial demonstration of how we can update the Angular source code that lies within the /ClientApp/ folder to better suit our needs. More specifically, we will remove all the stuff we don't need from the sample Angular app shipped with our chosen Angular SPA template and replace it with our own content.
Trimming down the Component list
The first thing we have to do is delete Angular Components we don't want to use.
Go to the /ClientApp/src/app/ folder and delete the counter and the fetch-data folders, together with all the files they contain.
As soon as we do that, the Visual Studio Error List view will immediately raise two blocking TypeScript-based issues:
Error TS2307 (TS) Cannot find module './counter/counter.component'.
Error TS2307 (TS) Cannot find module './fetch-data/fetch-data.component'.
All of these errors will point to the app.module.ts file, which, as we already know, contains...
The AppModule source code
Angular modules, also known as NgModules, were introduced in Angular 2 RC5 and are a great, powerful way to organize and bootstrap any Angular application; they help developers consolidate their own set of Components, directives, and pipes into reusable blocks. As we said previously, every Angular application since v2 RC5 must have at least one module, which is conventionally called a root module and is thus given the AppModule class name.
AppModule is usually split into two main code blocks:
- A list of import statements, pointing to all the references (in the form of TypeScript files) required by the application.
- The root NgModule block, which—as we can see—is basically an array of named arrays, each one containing a set of Angular objects that serve a common purpose: directives, components, pipes, modules, providers, and so on. The last one contains the Component we want to bootstrap, which in most scenarios—including ours—is the...
Updating the NavMenu
If we run our project in debug mode, we can see that our recent code changes—the deletion of those two Components—do not prevent the client app from booting properly. We didn't break it this time—yay! However, if we try to use the navigation menu to go to the Counter and/or Fetch data by clicking the links from the main view, nothing will happen. This is hardly a surprise since we just moved these Components out of the way. To avoid confusion, let's remove these links from the menu as well.
Open the /ClientApp/app/components/nav-menu/nav-menu.component.html file, which is the UI template for the NavMenuComponent. As we can see, it does contain a standard HTML structure containing the header part of our app's main page, including the main menu.
It shouldn't be difficult to locate the HTML part we need to delete to remove the links to CounterComponent and FetchDataComponent—both of them are contained within a dedicated...
In this second chapter, we spent some valuable time exploring and understanding our sample project's core Components, how they work together, and their distinctive roles. For the sake of simplicity, we split the analysis into two parts: the .NET Core back-end ecosystem and the Angular front-end architecture, each with its own configuration files, folder structure, naming conventions, and overall scope.
At the end of the day, we can definitely say that we met the end goal of this chapter and learned a fair amount of useful things: we know the location and purpose of both server-side and client-side source code files; we are able to remove existing content and insert new stuff; we are aware of the caching system and other setup parameters; and so on.
Last but not least, we also took the time to perform some quick tests to see whether we're ready to hold our ground against what's coming in upcoming chapters: setting up an improved request-response cycle, building our...
Razor Pages, separation of concerns, the single responsibility principle, JSON, web hosts, Kestrel, OWIN, middlewares, Dependency Injection, the Angular workspace, Jasmine, Karma, unit tests, Server-Side Rendering (SSR), TypeScript, Angular architecture, the Angular initialization cycle, browser cache, and client cache.
- Introduction to ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/
- Angular: Setting up the Local Environment and Workspace: https://angular.io/guide/setup-local
- Angular architecture overview: https://angular.io/guide/architecture
- TypeScript - Modules: https://www.typescriptlang.org/docs/handbook/modules.html
- TypeScript - Module Resolution: https://www.typescriptlang.org/docs/handbook/module-resolution.html
- Karma: https://karma-runner.github.io/
- Jasmine: https://jasmine.github.io/
- Angular - Testing: https://angular.io/guide/testing
- Strongly Typed Configuration Settings in ASP.NET Core: https://weblog.west-wind.com/posts/2016/may/23/strongly-typed-configuration-settings-in-aspnet-core
- Strongly typed configuration in ASP.NET Core without IOptions<T>: https://www.strathweb.com/2016/09/strongly-typed-configuration-in-asp-net-core-without-ioptionst/
- Strongly Typed Configuration Settings in ASP.NET Core Part II: https://rimdev.io/strongly-typed-configuration-settings...