Click here to Skip to main content
13,766,591 members
Click here to Skip to main content
Add your own
alternative version

Stats

32.9K views
27 bookmarked
Posted 2 Jun 2018
Licenced CPOL

Angular 6 within ASP NET Core 2.1

, 2 Jun 2018
Rate this:
Please Sign up or sign in to vote.
The easiest way to mix both worlds

Introduction

After a series of previews, Microsoft announced the final release of .NET Core 2.1, which includes newer version for ASP.NET Core and Entity Framework Core. As you probably know, the official Angular SPA template was using Angular 4 and just hours after the .NET Core 2.1 release, I tried the new Angular template and it was using version 5, and that was quite disappointing.

Background

During the last couple of months, many developers around the world have been trying to mix ASP NET Core and Angular 6 and, yes, I've read and tried almost all recipes on every blog post without good results, some recipes worked for me at a 85%, some others didn't work at all, so I decided to start my own recipe from scratch. I did it combining steps and techniques seen in all of those articles and it worked for me.

At that point, I was asked for the recipe by a friend of mine but due to the lack of time I decided to bring him a template to let him create a working project with few commands. That happened on Tuesday evening and I created a GitHub repository to share this with everybody on Wednesday morning just before starting to work, but just at that time, .NET Core 2.1 was released and I felt forced to update the repo to have it a 100% up-to-date.

Using the Code

I'll explain in this article how I did it, how it evolved, the challenges I faced and how I fixed but at this point, if you are curious, you might go to that repository, clone it, install the template and start developing Angular 6 with ASP NET Core 2.1.

