Book Image

ASP.NET Core 2 and Angular 5

By : Valerio De Sanctis
Book Image

ASP.NET Core 2 and Angular 5

By: Valerio De Sanctis

Overview of this book

Become fluent in both frontend and backend web development by combining the impressive capabilities of ASP.NET Core 2 and Angular 5 from project setup right through the deployment phase. Full-stack web development means being able to work on both the frontend and backend portions of an application. The frontend is the part that users will see or interact with, while the backend is the underlying engine, that handles the logical flow: server configuration, data storage and retrieval, database interactions, user authentication, and more. Use the ASP.NET Core MVC framework to implement the backend with API calls and server-side routing. Learn how to put the frontend together using top-notch Angular 5 features such as two-way binding, Observables, and Dependency Injection, build the Data Model with Entity Framework Core, style the frontend with CSS/LESS for a responsive and mobile-friendly UI, handle user input with Forms and Validators, explore different authentication techniques, including the support for third-party OAuth2 providers such as Facebook, and deploy the application using Windows Server, SQL Server, and the IIS/Kestrel reverse proxy.
Table of Contents (17 chapters)
Title Page
Credits
About the Author
About the Reviewers
www.PacktPub.com
Customer Feedback
Preface

Looking around


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 can be wise to highlight a couple of aspects of the code that has been put in place already so that we'll know how to properly move within our project in advance: where to find the server-side and client-side code, where to put some new content, how to change our initialization parameters, and so on. It will also be a good chance to review our base knowledge about the Visual Studio environment and the required packages we will need.

Note

IMPORTANT! The sample code we're reviewing here is the one that's being shipped with the Angular SPA Visual Studio Template at the time of writing--MVC ASP.NET Core with Angular. In the (likely) event that this sample code will get updated in future releases, ensure to get the former source code from the web using this book's official NuGet repository and use it to replace the contents of your /TestMakerFreeWebApp/ folder. If we avoid doing that, our sample code might differ from the one featured in this book.

The first thing that meets the eye is that, as we 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. The main difference is the brand new /wwwroot/ folder, which will contain the compiled, ready-to-publish contents of our application: HTML, JS, and CSS files, along with fonts, images, and everything else we want our users to have access to in terms of static files.

Other things worth noting are as listed:

  • The good old /Controllers/ and /Views/, which come shipped with any MVC-based ASP.NET application since the former release of the MVC framework
  • The /ClientApp/ folder, which already hosts a bunch of Angular source code files; we can see that they all have a .ts extension, which means we'll be using the TypeScript programming language (we'll say more about this in a bit)
  • The Dependencies virtual folder, which is basically the replacement of the old Resources folder and contains all the internal, external, and third-party references required to build and run our project
  • A bunch of root-level .cs, .json, and .js files that will determine our web application's configuration, the available client-side and server-side modules and also their setup, compilation, and publishing rules; we'll address them in a while

Just by observing these folders, provided that we already got a glimpse of ASP.NET MVC experience, we are already able to figure out how things will be handled:

  • Each request will be received by the MvcApplication, which will handle them at the server-side level and route those related to the GUI through the appropriate Controller
  • The Controller will do the required server-side tasks and then, depending on the given request, either serve raw JSON data or return the response content using a View
  • The View will serve the required client-side content (HTML, JS, and CSS), including the required JS modules that will be served in a highly-optimized fashion using a dedicated dynamic loader (Webpack)

Let's quickly review the whole process, starting with the root configuration files.

The configuration files

Let's start with the main .NET Core startup files: Program.cs and Startup.cs.

Program.cs

The Program.cs file will most likely raise the curiosity of 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 set up and build the IWebHost.

Note

That's great to know, but what is a Web Host? In a 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 preceeding statement can lead to thinking 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. The following excerpt from the .NET Core GitHub project does a great job explaining the key difference between them: The host is responsible for application startup and lifetime management. 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 could think of the host as being a wrapper around the server. The host is configured to use a particular server; the server is unaware of its host. Source: http://aspnetcore.readthedocs.io/en/stable/fundamentals/hosting.html

If we open the Program.cs file, we can easily see that the web host is built in an extremely easy way:

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

The WebHost.CreateDefaultBuilder() method is one of the many improvements of ASP.NET Core 2.0 over its 1.x counterpart as it simplifies the amount of source code required to set up basic use cases, thus making it easier to get started with a new project.

To understand this better, let's take a look at the sample Program.cs equivalent, like it was in ASP.NET Core 1.x:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .UseApplicationInsights()
            .Build();

            host.Run();
        }
    }

This used to perform the following steps:

  • Setting up the Kestrelweb server
  • Setting the Content root folder, that is, where to look for the appsettings.json file and other configuration files
  • Setting up the IIS Integration
  • Defining the Startup class to use (usually defined in the Startup.cs file)
  • Finally, Build and Run the now configured IWebHost

In .NET Core 1.x, all these steps must be called explicitly here and also manually configured within the Startup.cs file; in .NET Core 2.0, we can still do this, yet using the WebHost.CreateDefaultBuilder() method will generally be better as it will take care of most of the job, also letting us change the defaults whenever we want.

Note

If you're curious about this method, you can even take a peek at the source code on GitHub at https://github.com/aspnet/MetaPackages/blob/rel/2.0.0/src/Microsoft.AspNetCore/WebHost.cs.https://github.com/aspnet/MetaPackages/blob/rel/2.0.0/src/Microsoft.AspNetCore/WebHost.cs At the time of writing, the WebHost.CreateDefaultBuilder() method implementation starts at line #152.

Startup.cs

Let's move to the Startup.cs file. If you're a seasoned .NET developer, you might be already familiar with it, since it was first introduced in OWIN-based applications to replace most of the tasks previously handled by the good old Global.asax file.

Note

OWIN (for Open Web Interface for .NET) and comes as part of Project Katana, a flexible set of components released by Microsoft back in 2013 for building and hosting OWIN-based web applications. For additional info, refer to https://www.asp.net/aspnet/overview/owin-and-katana.

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 HTTP request pipeline by adding the required Middleware packages, in the Configure method

To better understand this, let's take a look at the following lines taken from the Startup.cs source code shipped with the project template we chose:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
        {
            HotModuleReplacement = true
        });
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");

        routes.MapSpaFallbackRoute(
            name: "spa-fallback",
            defaults: new { controller = "Home", action = "Index" });
    });
} 

This is the Configure method implementation, where--as we just said--we can set up and configure the HTTP request pipeline.

The code is very readable, so we can easily understand what happens here:

  • The first bunch of lines features an if-then-else statement that implements two different behaviors to handle runtime exceptions in development and production, throwing the exception in the former case or showing an opaque error page to the end user in the latter; that's a neat way to handle runtime exceptions in a very few lines of code.
  • The app.UseStaticFiles() call adds the Microsoft.AspNetCore.StaticFiles middleware to the HTTP pipeline, which will allow our web server to serve the static files within the web root. Without this line, we won't be able to serve locally hosted assets such as JS, CSS, and images; hence, having it there is a good thing. Also, note how the method is called with no parameters; the StaticFiles middleware default settings are more than enough for us, so there's nothing to configure or override here.
  • We can't say the same for the subsequent app.UseMvc() call, which comes with some interesting configuration parameters. We'll extensively talk about that in Chapter 2, Backend with .NET Core; for now, let's just understand that these lines serve the purpose of adding the MVC Middleware within the HTTP pipeline and also setting up a couple of HTTP routing rules pointing to the HomeController Index action method, which will be the web application main entry point.

Let's perform a quick test to ensure that we properly understand how these Middlewares work. From Visual Studio's Solution Explorer, go to the /wwwroot/ folder and add a new test.html page to our project. Once done, fill it with the following contents:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Time for a test!</title>
</head>
<body>
    Hello there!
    <br /><br />
    This is a test to see if the StaticFiles middleware is working properly.
</body>
</html>

Now, let's launch the application in debug mode--using the Run button or the F5 keyboard key--and point the address bar to http://localhost:<port>/test.html.

We should be able to see our test.html file in all its glory:

Now, let's go back to our Startup.cs file and comment out the app.UseStaticFiles() call to prevent the StaticFiles middleware from being loaded:

 // app.UseStaticFiles();

Once done, run the application again and go back to the previous URL:

 

As expected, static files aren't being served anymore. If we point our address bar to /home, we can see how this new behavior is also preventing the sample SPA provided by the Angular template from even loading, which means that we just broke our web app, yay!

Now that we proved our point, let's bring the StaticFiles middleware back in place by removing the comments and go ahead.

Note

For additional information regarding the StaticFiles middleware and static files handling in .NET Core, visit and read https://docs.asp.net/en/latest/fundamentals/static-files.html.

All in all, we can honestly say that the Startup.cs file shipped with the Angular SPA template already has everything we need, so we can leave it as it is for now. However, before going ahead, let's take another look at the if-then-else statement contained within this code snippet; we can easily see that there are other things planned when the application is in development mode. We're talking about the UseWebpackDevMiddleware() method with the HotModuleReplacement option set to true. This is one of the great features shipped with the Microsoft.AspNetCore.SpaServices package for those who use Webpack, which includes us; we'll get there later on, when talking about the Webpack configuration file.

Now, it's time to take a quick look at the three .json files also lying in the root folder. Each one of them is a configuration file for something; let's look at a bunch of words for each one of them.

The appsettings.json file

The appsettings.json file is nothing less than the replacement of the good old Web.config file; the XML syntax has been replaced by a more readable (and 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, even in a strongly-typed fashion using a custom POCO class (we'll get there later on).

Note

It's worth noting that there's also an appsettings.Development.json file nested below the former one. Such file serves the same purpose of the old Web.Debug.config file since ASP.NET 4.x; everything is written there will override the appsettings.json values as long as the application runs in the development mode. Back in .NET Core 1.x, this overriding behavior had to be specified manually within the Startup.cs file; in .NET Core 2, the WebHost.CreateDefaultBuilder() method within the Program.cs file takes care of that automatically, assuming that you don't need to add another custom .json configuration file.

Assuming that we understood everything here, it's time to move on to the next configuration file.

The package.json file

The package.json file is the NPM Configuration File; it basically contains a list of NPM packages that the developer want to be restored before the project starts. Those who already know what NPM is and how it works can skip to the next paragraph, while the others should definitely keep reading.

NPM (shortcode for Node Package Manager) started its life as the default package manager for the JavaScript runtime environment known as Node.js. During the latest years, though, it was also being used to host a number of independent JS projects, libraries, and frameworks of any kind, including Angular; eventually, it became the de facto package manager for JavaScript frameworks and tooling. If you never used it, you may easily think of it as the Nuget for the JavaScript world.

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 on. These packages get downloaded in the /node_modules/ folder within our project directory, which is hidden by default within Visual Studio; however, all the retrieved packages can be seen from the /Dependencies/npm/ virtual folder. As soon as we add, delete, or update the package.json file, Visual Studio will automatically update that folder accordingly.

In the Angular SPA template we've been using, the shipped package.json contains a huge amount of packages--all Angular packages plus a good bunch of dependencies, tools, and third-party utilities such as Karma (a great Test Runner for JavaScript/TypeScript).

Before moving ahead, let's take an additional look at our package.json file and try to get the most out of it. We can see how all the packages are listed within a standard JSON object entirely made of key-value pairs; the package name is the key, while the value is used to specify the version number. We can either input precise build numbers or use the standard npmJS syntax to specify auto-update rules bound to custom version ranges using the supported prefixes, such as the following:

  • The Tilde (~): A value of "~1.1.4" will match all 1.1.x versions, excluding 1.2.0, 1.0.x, and so on

  • The Caret (^): A value of "^1.1.4" will match everything above 1.1.4, excluding 2.0.0 and above

This is another scenario where Intellisense will come inhandy, as it will also suggest how to do that.

Note

For an extensive list of available npmJS commands and prefixes, it's advisable to check out the official npmJS documentation at https://docs.npmjs.com/files/package.json.

Upgrading (or downgrading) Angular

As we can see, the Angular SPA Template uses fixed version numbers for all the 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 the template developers will definitely try to keep their good work up to date.

That said, these are the Angular packages and releases that will be used within this book:

"@angular/animations": "5.0.2",
"@angular/common": "5.0.2",
"@angular/compiler": "5.0.2",
"@angular/compiler-cli": "5.0.2",
"@angular/core": "5.0.2",
"@angular/forms": "5.0.2",
"@angular/http": "5.0.2",
"@angular/platform-browser": "5.0.2",
"@angular/platform-browser-dynamic": "5.0.2",
"@angular/platform-server": "5.0.2",
"@angular/router": "5.0.2"

As we can see, the version number is the same for all packages and corresponds to the Angular release currently installed.

Note

The final version of Angular 5, codename Pentagonal Donut, has been released on November 1, 2017--just days before this book will hit the shelves: we did our best to use the latest possible final (non-beta, non-rc) version to give the reader the best possible experience with the most recent technology available. That said, that "freshness" will eventually decrease over time and this book's code will start to become obsolete: when it will happen, don't blame us for that!

If we want to ensure the highest possible level of compatibility between our project and this book's source code, we should definitely adopt that same release, which, at the time of writing, also corresponds to the latest stable one. We can easily perform the upgrade--or downgrade--by changing the version numbers; as soon as we save the file, Visual Studio will automatically fetch the new versions through NPM. In the unlikely case it won't, manually deleting the old packages and issuing a full rebuild should be enough to fix the issue.

As always, we're free to overwrite such behavior and get newer (or older) versions of these packages, assuming that we properly understood the consequences and according to this chapter's Disclaimer.

Note

If you encounter problems while updating your package.json file, such as conflicting packages or "broken" code, ensure that you download the full source code from the official GitHub repository of this book, which includes the same package.json file that has been used to write, review, and test this book; it will definitely ensure a great level of compatibility with the source code you'll find here.

Upgrading (or downgrading) the other packages

As we can easily expect, if we upgrade (or downgrade) Angular to 5.0.0 final, we also need to take care of a series of other NPM packages that might require to be updated (or downgraded) as well. Here's the full package list we'll be using in our package.json file throughout the book: the important packages are highlighted--be sure to triple-check them!

{
  "name": "TestMakerFree",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "test": "karma start ClientApp/test/karma.conf.js"
  },
  "dependencies": {
    "@angular/animations": "5.0.2",
    "@angular/common": "5.0.2",
    "@angular/compiler": "5.0.2",
    "@angular/compiler-cli": "5.0.2",
    "@angular/core": "5.0.2",
    "@angular/forms": "5.0.2",
    "@angular/http": "5.0.2",
    "@angular/platform-browser": "5.0.2",
    "@angular/platform-browser-dynamic": "5.0.2",
    "@angular/platform-server": "5.0.2",
    "@angular/router": "5.0.2",
    "@ngtools/webpack": "1.8.2",
    "@types/webpack-env": "1.13.2",
    "angular2-template-loader": "0.6.2",
    "aspnet-prerendering": "3.0.1",
    "aspnet-webpack": "2.0.1",
    "awesome-typescript-loader": "3.4.0",
    "bootstrap": "3.3.7",
    "css": "2.2.1",
    "css-loader": "0.28.7",
    "es6-shim": "0.35.3",
    "event-source-polyfill": "0.0.9",
    "expose-loader": "0.7.3",
    "extract-text-webpack-plugin": "2.1.2",
    "file-loader": "1.1.5",
    "html-loader": "0.5.1",
    "isomorphic-fetch": "2.2.1",
    "jquery": "3.2.1",
    "json-loader": "0.5.7",
    "preboot": "5.1.7",
    "raw-loader": "0.5.1",
    "reflect-metadata": "0.1.10",
    "rxjs": "5.5.2",
    "style-loader": "0.19.0",
    "to-string-loader": "1.1.5",
    "es6-shim": "0.35.3",
    "typescript": "2.4.2",
    "url-loader": "0.6.2",
    "webpack": "2.6.1",
    "webpack-hot-middleware": "2.20.0",
    "webpack-merge": "4.1.1",
    "zone.js": "0.8.12"
  },
  "devDependencies": {
    "@types/chai": "4.0.1",
    "@types/jasmine": "2.5.53",
    "chai": "4.0.2",
    "jasmine-core": "2.6.4",
    "karma": "1.7.0",
    "karma-chai": "0.1.0",
    "karma-chrome-launcher": "2.2.0",
    "karma-cli": "1.0.1",
    "karma-jasmine": "1.1.0",
    "karma-webpack": "2.0.3"
  }
}

Note

It's advisable to perform a manual command-linenpm update from the project's root folder right after applying these changes to the package.json file, in order to trigger a batch update of all the project's NPM packages: sometimes Visual Studio doesn't update the packages automatically and doing that using the GUI can be tricky. For this very reason, a convenient update-npm.bat batch file has been added to this book's source code repository on GitHub to handle that--without manually having to type the above command.

For further reference and/or future updates, please also check the updated source code within this book's official GitHub repository, which will always contain the latest improvements, bug fixes, compatibility fixes and so on.

The tsconfig.json file

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 the others should most likely stay.

In less than 100 words, TypeScript is a free, open source programming language developed and maintained by Microsoft that acts as a JavaScript superset; this means that any JavaScript program is also a valid TypeScript program. TypeScript also compiles to JavaScript, so it can seamlessly work on any JS-compatible browser without external components. The main reason to use it is to overcome the syntax limitations and overall shortcomings of JavaScript when developing large-scale applications or complex projects: in very short terms, to ease the developer's life when he's forced to deal with non-trivial JavaScript code.

In this project, we will definitely use TypeScript for a number of good reasons; the most important ones of them are as follows:

  • TypeScript has a number of features over JavaScript, such as static typing, classes, and interfaces. Using it in Visual Studio also gives us the chance to benefit from the built-in IntelliSense, which is a great benefit and often leads to a remarkable productivity burst.
  • For a large client-side project, TypeScript will allow us to produce a more robust code, which will also be fully deployable anywhere a plain JavaScript file would run.

Not to mention the fact that the Angular SPA template we chose is using it, hence we can say that we got a leg in that boat already!

Jokes aside, we're not the only ones praising TypeScript; it's something acknowledged by the Angular team itself, considering the fact that the Angular source code has been written using TypeScript since Angular 2, as proudly announced by Microsoft in the following MDSN blog post in March 2015:

https://blogs.msdn.microsoft.com/typescript/2015/03/05/angular-2-built-on-typescript/

It was further emphasized by this great post by Victor Savkin (co-founder of Narwhal Technologies and acknowledged Angular consultant) on his personal blog in October 2016:

https://vsavkin.com/writing-angular-2-in-typescript-1fa77c78d8e8

Getting back to the tsconfig.json file, there's not much to say; the option values used by the Angular SPA Template are more or less what we need to configure both Visual Studio and TSC (the TypeScript compiler) to properly transpile the TS code files included in the /ClientApp/ folder: however, while we're here, we can take the chance to tweak them a little more:

{
  "compilerOptions": {
    "module": "es2015",
    "moduleResolution": "node",
    "target": "es5",
    "sourceMap": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "skipDefaultLibCheck": true,
    "skipLibCheck": true,
    "strict": true,
    "lib": [ "es6", "dom" ],
    "types": [ "webpack-env" ]
  },
  "exclude": [ "bin", "node_modules" ],
  "atom": { "rewriteTsconfig": false },
  "angularCompilerOptions": {
    "strictMetadataEmit": true
  }
}

The interesting stuff here is the angularCompilerOptions object, which can be used to configure the behavior of the Angular AoT compiler: the strictMetadataEmit setting which we added will tell the compiler to report syntax errors immediately rather than produce an error log file.

Note

For more info regarding the new Angular AoT compiler, read the following URL: https://angular.io/guide/aot-compiler

The webpack configuration files

Last but not least we must spend some words on the webpack.config.js and webpack.config.vendor.js files, which play the most important role for the client-side components of our project because of the insane amount of tasks they take care of. Let's start with the usual question: what is Webpack to begin with? Those who know can move forward; as for the others, keep reading.

In short, Webpack is the most used--and arguably the most powerful nowadays--module bundler for modern JavaScript applications. Its main job is to recursively build a dependency graph of all the NPM modules needed by the client-side application before starting it, package them all into a small number of bundles--often only one--and then feed them (or it) to the browser.

The benefits brought to the developer by this revolutionary approach are simply too many and too great to be summarized in a short paragraph like this, as they will require too much space to be properly explained. We'll just scratch the surface by mentioning the most important ones:

  • Dramatically reduces the HTTP requests to load the client-side assets in normal scenarios, that is, when no package managers, task runners, or concatenation strategies are being used
  • Dramatically reduces the chance of variable conflicts when using standard concatenation strategies such as the .concat().uglify().writeTo() chains featured by Gulp, Grunt, and the likes
  • Dramatically increases the control over static files, as it can be configured to skip all the "dead" JS/CSS and even image (!) assets, reduce/optimize the size of CSS files even before minifying them, easily switch between CDNs URLs and locally hosted files, and so on

All these good things are real, as long as the tool is properly configured, which brings us to the only real bad thing about Webpack; it's not easy to set it up properly, especially for a newcomer, for a number of good reasons--the documentation has been greatly improved within the past 2 years, yet it's still not as good as other projects; the configuration file is quite hard to read and the syntax might be quite confusing at times.

Luckily enough, despite the steep learning curve, there's a gigantic amount of established examples, boilerplate projects, and code snippets available through the web that can be easily adapted to be used within most projects. The Angular SPA Template we've chosen is no exception, as it comes with two great configuration files - webpack.config.js and webpack.config.vendor.js - that already do all we need: the former one will be used to construct the bundle containing the application code, while the latter will bundle all the required vendor dependencies.

If we open them, we can see how they're both set up to build three main configuration objects:

  • The sharedConfig object for the assets that will be used within either the client-side and server-side bundles
  • The clientBundleConfig object used to bundle together the client-side assets for running-in browsers
  • The serverBundleConfig object used to bundle together the server-side (prerendering) assets

The former section acts as a prerequisite bundle that gets merged with the other two before they are deployed within the /wwwroot/ folder.

Note

If you want to know more about Webpack, we strongly suggest you to take a look at the official documentation, available at https://webpack.js.org/ . Also, it's worth noting that Webpack v2.x introduced a built-in validator for the config files that will greatly help the developer to track most coding errors; this new feature is extremely handy for those who want to update/improve the existing configuration files. For specific instruction on how to properly set up Webpack for Angular, it's also advisable to read the https://angular.io/docs/ts/latest/guide/webpack.html article from the official Angular documentation.

Do you remember the UseWebpackDevMiddleware() method we found in the Startup.cs file a short while ago? Now that we shed some light on Webpack, we can bring back the topic and easily explain what it was.

That middleware, only available when the web application is running in development mode, will intercept any request that will match files built by Webpack, dynamically build those files on demand and serve them to the browser without writing them to disk. Basically, it will act as an in-memory webhook.

Needless to say, such behavior will bring some benefits during development, such as these:

  • No need to run Webpack manually or set up file watchers: If you've been using task runners, you know how difficult it can be to have these always up in terms of resources
  • The browser will always receive up-to-date built output: No more outdated code due to caching issues or watchers not being up
  • Overall speed increase (at least arguably): The built artifacts should be served extremely quickly since the active Webpack instance should keep their partial compilation states already cached in memory

Note

For further information regarding the Webpack Dev Middleware, we suggest you to read the official documentation on the Microsoft.AspNetCore.SpaServices GitHub repository, available at https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#webpack-dev-middleware.

Updating the webpack.config.js file

Switching from Angular 4 to Angular 5 requires to perform a manual update to the webpack.config.js file to replace the previous AotPlugin to the new AngularCompilerPlugin: both of them are Webpack plugins that perform an AoT compilation of Angular components and modules. The former has been used since Angular 2 and up to Angular 4, while the latter has been released to work with Angular 5.

Open the webpack.config.js file with the Visual Studio editor and update line 4 in the following way (updated code highlighted):

const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;

Right after that, scroll down to the sharedConfig : module : rules section and replace the simple test: /\.ts$/ rule with the following one:

[...]

module: {
    rules: [
        { test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, [...]

[...]

Note

At the time of writing these steps are required because the template package still built around Angular 4 and AotPlugin. However, this will most likely change in the near future: if the AngularCompilerPlugin is already present in the webpack.config.js file we can skip this paragraph and go ahead.

Patching the webpack.config.vendor.js file

Before going further, the webpack.config.vendor.js file needs to be updated as well in order to fix a nasty bug that would prevent it from working as expected with Angular 5. Open that file and add the following line to the already existing sharedConfig.plugin array in the following way (new line highlighted):

[...]

plugins: [
    new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable)
    new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/11580
    new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/14898
    new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/20357
    new webpack.IgnorePlugin(/^vertx$/) // Workaround for https://github.com/stefanpenner/es6-promise/issues/100
]

[...]  

For further info regarding this fix, you can refer to the relevant GitHub issue at the following URL: https://github.com/angular/angular/issues/20357

Note

At the time of writing, this patch has to be done manually; however, it's more than likely that it will be included in a future release of the template package, together with the other GitHub issues already present. Needless to say, if that's the case we can skip this paragraph and go ahead.

Why use a dynamic module bundler?

Before moving ahead, it can be useful to explain why we just did so much hard work with a dynamic module packer/bundler such as Webpack instead of dropping a bunch of links pointing to all the relevant JS files--either hosted locally or, even better, through a high-performance CDN--right from the beginning.

To keep it simple, we did that because it's the only way to efficiently handle any modern JavaScript modular system such as Angular, RxJS, and also all applications based upon them, including the one we're working on right now.

What's a modular system exactly? It's nothing more than a package, library, or application split into a series of smaller files depending on each other using reference statements such as import and require. ASP.NET, Java, Python, and most compilation-based languages have it; that's not the case of script-based languages such as PHP and JavaScript, which are doomed to preload everything in memory before being able to determine whenever they'll be using it or not. All these change with the introduction of ECMAScript 6 (also known as ES6), which brings a full-featured module and dependency management solution for JavaScript.

Module bundlers such as Webpack pack a number of relevant JS/CSS resources at build time, including most ES6-polyfills for browsers that don't support it already, allowing us to get that module system working in modern browsers. Since both Angular and RxJS leverage such an approach, implementing it within our project will result in a huge performance gain.

We chose Webpack over other module packers, bundlers, and/or loaders (such as SystemJS) because of its great flexibility, as it provides a great way to properly package our application (concat, uglify, and the likes) with the additional knowledge given by its dependency graph. Additionally, Webpack received a great level of support in .NET Core 2, thanks to the introduction of the Microsoft ASP.NET Core JavaScript Services project and the Webpack Middleware, as we've already seen in this paragraph, making it the most logical choice when developing with .NET Core and Angular.

Refreshing the Webpack-generated files

To force Webpack to re-compile the vendor configuration file taking into account the fix we applied early on, we need to run the following command-line instruction from the project's root folder:

> node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js

Note

A convenient update-webpack.bat batch file has been added to this book's source code repository on GitHub to handle that--without manually having to type the above command.

Alternatively, we can also delete the /wwwroot/dist/ folder to force a full re-compilation upon the first project build.

It's generally wise to recompile the vendor configuration file everytime whenever we perform a task that could impact the generated bundles, such as: update the package.json file, perform a manual npm update from the command-line, alter the webpack.config.js configuration file, and so on.

The server-side code

Now that we've understood the meaning of the root configuration files, we can take a look at the Server-Side code shipped with the Angular SPA Template. As we already saw, we're talking about the contents of the /Controllers/ and /Views/ folders; let's start with the former.

Controllers/HomeController.cs

If we remember what we've seen within the Startup.cs file, we already know that it is the controller that all the requests not pointing to static files will be routed to. In other words, HomeController will handle all the requests that point (or get redirected to) our Single-Page Application first landing page, which we'll call Home View from now on.

More specifically, these requests will be handled by the Index action method. If we open the HomeController.cs file, we can see that the method does indeed exist, although being extremely lightweight--a single line of code that just returns the default Index View. That's more than enough; there's nothing to add there, as this is precisely what we need for a Single-Page Application entry page--just serve the Home View and let the client-side framework, Angular via Webpack, in our scenario, handle the rest.

The only exception for such behavior will be when we need to route the user away from the SPA, for example, when he's hitting an unrecoverable error. For these kinds of scenarios, an Error() action method was also implemented within the HomeController, which returns a dedicated Error View; however, before doing that, it will add some basic debug information to the ViewData collection (the current request unique identifier). This level of error handling is far from ideal, but we can live with it for the time being.

Controllers/SampleDataController.cs

The HomeController is a perfect example of a standard MVC Controller returning Views; conversely, the SampleDataController.cs is only returning structured JSON data, thus making it conceptually closer to the APIControllers featured by the ASP.NET Web API framework. Luckily enough, the new MVC 6 merged the best of both worlds into one, which means that there's no difference between these two kinds of controllers anymore; we're free to return any content we want from within the same controller class.

That said, there's no need to dig much into this controller's code for now; we'll do that soon enough, starting from Chapter 2, Backend with .NET Core. Let's just take a look at the resulting JSON data by issuing a couple of requests.

Start the application in debug mode by clicking on the Run button or pressing the F5 keyboard key; then, replace the /home part of the address bar URL with /api/SampleData/WeatherForecasts. The full URL should be just like the following:

http://localhost:<port>/api/SampleData/WeatherForecasts

We should be able to see something like this:

These are the raw (sample) data elements coming out from the server. Now, point the URL back to /home, which will bring us back to the Home View. From there, click on the Fetch data link from the left menu to get the following:

These are the same data elements mentioned earlier, fetched and served by Angular through the sample SPA provided by our current project template. All the GUI elements--menu links, page title, and labels, HTML code, CSS styles, and so on--are inside the /ClientApp/ folder; the server-side code only provides the raw data, just like it's meant to be.

The /Views/ folder

A quick look at the /Views/ folder is more than enough, as the view files merely contain the least possible amount of required code:

  • A minimalistic HTML5 skeleton to host the <head> and <body> elements, along with some child elements, such as the <title> page
  • Some <script> and <link> elements pointing to the local paths where the Webpack bundles will be built
  • The <app> element, which is the DOM placeholder used by Angular to inject the SPA into

Those who are used to the ASP.NET MVC and Razor convention can easily see how the template did a good job in putting all the common HTML structure in the Layout view (the _Layout.cshtml file), leaving the Index and Error views as lightweight as possible. The result is stunning and also very easy to read--the whole HTML base structure is comprised within a few lines of Razor and HTML code.

The client-side code

Last but not least, let's pay a visit to the sample Angular app and see how it works. Rest assured, we won't stay for long; we just want to take a glimpse of what's under the hood.

By expanding the /ClientApp/ directory, we can see that there are three subfolders and two files. However, the only thing that we should take into consideration, for the time being, is the /ClientApp/app/ folder, along with all its subfolders; this is the place containing all the Angular TypeScript files. In other words, the whole source code of our client-side application is meant to be put here.

Before going there, let's spend a couple words on its siblings:

  • The /ClientApp/dist/ folder and the boot.server.ts file are both used by Webpack to build the server bundles that will be used to enable the Angular Universal's Server-Side Rendering (SSR), which has been adopted and implemented by .NET Core 2.0 within the Microsoft ASP.NET Core JavaScript Services package. We'll get back to it later on; however, we can safely ignore them both for now.
  • The /Client/test/ folder is where we'll put our unit tests; we can entirely skip it for now and get back once we're ready.
  • The boot.browser.ts file contains the first code that will be executed by the browser, thus acting as the client-side bootstrapper of our Angular application; if we open it and take a look at the code, we can see how it imports the required packages to perform the bootstrap--including the AppModule from the /ClientApp/app/ folder--and then perform the app initialization within the <app> HTML element, also using different behaviors for development and production. Although it plays a very important role, we won't need to change its contents for a while; hence it's better to leave it as it is for now and focus on the application source code instead.

Note

If you're curious about Angular Universal and Server-Side Rendering and want to know more about them, we strongly suggest you to start with reading this good article by Burak Tasci:https://medium.com/burak-tasci/angular-4-with-server-side-rendering-aka-angular-universal-f6c228ded8b0 Although not diving deep into these concepts, it does a good job of explaining the core concept of this innovative approach. As soon as you get the basics, you can take a look at the real deal here:https://universal.angular.io/https://github.com/aspnet/JavaScriptServices

The /ClientApp/app/ folder

The /ClientApp/app/ folder loosely follows the Angular folder structure best practices, thus containing the following:

  • A /component/ folder, containing all the Angular components. We will talk more about them in Chapter 3, Frontend with Angular; for now, we'll just say that these are the building UI blocks of any Angular application, to the point that we can say that it is basically a tree of components working together. Each component has its very own namespace, which is represented with a subfolder, and comes with the following:
    • A required TypeScript file, which follows the <componentName>.component.ts naming conventions
    • An optional HTML file containing the component template, or (in other words) its UI layout structure
    • An optional CSS file to handle the UI styling
    • Other optional files, such as the counter.component.spec.ts in the /components/counter/ folder, which can be used whenever we need to split the component code into multiple files for readability or code reuse purposes
  • Three TypeScript files: app.module.browser.ts, app.module.server.ts, and app.module.shared.ts containing the Angular root module class, also known as the AppModule.

If you already have some Angular experience, you most likely know what the AppModule is and how it works. If you don't, the only thing you need to understand is that it serves as the main application entry point, the one that gets bootstrapped by the boot file(s) we talked about earlier.

Here's a schema of the standard Angular Initialization Cycle that will help us better visualize how it works:

As we can see, the boot.ts file bootstraps the app.module.ts (AppModule), which then loads the app.component.ts file (AppComponent); the latter will then load all the other components whenever the application needs them.

We can find such structure in any Angular application, it being the default initialization behavior enforced by the Angular.io project developers. However, the Angular SPA template we've chosen features a slightly more complex scenario because, as we said earlier, it also supports Server-Side Rendering; for this very reason, it needs to take care of the server-side part as well. This is why we got two very different boot files --boot.browser.ts and boot.server.ts, respectively--to load our app into the browser and to support Server-Side Rendering, and also two different AppModule classes to boot: the app.module.browser.ts and app.module.server.ts, both of them including the common app.module.shared.ts file.

Here's the improved schema when using SSR:

All these files will then be processed by Webpack and built in the /wwwroot/dist/main-client.js and /ClientApp/dist/main-server.js files, which will contain the "bundled" version of our Angular app, respectively, for Client-Side and Server-Side rendering.

That's about it, at least for now. If you feel like you're still missing something here, don't worry, we'll be back there soon enough to understand all of this better.