Download source
Introduction
There is nothing like a great Sunday brunch buffet. You can truly have it all, from all-you-can-eat buffets in fancy hotel digs to the plethora of bottomless items to consume. A variety of breakfast-lunch treats await you; mimosas and bloody marys, eggs Benedict, omelets, scrambled eggs, French toast, Belgian waffles, bagels, pasta, chicken, seafood, and carved prime rib. The list is endless. The only limitations are your budget and how much you can and want to eat.
As a software developer, you also have a buffet of technology to choose from; especially if you are developing web based applications. Do you develop your application front-end with one of the latest JavaScript frameworks: Angular, React or VueJS or do you decide to develop your front end using Microsoft ASP.NET MVC. Besides the Microsoft stack of technologies there are also several full end-to-end technologies and design patterns to choose from including the MEAN stack, the LAMP stack or a Java stack of technologies. Of course you can always choose from other technologies such as Python or Ruby On Rails to build your web application.
With all these technology choices available, this is no time not to be a software developer or as the rock band U2 sings on their latest album; this is no time not to be alive. Similar to the Sunday brunch buffet, technology has its limitations including time and money. Your time is often limited and you must strategically decide how you are going to spend your time. There are other factors too when choosing a technology to learn and/or build your applications with; including company budget, time to market, maturity of the technology stack, your skill set and the skill set of your development team and the technology's overall ecosystem, public support and demand in the marketplace.
Goals of The Sample Application
If you are a member of an Agile/Scrum team in a Microsoft development shop, you are most likely using an edition of Microsoft Visual Studio and using Microsoft Team Foundation Services (TFS) or Visual Studio Team Services (VSTS). These tools allow you to strategically develop, test and deploy your application to different environments including deploying your application to the Azure cloud.
Being both an Angular and Microsoft .NET developer myself, naturally I focused this article on describing the initial development of a web-based application that will deploy the latest version of Angular; version 6 with the latest version of the Microsoft ASP.NET Core 2.1 technology stack using both Visual Studio 2017 and Visual Studio Code.
One of the things I like about hosting an Angular application inside an ASP.NET project and application is that it allows you to store configuration settings inside a web.config file with different configuration settings that can be published out to different environments. These configuration settings can be injected into an Angular application at runtime when the application gets bootstrapped by ASP.NET. It gives you a lot of flexibility when it comes to configuring and publishing and starting up an Angular application.
Choosing Microsoft ASP.NET Core 2.1
One of the first decisions you will have to make when integrating Angular with ASP.NET is the integration itself. Do you integrate Angular with the traditional ASP.NET MVC framework or do you jump into the new ASP.NET Core platform. An Angular front-end can be initially created by structuring the Angular components, services and modules manually, or by scaffolding the initial project using Angular CLI or by using the latest Microsoft .NET Core 2 project template that now includes an Angular project already integrated.
After going through each of these alternatives and test driving the Angular template that came with ASP.NET Core 2.1, I didn't really like all the hooks and SPA middleware I saw in the code that was required in the ASP.NET Core project to make it all work with the Angular CLI.
I also noticed that with each new release of Angular that there was a time delay getting the proper instructions to update the project template to the latest released version of Angular. Overall, I wanted more flexibility to develop and test my application independently of ASP.NET Core and Visual Studio 2017.
To maintain technology independence, I decided to loosely couple the Angular 6 application with it's integration with Visual Studio 2017. This would allow me the flexibility to use either Visual Studio 2017 or Visual Studio Code and use either IIS Express or the Webpack development web server to develop and test the application through the Angular CLI and use ASP.NET Core with Visual Studio 2017 and TFS to publish the application to a production IIS web server.
ASP.NET Core Razor Pages
With the release of ASP.NET Core 2, a new framework was introduced for structuring an ASP.NET MVC web project. We now have two options: we can choose the traditional folder structures and conventions for the models, the views and the controllers or implement the new web framework for creating a “page” without the full complexity of ASP.NET MVC.
The introduction of Razor Pages represents a slimmer version of the ASP.NET MVC framework. A Razor page is very similar to the view component that ASP.NET MVC developers are used to. It has all the same syntax and functionality. The key difference is that the model and controller code are also included within the Razor page itself in a code-behind file. It enables two-way data binding and a simpler development experience without the complexly of separate folders for all the models, views and controllers.
After working with ASP.NET Razor Pages for a little while, it became obvious with it's slimmer implementation, that Razor Pages were the perfect solution for integrating and launching an Angular application hosted inside ASP.NET Core.
Getting Started - ASP.NET Core 2.1 Web Project
To get things started I created an ASP.NET Core 2.1 Web Project using the default Web Application template. This template implements Razor Pages which will be a simpler solution for integrating with Angular 6 compared with the traditional Web Application Model-View-Controller template.
Using a dot notation syntax, I named the project CodeProject.AngularCore.Portal.
Getting Started - Angular 6 CLI Project
To get started with the Angular CLI, you will need to install NodeJS and the Node Package Manager (NPM). Go to https://nodejs.org. Once NodeJS is installed you can install the Angular CLI from the Windows command prompt.
npm install -g @angular/cli
Once this is done you can go ahead and create your new Angular CLI project from the command line as follows:
ng new CodeProjectAngularCorePortal
The latest version of the Angular CLI doesn't support dot notation in the project name so I named the Angular 6 project as CodeProjectAngularCorePortal. This is a minor detail because the next step is to go into the root project folder and simply cut and paste all the contents and sub-folders of this project into the ASP.NET Core project root folder.
Opening up the ASP.NET Core project in Visual Studio 2017, you will see the following folders with the combined contents of both projects.
In the project there are three significant folders that we will be working with:
- src - contains the Angular 6 source code
- Pages - contains the ASP.NET Razor Pages
- wwwroot - will contain the build output for the Angular application
Setting the ASP.NET Core Environement Variable
One of the things you'll notice when working with ASP.NET Core for the first time, is that there is no web.config file in your project. You'll start asking, where do I put all my application settings and connection strings and how do I set these configurations for different environments and servers.
In ASP.NET Core there is this concept of “environments” where you set at the machine level or at the code level what “environment” you are in. Environments may include development, qa, staging, production or any name you want. Previously in the ASP.NET MVC framework you would use a web.config file and use XML transformations to transform the configuration at build and publish time, ASP.NET Core instead determines it’s configuration at runtime.
In ASP.NET Core, environment settings are driven by a single variable named ASPNETCORE_ENVIRONMENT. By default an ASP.NET Core project sets this value to "Development". The "Development" setting will appear in Visual Studio 2017 in the dropdown menu next to the run button.
Of course the first thing I wanted to do was create and test this environment variable for multiple environments when launching Visual Studio 2017 in debug mode. Visual Studio 2017 and ASP.NET Core projects include a new file called launchSettings.json and it resides under the Properties folder of the project. In this file you can set-up different launch environments that Visual Studio 2017 can use.
In the launchSettings.json file below, I edited the file directly and created additional profiles in the JSON formatted file to set the ASPNETCORE_ENVIRONMENT variable for the different environments that I want. Each one of these environment settings also tell Visual Studio 2017 to launch the application in IIS Express when you run the application in Visual Studio 2017. These additional environment settings will automatically appear in the Visual Studio 2017 debug dropdown which you can select before launching the application.
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:64984",
"sslPort": 44326
}
},
"profiles": {
"Development": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"QA": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "QA"
}
},
"Staging": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}
},
"Production": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
}
}
}
Application Settings
Now that you can select and set the environment for ASP.NET Core when launching the application in Visual Studio 2017, you now need to have a place to store the actual application settings and/or connection strings that the application needs per environment you want to run under.
As a replacement for the web.config file, ASP.NET Core uses a JSON-formatted configuration file named appsettings.json. To save your application settings per environment you can create additional appsettings files, one for each environment with the naming convention of appsettings.<environment>.json. Once created, Visual Studio 2017 will automatically place these files under the appsettings.json file similar to the previous web.config convention.
For the sample application for this article, I created the following application settings files:
- appsettings.developmnent.json
- appsettings.qa.json
- appsettings.staging.json
- appsettings.production.json
In the appsettings.production.json file below, I created an AppSettings section that sets a property for a Web API endpoint. Eventually the sample application will need to make calls to a web api endpoint per environment. You can create additional sections in this file as you need such as creating a ConnectionStrings section to store your database connection strings per environment.
{
"AppSettings": {
"WebApiUrl": "https://production.com/api/"
}
}
Publishing To Different Environments and IIS
So far all we have done is set-up Visual Studio 2017 and the application to run with different environment settings under IIS Express when we launch the application from Visual Studio 2017. After doing all this, the next challenge was to figure out how to publish the application to another environment such as qa, staging or production and have the appropriate application settings be used for that particular environment while the application is running under the IIS web server.
As normal, you can still use Visual Studio 2017 to publish your application to different environments by setting up different publishing profiles and configurations for publishing purposes.
So I went ahead and published my application to a QA server using a QA publishing profile. What I discovered on the QA server was that the application was still running in "Development" mode based on the ASPNETCORE_ENVIRONMENT variable and all the appsettings files were published as individual files. Looking at the root folder of the QA web site; I noticed that Visual Studio 2017 had also automatically generated and published a web.config file. When you open the web.config file, you'll notice that the config file implements the ASP.NET Core Module to handle HTTP requests between IIS and ASP.NET Core even though ASP.NET Core does not itself use the web.config file.
As it turns out, we can still use a web.config file with our application and perform XML transformations when publishing our application. Basically we just need a way to override the ASPNETCORE_ENVIRONMENT variable and set it's value for the target environment. Now the trick was to figure out how to do XML web.config transformations because this functionality is not provided in the ASP.NET Core project templates.
After a little research, its turns out that .NET Core comes with a toolbox of commands that can be run from the command line interface (.NET Core CLI). The NuGet package needed is called Microsoft.DotNet.Xdt.Tools; its an XML Document Transform (XDT) publish tool for the .NET Core CLI. This package contains the utility command called dotnet-transform-xdt for transforming XML files at publishing time.
You can install this package from the NuGet console in Visual Studio 2017:
Install-Package Microsoft.DotNet.Xdt.Tools -Version 2.0.0
The next thing to do is add four web.config files; a main web.config file and one for each of your environments. For the sample application, I created the following web.config files:
- web.config
- web.qa.config
- web.staging.config
- web.production.config
The main web.config file was created with the following content:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- To customize the asp.net core module uncomment and edit the following section.
For more info see https:
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
</aspNetCore>
</system.webServer>
</configuration>
The main goal with these web.config files and using transformations is to simply set and override the ASP.NET Core environment variable ASPNETCORE_ENVIRONMENT per environment.
The next thing we needed to do was add the XML transformation information into each web.config file for each environment using XDT.
Below is the web.release.config file that will transform the ASPNETCORE_ENVIRONMENT variable and set it to the value of "Production" when publishing to a production release environment.
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.webServer>
<aspNetCore
processPath="%LAUNCHER_PATH%"
arguments="%LAUNCHER_ARGS%"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</environmentVariables>
</aspNetCore>
</system.webServer>
</configuration>
Now we just need to tell Visual Studio 2017 to run the XML transformation when we publish our application. To do this we need to edit the project file and add a task to the file. The cool thing now is that you don't have to unload the project to edit the project file. You can simply right-click on the project name and select edit.
When you look at the project file you will see that its a much cleaner file to read and edit than the project files for standard .NET framework projects. To tell Visual Studio 2017 to run an XML transformation, I added the below snippet to the bottom of the project file. We can execute an XML transformation by executing the dotnet transform-xdt .NET Core CLI command.
I created a custom target that I named ApplyXdtConfigTransform. I setup the task to tell Visual Studio 2017 to run the task before the build pipeline _TransformWebConfig task runs. The custom task also tells Visual Studio 2017 to only run the XML transformation task when we are not in the debug configuration.
<Project Sdk="Microsoft.NET.Sdk.Web">
<Target Name="ApplyXdtConfigTransform" BeforeTargets="_TransformWebConfig"
Condition="'$(Configuration)'!='Debug'">
<PropertyGroup>
<_SourceWebConfig>$(MSBuildThisFileDirectory)Web.config</_SourceWebConfig>
<_XdtTransform>$(MSBuildThisFileDirectory)Web.$(Configuration).config</_XdtTransform>
<_TargetWebConfig>$(PublishDir)Web.config</_TargetWebConfig>
</PropertyGroup>
<Exec Command="dotnet transform-xdt --xml '$(_SourceWebConfig)' --transform '$(_XdtTransform)'
--output '$(_TargetWebConfig)'" Condition="Exists('$(_XdtTransform)')" />
</Target>
</Project>
Building and Bundling The Angular 6 TypeScript Code
Before setting up all these environment settings, what we really needed to do first was compile and bundle the Angular 6 TypeScript code and have the build push the content to the wwwroot folder within the project.
The wwwroot folder is new in ASP.NET 5.0 and ASP.NET Core. All the static files in your project go into this folder. These are assets the application will serve directly to clients including HTML files, CSS files, image files and JavaScript files. The wwwroot folder is the root of your web site.
To compile and bundle the Angular TypeScript code, we need to execute ng build from the Angular CLI. We can have Visual Studio 2017 perform this task for us automatically by adding target tasks to the project file. A separate task is created for each configuration. We tell Visual Studio 2017 to run these tasks before the standard build task runs.
For qa, staging and production environments, we can run ng build with the --prod option so that the Angular build is optimized with the following features:
- Bundling: concatenates your application code and library files into bundles.
- Minification: removes excess whitespace, comments, and optional tokens.
- Uglification: rewrites code to use short, cryptic variable and function names.
- Dead code elimination: removes unreferenced modules and any unused code.
- Ahead-of-Time (AOT) Compilation: pre-compiles Angular component HTML templates
Below is the snippet that was added to the project file for compiling and bundling the Angular code.
<Target Name="Build Angular Debug" Condition="'$(Configuration)'=='Debug'" BeforeTargets="Build">
<Message Text="* * * * * * Building Debug Angular App * * * * * *" Importance="high" />
<Exec Command="ng build" />
</Target>
<Target Name="Build Angular Release" Condition="'$(Configuration)'=='Release'" BeforeTargets="Build">
<Message Text="* * * * * * Building Release Angular App * * * * * *" Importance="high" />
<Exec Command="ng build --prod --aot" />
</Target>
<Target Name="Build Angular QA" Condition="'$(Configuration)'=='QA'" BeforeTargets="Build">
<Message Text="* * * * * * Building QA Angular App * * * * * *" Importance="high" />
<Exec Command="ng build --prod --aot" />
</Target>
<Target Name="Build Angular Staging" Condition="'$(Configuration)'=='Staging'" BeforeTargets="Build">
<Message Text="* * * * * * Building Staging Angular App * * * * * *" Importance="high" />
<Exec Command="ng build --prod --aot" />
</Target>
Configure The Anguar CLI for WWWROOT
After getting familiar with the wwwroot folder, you start to realize how convenient this folder is for placing your Angular CLI build output. We just need to tell the Angular CLI to use wwwroot as the output folder for the build.
To do this we just need to edit the angular.json file that the Angular CLI uses for its configuration settings and change the outputPath property to "wwwroot".
"outputPath": "wwwroot",
Serving The Angular Application - Index Razor Page
When we launch the sample application through Visual Studio 2017, the default root Index Razor page is executed. The Index.csthml page lives in the Pages folder. This is the only page we will need to host and run the Angular application within ASP.NET Core.
The Index Razor page consists of a view page and a code-behind file which serves as the controller for the view. Basically Razor Pages are consolidated into a combined model-view-controller in two files that resides in the same folder. Controllers, views and models are not separated out into separate folders with Razor Pages.
The folder structure of the Pages folder determines the ASP.NET Core routing for Razor Pages. The ASP.NET Core router matches URLs to Razor Pages based on matching URLs to file paths, starting from the root Razor Pages folder, which is named Pages by default.
Index Razor Page Code-Behind Controller
When the root path of the sample application is accessed, the controller in the Index Razor page is executed which consists of a constructor and an OnGet method.
In the constructor, we grab the hosting environment configuration object that contains all the environment information that we might need for the currently running application.
After the constructor executes, the OnGet method will execute. In the snippet below, the OnGet method populates two properties; _wwwroot and _currentRoutePath. Creating these properties as public properties of the class IndexModel ends up creating the model properties for the view to access.
The Index Razor view will reference both of these properties. The WebRootPath property from the hosting environment object will return the current physical path of the wwwroot folder. The value of this property will be used to traverse the wwwroot folder so that the view can dynamically inject script tags referencing the bundled Angular JavaScript files. The bundled JavaScript files have static filenames during development but will have dynamic filenames in production for cache busting purposes when we create optimized production bundles.
When the user is on an Angular page that contains a full route in the browser and they refresh the page, ASP.NET Core will not be able to find any Razor pages for that route and an IIS server error will occur. To overcome this the route will be passed into the OnGet method and the value is mapped to the currentRoutePath property so that the Razor Page can process the request properly.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using CodeProject.AngularCore.Models.Models;
using Microsoft.Extensions.Options;
namespace CodeProject.AngularCore.Portal.Pages
{
public class IndexModel : PageModel
{
private Microsoft.AspNetCore.Hosting.IHostingEnvironment _hostingEnvironment;
public string _wwwroot { get; private set; }
public string _currentRoutePath { get; private set; }
public IndexModel(Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public void OnGet(string currentRoutePath)
{
_wwwroot = _hostingEnvironment.WebRootPath;
_currentRoutePath = "/";
if (currentRoutePath != null)
{
_currentRoutePath = currentRoutePath;
}
}
}
}
Index Razor Page View
To start-up the Angular application in the Index Razor view; all that it we really need is a reference to the application root component.
<app-root></app-root>
But this sample application has a few things it wants to do while launching the application including:
- Injecting the application settings into the Angular application
- Setting the current url route into the browser history to support page refreshes of the Angular application
- Injecting script tags into the page to reference the generated Angular JavaScript bundles
@page
@using Microsoft.Extensions.Configuration;
@using System.Net;
@using CodeProject.AngularCore.Models.Models;
@using System.IO;
@inject IConfiguration configuration
@inject Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnv
@model IndexModel
@{
AppSettings appSettings = new AppSettings();
configuration.GetSection("AppSettings").Bind(appSettings);
string settings = Convert.ToString(Json.Serialize(appSettings));
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
List<string> bundles = new List<string>();
bundles.Add("runtime.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
fileEntries = Directory.EnumerateFiles(@Model._wwwroot);
}
<p> ASPNETCORE_ENVIRONMENT = @hostingEnv.EnvironmentName</p>
<p> CURRENT ROUTE = @Model._currentRoutePath</p>
@{
<script>
history.pushState({}, null, "@Model._currentRoutePath");
</script>
<app-root settings="@settings"></app-root>
<div style="visibility:hidden; display:none">
@WebUtility.HtmlEncode(settings)
</div>
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInformation = new FileInfo(fileName);
if (fileInformation.Name.Contains(bundleName) == true &&
fileInformation.Name.Contains(".js") == true &&
fileInformation.Name.Contains(".js.map") == false)
{
<script type="text/javascript"src="@fileInformation.Name">/script>
}
}
}
}
Injecting Application Settings Into Angular
The appsettings.json structure can be bound to a class and referenced as a regular class object, For the AppSettings section, the following class was created:
namespace CodeProject.AngularCore.Models.Models
{
public class AppSettings
{
public string WebApiUrl { get; set; }
}
}
We can bind the AppSettings section to this class and serialize the object to a json string with the following statements in the Index Razor view.
@inject IConfiguration configuration
AppSettings appSettings = new AppSettings();
configuration.GetSection("AppSettings").Bind(appSettings);
string settings = Convert.ToString(Json.Serialize(appSettings));
The serialized json string can then be injected into the Angular root component as follows:
<app-root settings="@settings"></app-root>
The root Angular component app.component.ts uses ElementRef to reference the settings input variable and parses the string into a json object which is then stored into an Angular singleton session service object that can be referenced throughout the entire Angular application. The AppSettings will initially just contain the web api endpoint that the sample application will need to call. In the future the AppSettings object can be expanded for other application settings needs.
import { Component, ElementRef } from '@angular/core';
import { AppSettings } from './shared/models/appsettings.model.';
import { SessionService } from './shared/services/session.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private elementRef: ElementRef, private sessionService: SessionService) {
let native = this.elementRef.nativeElement;
let settings = native.getAttribute("settings");
let appSettings = new AppSettings();
appSettings = JSON.parse(settings);
sessionService.setAppSettings(appSettings);
}
}
Handling Page Refreshes (F5)
When the user hits F5 in the browser or refreshes an Angular page, the request will go back to the server and ASP.NET Core will try to map the browser route to a Razor page in the Pages folder. If the route is not found a server error will be generated. Since Angular routes are only defined in Angular and not server-side, we need a way to handle this.
One of the things we can do is intercept the HTTP request before ASP.NET Core trys to route the request. To do this we can edit the startup class in the ASP.NET Core project to configure the HTTP request pipeline.
The Configure method in the startup.cs file sets up the ASP.NET Core pipeline. ASP.NET Core is lightweight and we must configure what functionality we want to include in our application, including telling ASP.NET Core to use MVC, static files, cookies etc.
To intercept page requests, an app.use statement was added to startup file to listen asynchronously to web requests. When a web request comes in, the request path is checked and if a full path is passed in, the request will be redirected back to the Index Razor page. The Index Razor page will be the only page in the sample ASP.NET Core application.
IIn the below Configure method of the start up file, the current path is appended to the root route as a query string and the request is redirected to the Index Razor page. The controller for the Razor page maps the query string parameter value to the currentRoutePath property in the OnGet method as previously described in this article.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.Use(async (context, next) =>
{
var request = context.Request;
if (request.Path != "/")
{
context.Response.Redirect("/" + "?currentRoutePath=" + request.Path);
}
else
{
await next.Invoke();
}
});
}
The Index Razor page view can access the _currentRoutePath property through the Razor Page model.
Setting the browser history state using the _currentRoutePath in the Index Razor page view preserves the url in the browser and allows Angular to process the route as normal. Basically this functionality will keep the user on the same Angular page that they refreshed.
<script>
history.pushState({}, null, "@Model._currentRoutePath");
</script>
Injecting JavaScript Tags Into The Index Razor Page
Finally the Index Razor page needs to inject JavaScript script tags into the page at runtime to reference the JavaScript bundles produced by the Angular CLI build. In development mode these JavaScript tags could technically be hard coded because the JavaScript filenames will always be the same.
Things become a little more complicated when publishing a production build. The Angular CLI will generate bundles with a dynamic unique suffix appended to the bundle filename for browser cache busting on the user's client. We will never know what these filenames will end up being.
This is where the wwwroot folder comes in handy. We can simple traverse the wwwroot folder for Angular JavaScript bundles and dynamically inject JavaScript script tags for these bundles into the Index Razor view.
Using the .NET Core Directory library, we can create an enumerable list of files in the wwwroot folder.
For the purposes of injecting JavaScript script tags, we only want to inject script tags for the core Angular application bundles needed to start up the application. All the other Angular bundles will automatically be lazy-loaded as needed when the user enters different modules of the application.
A generic list of known bundle names is created and a loop will iterate through this list looking for JavaScript files and set the src attribute with each filename in the required injected script tag. The list of bundles below are ordered so that each file is injected in proper module dependency order.
@{
IEnumerable<string> fileEntries = Enumerable.Empty<string>();
fileEntries = Directory.EnumerateFiles(@Model._wwwroot);
List<string> bundles = new List<string>();
bundles.Add("runtime.");
bundles.Add("polyfills");
bundles.Add("styles.");
bundles.Add("vendor.");
bundles.Add("main.");
foreach (string bundleName in bundles)
{
foreach (string fileName in fileEntries)
{
FileInfo fileInformation = new FileInfo(fileName);
if (fileInformation.Name.Contains(bundleName) == true
&& fileInformation.Name.Contains(".js") == true
&& fileInformation.Name.Contains(".js.map") == false)
{
<script type="text/javascript" src="@fileInformation.Name"></script>
}
}
}
}
Angular CLI And LIVE RELOAD
Angular CLI is a command-line interface (CLI) to automate your development workflow. It allows you to:
- create a new Angular application
- run a development server with Live Reload support to preview your application during development
- add features to your existing Angular application
- run your application’s unit tests
- run your application’s end-to-end (E2E) tests
- build your application for deployment to production.
To start up your Angular application with the Angular CLI, you simply run the following command from the command window from inside the root folder of your project:
ng serve
After executing this command you can navigate to http://localhost:4200 to start your application in your browser. The Angular CLI comes with the Webpack bundling tool and uses Webpack to compile, build and bundle all the TypeScript code and starts a Webpack development web server which runs and listens to requests on port 4200 by default.
Of course, the real power of the Angular CLI is its support for live reloading of your application. While still running, the Webpack process is actively watching the src folder for file changes. When a file change is detected the application is automatically rebuilt and reloaded in the browser.
Development With Visual Studio Code
One of the reasons I didn't want to use the Angular template that comes with the latest version of Visual Studio 2017 for a ASP.NET Core Web Application is that I didn't want to be locked into using Visual Studio 2017 for Angular development and instead I prefer to use the Visual Studio Code IDE.
Visual Studio Code is quickly becoming the go to tool for front-end web development work. It's lightweight and comes with a large marketplace of extensions that lets you add tools and functionality to the editor that are out-of-the-box.
There are some great extensions for Visual Studio Code that greatly enhances Angular development including TypeScript linting, auto importing, code snippets and file switching etc.
To start with Visual Studio Code you can simply open the root folder of the web application project.
Visual Studio Code Debugging
One of my favorite features of Visual Studio Code is being able to debug TypeScript code and set breakpoints directly in the editor while the application is running in the browser.
The Angular CLI handles all the compiling and bundling of all the TypeScript code and modules, so all we need to do is configure Visual Studio Code for debugging.
There are three things needed for configure Visual Studio Code for debugging:
- Create a shortcut for the Chrome browser that launches Chrome with an extended command line option that enables remote debugging
- Install a Chrome Debugging Extension in Visual Studio Code
- Create a launch.json file in Visual Studio Code with debugging configuration settings
Adding --remote-debugging-port=9222 to the Chrome browser command line will tell Chrome to listen on port 9222 for any debugging notifications. To debug with Chrome you will want to close all your Chrome browsers and always start up the Chrome browser using this shortcut with the port number attached to the command line.
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
Next, from Visual Studio Code you can go into the Visual Studio Code Extensions (5th button on the left menu) and install the Debugger for Chrome extension. You can enter the name in the Search Extensions in Marketplace window to find it.
Finally you need to create a configuration file called launch.json that tells Visual Studio Code to attach to the Chrome browser to start debugging. You can create this configuration file from the debug window when you select the debug button (the 4th button in the left side menu) and add the configuration settings.
The configuration file will be saved in a folder named .vscode one level down from the root project folder. In the configuration file, the key setting is url, which tells the debugger to look for a Chrome window/tab referencing localhost:4200. The absolute url http://localhost:4200 is the default URL when the sample application starts up. The debugger will look for this exact URL when trying to attach to the browser.
The request property can be set to either attach or launch. Setting the property to launch will allow Visual Studio Code to start-up the Chrome browser right from the IDE. For this demo, Visual Studio Code will simply attach to an already running Chrome browser.
{ "version": "0.2.0",
"configurations":
[{
"name": "Attach to Chrome, with sourcemaps",
"type": "chrome",
"request": "attach",
"url""http://localhost:4200",
"port": 9222,
"sourceMaps": true,
"trace": true,
"webRoot": "${workspaceRoot}"
}]
}
While ng serve is running and serving the application in the background from the Windows command prompt, you can launch the Chrome browser from the shortcut you created on your desktop with the remote debugging port append to the command line.
From Visual Studio Code, you can begin debugging the application and setting breakpoints. To start debugging you can press the debug button and hit the green arrow button with the launch configuration named Attach to Chrome, with sourcemaps set in the debug window which references the launch.json file.
In the example below from Visual Studio Code, I set a breakpoint on line 30 directly in the TypeScript code and the application will break on this line when the component is executed in the browser. Once the breakpoint is hit you can step through the code hitting F10 (similar to Visual Studio 2017) with the yellow line indicating which line you are executing next. You get full debugging features in Visual Studio Code, including watch and debug console windows. Debugging TypeScript code directly in an IDE is a great tool for Angular and TypeScript developers.
The Angular CLI Index.Html Page
The Angular CLI comes with an index.html file that launches the Angular application when running the application from the CLI ng serve command. Since the Angular application is expecting the application settings to be injected into the application root, I had to copy and paste the JSON string into the page that was being generated by the Index Razor page. In this case the JSON string is HTML encoded.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Code Project</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root settings="{"webApiUrl":"https://localhost:44305/api/"}"></app-root>
</body>
</html>
At some point in time I'll look into a way to dynamically update this static page automatically as the structure and values of the JSON settings changes.
ASP.NET Core 2.1 IIS Integration
One of the things you will notice when creating a new ASP.NET Core project is that the project now contains a Program.cs file. ASP.NET Core web applications truely are console applications. The console application architecture facilitates the cross-platform portability goals of ASP.NET Core.
ASP.NET Core also comes with a new Kestrel web server. The Kestrel web server is a lightweight web server that runs on Windows and comes with a new request pipeline and new HTTP handlers.
One of the big problems with Internet Information Services (IIS) and the existing ASP.NET pipeline is the performance of it. For most real world applications, the performance is perfectly fine. However, it has lagged way behind in benchmarks compared to other web servers.
If you are like me, and you have been developing ASP.NET applications for awhile, you are probably familiar with IIS. It does literally anything and everything as a web server. For the purpose of this article, I have decided to deploy the sample application to IIS. Moving forward investigating other web servers like the Kestrel web server will be worth looking into.
Of course publishing an ASP.NET Core web application to IIS is not completely seamless. As stated before in the web.config, ASP.NET Core registers an ASP.NET Core module as an HTTP handler. This module handles all incoming traffic to IIS and acts as a reverse proxy that knows how to hand the traffic off to your ASP.NET Core application. Its a bridge between IIS and ASP.NET Core.
Before you deploy your application, you'll need to do the following:
- Download the .NET Core Windows Hosting SDK 2.1
- Install the .NET Core hosting bundle for IIS. This will install the .NET Core runtime, libraries, and the ASP.NET Core module for IIS.
- Restart the IIS web server
- Create a new IIS Application Pool. You willl need to create one under the .NET CLR version of “No Managed Code“. Since this module works as a reverse proxy, it isn’t actually executing any .NET code.
- Create your new application under your existing IIS Site, or create a new IIS site. Either way, you will want to pick the new IIS Application Pool and point the new site to the folder where you will be publishing your application.
Summary
This article focused heavily on setting up and configuring Visual Studio 2017 to be able to build and deploy an Angular 6 web application hosted in an ASP.NET Core web application while also discussing how to take advantage of debugging TypeScript code and setting break points using Visual Studio Code and using the Live Reload functionality of Angular CLI. The new Razor Pages architecture introduced in ASP.NET Core represents a slimmer implementation of the ASP.NET MVC pattern and seems ideal for serving up an Angular application within a ASP.NET Core web application.
The sample application included in this article is just a shell application with no current functionality. In a follow-up article, this application will be built out to contain functionality that interacts with a ASP.NET Core Web API project while also incorporating the features of Entity Framework (EF) Core to update a backend database.
The Microsoft .NET Core framework is lightweight and supports running server applications cross-platform on Windows, Linux, Unix and MacOs. The ASP.NET Core technology stack looks promising and could be the next big thing for Microsoft developers as it continues to mature.
Requirements
To run the sample application attached to this article, you will need to install the following:
- Visual Studio 2017 15.7.3 or greater
- .NET Core cross-platform development (install with Visual Studio 2017)
- .NET Core Windows Hosting SDK 2.1
- Visual Studio Code 1.24 or greater (optional)
- NodeJs (latest version)
- Angular CLI (latest version)
The node_modules folder has been excluded from the source code download attached to this article. After downloading and installing the above items, open the project solution file in Visual Studio 2017 and the npm dependencies for the project should automatically start restoring into the node_modules folder. If the restore does not occur automatically you can manually start the restore process by right-clicking on the Dependencies/npm folder in Visual Studio 2017 and select Restore Packages.