This template includes:

  1. NET Core API 2.1 (basic MVC, with ValuesController as usual)
  2. Swagger (just navigate to your '/swagger')
  3. Angular 6 project with Angular CLI (6.0.7) including:
    1. MDBootstrap (https://mdbootstrap.com/)
    2. ngx-mapbox-gl (https://github.com/Wykks/ngx-mapbox-gl)
    3. rxjs & rxjs-compat
  4. Docker support (just select docker-compose as startup project and run it)

Install from the git repo

  1. Git clone this repo
  2. Install the template dotnet new --install .\content
  3. Create a directory where you would like to store the project
  4. Type `dotnet new angular6 -n <name_of_your_project>
  5. Open the solution and run the project
  6. Enjoy coding!

How I Did It?

Well, best recipes agreed on the same steps to start from scratch, and those are:

  1. Update your environment (npm, Node and Angular CLI)
  2. Create a new Angular project
  3. Get inside new generated project folder and create, with dotnet CLI, a new Angular SPA project with 'dotnet new angular -n ProjectName'
  4. Refactor to mix both ecosystems

That final step was the most challenging one, on the one hand, you have the ClientApp folder generated by the SPA template but that includes an Angular 4 application and also you have the src folder with the ng-generated Angular 6 application, so, the first step for me was to get rid of every Angular 4 stuff, this includes webpack.config removal and many .ts refactors that, today, I'm unable to list. To be honest, I got into a try-failure loop that consumed my spare time during the last couple of weeks.

How It Evolved?

And finally, I got a working version that satisfied me, without tricks, a clean solution with a clean folder structure that would allow me to grow on both sides of my developments, frontend with Angular 6 and backend with .NET Core, version 2 at that time. And I saved a snapshot just in case.

Then I added Swagger, with Swasbucle's NuGet package and I could see my page meanwhile I was able to access the swagger interface and call an API Controller. And I saved a snapshot just in case.

Then I went for web styling, and I was tried Bootstrap, Angular Material and Bootstrap Material Design UI KIT (aka MDBootstrap), I decided to go for MDBootstrap because it is the one I like the most. And it worked, well, There are few warnings at compile time but I can live with it. And I added a dummy navbar and a dummy footer, and also a dummy animation, and everything worked... And I saved a snapshot just in case.

And as in my project I need to work with maps, even though I already knew the answer, I asked for best maps integration in Angular and I was told to go for mapbox by using ngx-mapbox-gl. And I went for it, I installed, configured it basically, I inserted a map in the site and it worked... And I saved a snapshot just in case.

(NOTE: I made a newbie mistake, but it doesn't matter, the repo has my mapbox token, it will work for everyone but I have to update the example to avoid that and then update the Readme with some quickstart instructions.)

And then, on Wednesday at 5 a.m, I decided to share this with the world so I created a template project and I renamed few things to allow the template to create a working solution with three steps:

  1. Clone the repo
  2. Install the template
  3. Create new project (dotnet new angular6)

And then .NET Core 2.1 was released, but as the SPA came with Angular 5.2 instead of Angular 6, I decided to update my project to be Angular 6 and Core 2.1, and it worked, and I pushed it to the repo.

And then, I was asked if I had been able to run this in docker, and, well, I really hadn't tried that out yet with this solution and, indeed, that had been one of the challenging things at the beginning because I was able to run everything locally but not inside a Docker container. I added Docker support to the project and I spent few hours improving the docker file stages until I reached a clean small set of stages that did the work quite quick and I pushed that to the repo.

Code Details

Mainly two files are important here. Startup.cs and angular.json because those files are the ones that orchestrates the behaviour and interaction between both worlds, dotnet and angular.

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.IO;

namespace AspNetCore2Ang6Template
{
  /// <summary>
  /// Startup class
  /// </summary>
  public class Startup
  {
    /// <summary>
    /// Configures app the services.
    /// </summary>
    /// <param name="services">The services.</param>
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddMvc();
      services.AddSpaStaticFiles(c =>
      {
        c.RootPath = "wwwroot";
      });

      services.AddSwaggerGen(c =>
      {
        c.SwaggerDoc("v1", new Info
        {
          Version = "v1",
          Title = "AspNetCore2Ang6Template API",
          Description = "A simple example ASP.NET Core Web API",
          Contact = new Contact { Name = "Juan García Carmona", 
                    Email = "d.jgc.it@gmail.com", Url = "https://wisegeckos.com" },
        });
        // Set the comments path for the Swagger JSON and UI.
        var basePath = AppContext.BaseDirectory;
        var xmlPath = Path.Combine(basePath, "AspNetCore2Ang6Template.xml");
        c.IncludeXmlComments(xmlPath);
      });
    }

    /// <summary>
    /// Configures the application.
    /// </summary>
    /// <param name="app">The application.</param>
    /// <param name="env">The hosting environment</param>
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }

      app.UseDefaultFiles();
      app.UseStaticFiles();

      // Enable middleware to serve generated Swagger as a JSON endpoint.
      app.UseSwagger();

      // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 
      // specifying the Swagger JSON endpoint.
      app.UseSwaggerUI(c =>
      {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
      });
      app.UseStaticFiles();
      app.UseSpaStaticFiles();
      app.UseMvc(routes =>
      {
        routes.MapRoute(name: "default", template: "{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 = "wwwroot";

        if (env.IsDevelopment())
        {
          spa.UseAngularCliServer(npmScript: "start");
        }
      });
    }
  }
}

As you can see, startup mixes MVC and SPA using as source path the well-known folder 'wwwroot'.

Angular.json (just important part):

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "AspNetCore2Ang6Template": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {
        ":component": {
          "styleext": "scss"
        }
      },
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "wwwroot",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "node_modules/font-awesome/scss/font-awesome.scss",
              "node_modules/angular-bootstrap-md/scss/bootstrap/bootstrap.scss",
              "node_modules/angular-bootstrap-md/scss/mdb-free.scss",
              "node_modules/mapbox-gl/dist/mapbox-gl.css",
              "src/styles.scss"
            ],
            "scripts": [
              "node_modules/chart.js/dist/Chart.js",
              "node_modules/hammerjs/hammer.min.js"
            ]
          },
          "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": "AspNetCore2Ang6Template:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "AspNetCore2Ang6Template:build:production"
            }
          }
        }, [ETC...]
  • It establishes source root as src, which means that the application source code is under src folder.
  • It also adds scss support, because it was a requirement for MDBootstrap.
  • It determines that the output path is wwwroot, which is what makes more sense and, hell, I haven't seen this in other places, at least not in a direct and clean approach.
  • It replaces environment configuration when building in production mode.

Also, I'm proud of this Docker file because I spent, as I said before, a couple of hours, mainly because I hadn't understood multi-stage docker files.

FROM microsoft/dotnet:2.1-sdk as build-env
WORKDIR /app

# Setup node
ENV NODE_VERSION 8.11.1
ENV NODE_DOWNLOAD_SHA 0e20787e2eda4cc31336d8327556ebc7417e8ee0a6ba0de96a09b0ec2b841f60

RUN curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" 
         --output nodejs.tar.gz \
    && echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \
    && tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \
    && rm nodejs.tar.gz \
    && ln -s /usr/local/bin/node /usr/local/bin/nodejs\
    && npm i -g @angular/cli npm i -g @angular/cli

# Copy csproj and restore as distinct layers
COPY src/AspNetCore2Ang6Template/ ./AspNetCore2Ang6Template/
RUN cd AspNetCore2Ang6Template \
    && dotnet restore \
    && npm install

RUN cd AspNetCore2Ang6Template \
    && dotnet publish -c Release -o out

# build runtime image
FROM microsoft/dotnet:2.1-aspnetcore-runtime
WORKDIR /app

# Setup node
ENV NODE_VERSION 8.11.1
ENV NODE_DOWNLOAD_SHA 0e20787e2eda4cc31336d8327556ebc7417e8ee0a6ba0de96a09b0ec2b841f60

RUN curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" 
         --output nodejs.tar.gz \
    && echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \
    && tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \
    && rm nodejs.tar.gz \
    && ln -s /usr/local/bin/node /usr/local/bin/nodejs\
    && npm i -g @angular/cli

COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "AspNetCore2Ang6Template.dll"]

Article Improvements
  • It uses newest microsoft/dotnet image as build environment.
  • It installs on a single step, node and npm and finally Angular cli.
  • In the next step, it gets into the folder and, again, in a single RUN, it restores project dependencies, the NuGet packages by dotnet restore and the npm with npm install.
  • Then we switch to the runtime environment, microsoft/dotnet:2.1-aspnetcore-runtime, and we install node npm and angular cli in this environment too.
  • We copy build environment output here.
  • And we run our DLL.

Note that to whoever uses this template, at every place, during project creation with dotnet new, the dotnet finds AspNetCore2Ang6Template keyword, it will replace that with your project name. I used this approach because I've found many POCs on GitHub that at the end implies huge refactorings. I'd say that with three steps, create a project that looks yours from scratch seems a better idea.

Final Words

I'm sure that this can help many other developers out there but also, I'm willing to give direct help, so don't hesitate to leave a comment or use a direct approach by contacting me by email or LinkedIn (it's easy to find me). As always, I hope it helps.

History

  • Article created on 02/June/2018 by Juan García Carmona
  • Few minutes later: added Startup.cs and Angular.json code
  • And few minutes later, I added the Docker file and its explanation

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Juan G. Carmona
Technical Lead
Spain Spain
Father, husband, mountaineer, developer and software architect.

I enjoy solving problems and producing high quality software.


You may also be interested in...

Comments and Discussions

 
QuestionRunning the app but got stuck Pin
Member 139697551-Sep-18 23:35
memberMember 139697551-Sep-18 23:35 
QuestionTrouble Installing your template Pin
Member 139697551-Sep-18 14:34
memberMember 139697551-Sep-18 14:34 
QuestionTrouble Installing Pin
Member 1392709126-Jul-18 8:34
memberMember 1392709126-Jul-18 8:34 
AnswerRe: Trouble Installing Pin
Jonah Lee16-Aug-18 17:17
memberJonah Lee16-Aug-18 17:17 
QuestionPublish to IIS Pin
Member 139021167-Jul-18 1:06
memberMember 139021167-Jul-18 1:06 
BugCannot create the project Pin
JanBorup5-Jun-18 3:27
memberJanBorup5-Jun-18 3:27 
GeneralRe: Cannot create the project Pin
Member 100280165-Jun-18 4:33
memberMember 100280165-Jun-18 4:33 
QuestionNice job, but... Pin
Klaus Luedenscheidt3-Jun-18 5:57
memberKlaus Luedenscheidt3-Jun-18 5:57 
PraiseThank you Pin
Member 100280162-Jun-18 17:59
memberMember 100280162-Jun-18 17:59 
GeneralRe: Thank you Pin
Member 100280163-Jun-18 3:40
memberMember 100280163-Jun-18 3:40 
GeneralRe: Thank you Pin
Juan G. Carmona3-Jun-18 13:21
professionalJuan G. Carmona3-Jun-18 13:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06-2016 | 2.8.181114.1 | Last Updated 2 Jun 2018
Article Copyright 2018 by Juan G. Carmona
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid