Implementing a Angular project with AspNet Core
Tags: typescript, angular, aspnetcore, csharp
Published: 2019-11-04 15:13:20

<!-- wp:paragraph -->
<p>This article will help us understand how to use the .NET Core command line interface to create and run an ASP.NET Core web application.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This way of implementing an Angular project provides a convenient starting point for ASP.NET Core applications that use Angular and the Angular CLI to implement a rich client-side user interface (UI).</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The mechanism is equivalent to creating an ASP.NET Core project to act as an API back-end and an angular CLI project to act as an UI. This route offers the convenience of hosting both types of projects in a single application project. Consequently, the application project can be built and published as a single unit, which also allows us to deploy it to nopcommerce as a plugin.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You'll learn how to:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Create a web app project</li><li>How to use the SPA<strong> </strong>subdirectory<strong> </strong>approach</li><li>Add pages, images, styles, modules</li><li>Run the app</li><li>Publish and deploy</li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":5} -->
<h5><strong>Create a web app project</strong></h5>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Open a command shell, and enter the following command:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>dotnet new angular</code></pre>
<!-- /wp:code -->

<!-- wp:image {"id":558} -->
<figure class="wp-block-image"><img src="https://diogenespolanco.com/wp-content/uploads/2019/11/DeepinScreenshot_select-area_20191103000136.png" alt="" class="wp-image-558"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Run&nbsp;<a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build">dotnet build</a>&nbsp;to verify the app builds correctly. On the first run, the build process restores npm dependencies, which can take several minutes. Subsequent builds are much faster.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":560} -->
<figure class="wp-block-image"><img src="https://diogenespolanco.com/wp-content/uploads/2019/11/DeepinScreenshot_select-area_20191103000316.png" alt="" class="wp-image-560"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Run&nbsp;<a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-run">dotnet run</a>&nbsp;to start the app. A message similar to the following is logged:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>Now listening on: http://localhost:&lt;port></code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Navigate to this URL in a browser.<br></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In ASP.NET Core, supporting SPAs works via a middleware to then point to this directory (no matter what you call it). It does this by injecting the middleware as the last step in Startup’s Configure method:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace test
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            // In production, the Angular files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            if (!env.IsDevelopment())
            {
                app.UseSpaStaticFiles();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                // To learn more about options for serving an Angular SPA from ASP.NET Core,
                // see https://go.microsoft.com/fwlink/?linkid=864501

                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });
        }
    }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This involves two calls:&nbsp;<strong>UseSpaStaticFiles&nbsp;</strong>and&nbsp;<strong>UseSpa</strong>. These calls set up the ability to get calls from a generated index.html that Angular-CLI builds for you. In this case, you’ll see that during development, it will launch ‘npm start’ for you; that is part of how it launches the development build of the Angular app.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>While the middleware is a good option if you’re building a single, monolithic SPA without much use for other web pages, I think for most developers, integrating ASP.NET Core into a traditional MVC application makes the most sense. You can typically keep an SPA as a separate folder and treat it as a child-project, without having to do a lot of integration between the two projects.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4><strong>How to use the SPA subdirectory approach</strong></h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>suppose you have an Angular project in a subdirectory called&nbsp;<strong>ClientApp</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Because this directory has its own<strong>&nbsp;package.json</strong>, you can just use the CLI of the library you’re working with to do builds and such. You could even develop it in isolation without invoking the ASP.NET Core project, but since I’m usually adding the SPA to an existing Razor view, I generally just run them both.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>One issue with this method is that the build directory for the SPA project is usually in this directory. You could use the&nbsp;<strong>SpaStaticFiles&nbsp;</strong>middleware to solve this, but I usually just change the configuration of the SPA to build into my&nbsp;<strong>wwwroot&nbsp;</strong>directory. For example, this is what that looks like in the angular.json file (for the Vue.js CLI):</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "test": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "progress": true,
            "extractCss": true,
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": ["src/assets"],
            "styles": [
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "test:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "test:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "test:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "karmaConfig": "src/karma.conf.js",
            "styles": ["styles.css"],
            "scripts": [],
            "assets": ["src/assets"]
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"],
            "exclude": ["**/node_modules/**"]
          }
        },
        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist-server",
            "main": "src/main.ts",
            "tsConfig": "src/tsconfig.server.json"
          },
          "configurations": {
            "dev": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": true
            },
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false
            }
          }
        }
      }
    },
    "test-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "test:serve"
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": "e2e/tsconfig.e2e.json",
            "exclude": ["**/node_modules/**"]
          }
        }
      }
    }
  },
  "defaultProject": "test"
}
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>In this case, I’m just telling the Vue.js CLI to output the build to a subdirectory of the&nbsp;<strong>js&nbsp;</strong>folder in&nbsp;<strong>wwwroot</strong>, so I don’t need to configure ASP.NET Core to look into the SPA directory directly. Angular and React both support this kind of output directory, if you don’t want to use the&nbsp;<strong>SpaStaticFiles&nbsp;</strong>middleware.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>By using one or more subdirectories with their own build, you can have many SPA projects (even if they’re different frameworks) in one ASP.NET Core project. But it does have some limitations involving deployment and dependency management. By doing this, each project will have it’s own node_modules directory which means that build times can get very lengthy.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Because you’re configuring the output directory into&nbsp;<strong>wwwroot</strong>, you don’t need to specify including a directory when you deploy the project, but you will need to tell the MSBuild (e.g.&nbsp;<strong>csproj&nbsp;</strong>file) to build the SPA project before you deploy. The trick here is to add a Target to your .<strong>csproj&nbsp;</strong>file:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  &lt;Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    &lt;!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    &lt;Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    &lt;Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
    &lt;Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />

    &lt;!-- Include the newly-built files in the publish output -->
    &lt;ItemGroup>
      &lt;DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
      &lt;DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
      &lt;ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        &lt;RelativePath>%(DistFiles.Identity)&lt;/RelativePath>
        &lt;CopyToPublishDirectory>PreserveNewest&lt;/CopyToPublishDirectory>
        &lt;ExcludeFromSingleFile>true&lt;/ExcludeFromSingleFile>
      &lt;/ResolvedFileToPublish>
    &lt;/ItemGroup>
  &lt;/Target>
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The&nbsp;<strong>Target&nbsp;</strong>specifies that it should consider something before it computes the files to publish (so before it looks at&nbsp;<strong>wwwroot&nbsp;</strong>for the files to include). The two ‘<strong>Exec</strong>’ lines are just a way to run the install and build steps in the&nbsp;<strong>ClientApp</strong>&nbsp;directory. This is only executed during a publish, so it won’t impact your development cycle.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The big drawback is more about Visual Studio than about ASP.NET Core. Because the&nbsp;<strong>project.json</strong>&nbsp;is in a subdirectory (not in the root of the ASP.NET Core project), you can’t get the Task Runner Explorer to find the build. This means you’ll have to run it separately. If you’re using Visual Studio Code, Rider, or other IDE, you might already be used to opening console/terminal windows and doing the build yourself so this might not be much of a hardship.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The other limitation is related to this one, more than one&nbsp;<strong>package.json</strong>&nbsp;to manage. This is the one that hurts me the most. Most of my ASP.NET Core projects already use NPM to manage other dependencies (both dev and production dependencies). Having to manage two or more&nbsp;<strong>package.json</strong>&nbsp;files makes me a little frustrated. That’s why I usually resolve to use the last of the options — fully integrate the SPA into the ASP.NET Core project.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4><strong>Add pages, images, styles, modules</strong></h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The&nbsp;<em>ClientApp</em>&nbsp;directory contains a standard Angular CLI app. See the official&nbsp;<a href="https://github.com/angular/angular-cli/wiki">Angular documentation</a>&nbsp;for more information.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>There are slight differences between the Angular app created by this template and the one created by Angular CLI itself (via&nbsp;<code>ng new</code>); however, the app's capabilities are unchanged. The app created by the template contains a&nbsp;<a href="https://getbootstrap.com/">Bootstrap</a>-based layout and a basic routing example.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4><strong>Run the app</strong></h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>If you have the&nbsp;<code>ng</code>&nbsp;tool installed globally, you can run any of its commands. For example, you can run&nbsp;<code>ng lint</code>,&nbsp;<code>ng test</code>, or any of the other&nbsp;<a href="https://github.com/angular/angular-cli/wiki#additional-commands">Angular CLI commands</a>. There's no need to run&nbsp;<code>ng serve</code>&nbsp;though, because your ASP.NET Core app deals with serving both server-side and client-side parts of your app. Internally, it uses&nbsp;<code>ng serve</code>&nbsp;in development.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you don't have the&nbsp;<code>ng</code>&nbsp;tool installed, run&nbsp;<code>npm run ng</code>&nbsp;instead. For example, you can run&nbsp;<code>npm run ng lint</code>&nbsp;or&nbsp;<code>npm run ng test</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In a command prompt, switch to the&nbsp;<em>ClientApp</em>&nbsp;subdirectory:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>cd ClientApp &amp;&amp; npm start</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Use&nbsp;<code>npm start</code>&nbsp;to launch the Angular CLI development server, not&nbsp;<code>ng serve</code>, so that the configuration in&nbsp;<em>package.json</em>&nbsp;is respected. To pass additional parameters to the Angular CLI server, add them to the relevant&nbsp;<code>scripts</code>&nbsp;line in your&nbsp;<em>package.json</em>&nbsp;file.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4><strong>Publish and deploy</strong></h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>In development, the app runs in a mode optimized for developer convenience. For example, JavaScript bundles include source maps (so that when debugging, you can see your original TypeScript code). The app watches for TypeScript, HTML, and CSS file changes on disk and automatically recompiles and reloads when it sees those files change.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In production, serve a version of your app that's optimized for performance. This is configured to happen automatically. When you publish, the build configuration emits a minified, ahead-of-time (AoT) compiled build of your client-side code. Unlike the development build, the production build doesn't require Node.js to be installed on the server (unless you have enabled server-side rendering (SSR)).</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I will soon be publishing the code on GitHub and some extra steps to deploy it in the cloud ...</p>
<!-- /wp:paragraph -->