Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Master Chef (Part 3) ASP.NET Core MVC with Entity Framework Core and Angular2

4.89/5 (16 votes)
26 Jan 2017CPOL16 min read 33K   555  
From this article, I’d like create ASP.NET Core (Net Core) application with Angular 2.

Introduce

In Master Chef Part 1 and Master Chef Part 2, I introduced how to integrate ASP.NET Core (Framework) with Angular JS and Fluent NHibernate. In this article, I’d like create an ASP.NET Core (Net Core) application. You should know NHibernate doesn’t have Net Core version yet, so I switch to Entity Framework Core. On 9/14/2016, Angular 2 Final release was out. Angular 2 is the second major installment of AngularJS and is entirely written in TypeScript. For the developers who are working with Angular 1.x, you might find a big change in Angular 2, as it is entirely component based and an object orientation, is much easier with enhanced DI capability. I’ll show you how to build Angular2 application in Visual Studio 2015 Update 3.

Create MasterChef2 Application in Visual Studio 2015 Update 3

Download and install the latest ASP.NET Web Tools from https://marketplace.visualstudio.com/items?itemName=JacquesEloff.MicrosoftASPNETandWebTools-9689. Then install TypeScript 2 from https://www.microsoft.com/en-us/download/details.aspx?id=48593.

Form Visual C#/Web, select ASP.NET core Web Application (.NET Core).

Image 1

ASP.NET Core has two kinds of applications.

  1. ASP.NET Core .NET Framework Application is application running on Windows using .NET Framework.
  2. ASP.NET Core .NET Core Application is cross-platform application running on Windows, Linux, and OS X using .NET Core.

This time we select web application .NET core.

Image 2

Select "Empty" template and untick "Host in cloud".

Image 3

Have a look at ASP.NET Core Web solution structure. It creates "src" folder and the actual project is under "src" folder. Within this src folder is a special folder here - wwwroot, which is a folder that's going to hold all of our live web files.

Update project.json (Nuget Package)

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.1",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Diagnostics": "1.0.0",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.1",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0",
    "Microsoft.EntityFrameworkCore.SqlServer" : "1.1.0",
    "Microsoft.EntityFrameworkCore.Tools": {
      "version": "1.1.0-preview4-final",
      "type": "build"
    },
    "Microsoft.EntityFrameworkCore.Design": "1.1.0",
    "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.1.0",
    "Newtonsoft.Json": "9.0.1"
  },

  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
    "Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final"
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

 

What’s added? I add Asp.NetCore.MVC, Asp.NetCore.StaticFiles, EntityFrameworkCore and NewtonSoft.Json.

After save project.json, Visual Studio automatically restores the references.

Image 4

 

Configure MVC and Static Files

Go to Startup.cs.

Add services.AddMvc() in ConfigureServices(…), and add app.UseMvc in Configure(…).

Add app.UseDefaultFiles() and app.UseStaticFiles() to make index.html under wwwroot serve to client directly.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseMvc();
    app.UseDefaultFiles();
    app.UseStaticFiles();
}

 

Create models from the existing database

We have created MasterChef database already in the previous articles. Now we need create model classes from the existing database. To enable reverse engineering from an existing database we need to install Microsoft.EntityFrameworkCore.Tools, Microsoft.EntityFrameworkCore.Design, and Microsoft.EntityFrameworkCore.SqlServer.Design.

Tools –> NuGet Package Manager –> Package Manager Console

Run the following command to create a model from the existing database. If you receive an error stating the term 'Scaffold-DbContext' is not recognized as the name of a cmdlet, then close and reopen Visual Studio.

Scaffold-DbContext "Server=.\sqlexpress;Database=MasterChef;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models

Image 5

After running this command, you can see MasterChefContext.cs, Recipes.cs, RecipeSteps.cs and RecipeItems.cs which is created under the Models folder. I’m not happy with the Model class name with plural ‘s’.

So I renamed Recipes.cs to Recipe.cs, RecipeSteps.cs to RecipeStep.cs, and RecipeItems.cs to RecipeItem.cs.

Image 6

Then create repository class.

We use repository pattern to separate the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. The business logic should be agnostic to the type of data that comprises the data source layer.

The repository mediates between the data source layer and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source. A repository separates the business logic from the interactions with the underlying data source.

When we get a list of recipe objects, we need to load recipe steps and recipe items as well. How can we do that in Entity Framework Core?

Entity Framework Core allows you to use the navigation properties in your model to load related entities. There are three common O/RM patterns used to load related data.

  1. Eager loading means that the related data is loaded from the database as part of the initial query.
  2. Explicit loading means that the related data is explicitly loaded from the database at a later time.
  3. Lazy loading means that the related data is transparently loaded from the database when the navigation property is accessed. Lazy loading is not yet possible with EF Core.

Here we use the Include method to specify related data to be included in the query results.

public IList<recipe> GetAllRecipes()
{
    try
    {
        var recipes = _dbContext.Recipes.Include(x => x.RecipeSteps).ThenInclude(y => y.RecipeItems).ToList();
        recipes.ForEach(x => x.RecipeSteps = x.RecipeSteps.OrderBy(y => y.StepNo).ToList());
        return recipes;

    }
    catch (Exception ex)
    {
        throw ex;
    }
}

 

Add Web API Controller

Create the "api" folder, then right click the api folder to add new item. In ASP.NET select Web API Controller Class template. We name our class to RecipesController.cs

Image 7

In the RecipesController class, for the time being we only set up a GET request here requesting all recipes.

We add _repository member to handle the database stuff.

[Route("api/[controller]")]
public class RecipesController : Controller
{
    Repository _repository = Repository.Instance;
    // GET: api/recipes
    [HttpGet]
    public IEnumerable<recipe> Get()
    {
        return _repository.GetAllRecipes();
    }
}

Now from browser we test to see if the web API is working or not.

Run from IIS Express, and get the below result.

Image 8

It looks like working. But if you run a query from the database, you’ll find the result is not quite right. It looks like only gets the first recipe, recipe step and recipe item. What’s happening? Let’s debug. I put the break point in the Repository class. Has a quick watch of the query result.

Image 9

Entity Framework Core is working properly. Then the problem is happening during the JSON serialization. Finally, I find the reason. The reference loop causes JSON serialization to not work properly.

Have a look our model classes. Recipe has reference collection RecipeSteps, and RecipeStep has a reference item Recipe. They reference each other cause the reference loop. Similarly, RecipeStep has reference collection RecipeItems, and RecipeItem has a reference item RecipeStep. They cause the reference loop too.

The solution is to let JSON ignore Recipe in RecipeStep and RecipeStep in RecipeItem. Thus the reference loop is gone and serialization will be done properly.

RecipeStep class.

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace MasterChef2WebApp.Models
{
    public partial class RecipeStep
    {
        public RecipeStep()
        {
            RecipeItems = new HashSet<recipeitem>();
        }

        public Guid RecipeStepId { get; set; }
        public Guid RecipeId { get; set; }
        public int StepNo { get; set; }
        public string Instructions { get; set; }

        public virtual ICollection<recipeitem> RecipeItems { get; set; }
        [JsonIgnore]
        public virtual Recipe Recipe { get; set; }
    }
}

RecipeItem class.

using System;
using Newtonsoft.Json;

namespace MasterChef2WebApp.Models
{
    public partial class RecipeItem
    {
        public Guid ItemId { get; set; }
        public Guid RecipeStepId { get; set; }
        public string Name { get; set; }
        public decimal Quantity { get; set; }
        public string MeasurementUnit { get; set; }
        [JsonIgnore]
        public virtual RecipeStep RecipeStep { get; set; }
    }
}

Now we run it again to check the web API, and everything is coming this time.

Image 10

TypeScript

TypeScript is a free and open source programming language developed and maintained by Microsoft. It is a strict superset of JavaScript, and adds optional static typing and class-based object-oriented programming to the language. For a large client-side project, TypeScript will allow us to produce a more robust code, which will be also fully deployable anywhere a plain JavaScript file would run. Since TypeScript is a superset of JavaScript it can be used alongside any JavaScript code without problems. Using TypeScript in Visual Studio also enables a strong intelliSense.

Add the tsconfig.json

First we need to add a type script configuration file. Just right-click on your project to add new item. In client-side select TypeScript JSON Configuration File.

Image 11

Replace the default setting with the below.

{
  "compileOnSave": false,
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "system",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noEmitOnError": false,
    "removeComments": false,
    "sourceMap": true,
    "target": "es5"
  },
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

The compileOnSave signals the IDE to generate all files for a given tsconfig.json upon saving. compilerOptions configuration will influence how the Intellisense, and our external TypeScript compiler will work. By excluding the folders in the config, we tell the built-in TypeScript compiler provided by Visual Studio 2015 to disable compiling the external TypeScript files within that location. Please note we will use NPM to download typescript packages including Angular2. All these packages will be under the node_modules folder. We don’t want Visual Studio to compile them, that’s why we exclude node_modules folder.

NPM

NPM is originally created for managing packages for the open-source NodeJS framework. The package.json is the file that manages your project's NPM packages.

Right-click in the Project and select Add > New Item. In the dialog select "NPM configuration file".

Image 12

Modify the "package.json" file by adding the following dependencies.

{
  "version": "1.0.0",
  "name": "asp.net",
  "dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/router": "3.0.0",
    "@angular/upgrade": "2.0.0",
    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.8",
    "rxjs": "5.0.0-rc.4",
    "systemjs": "^0.19.41",
    "typings": "^1.3.2",
    "zone.js": "^0.7.2",
    "moment": "^2.17.0"
  },
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-clean": "^0.3.2",
    "gulp-concat": "^2.6.1",
    "gulp-less": "^3.3.0",
    "gulp-sourcemaps": "^1.9.1",
    "gulp-typescript": "^3.1.3",
    "gulp-uglify": "^2.0.0",
    "typescript": "^2.0.10"
  },
  "scripts": {
    "postinstall": "typings install dt~core-js --global"
  }
}

Image 13

After save "package.json", Visual Studio restores packages automatically. All packages are installed under node_modules folder. The packages with the @ symbol is part of the new Angular 2 bundle: the other ones are loading libraries, helper tools.

Gulp

Much like Grunt, Gulp is a JavaScript task runner. Gulp, however, prefers code over configuration. Being that your tasks are written in code, Gulp feels more like a build framework, giving you the tools to create tasks that fit your specific needs. We will be using Gulp as the JavaScript Task Runner to automate our client-side scripts.

Add a new file for our Gulp configuration. Right click on the project solution and then select Add > New Item. Under Client-side template, select "Gulp Configuration File"

Image 14

Then replace the default generated configuration with the following code below:

var gulp = require('gulp'),
    gp_clean = require('gulp-clean'),
    gp_concat = require('gulp-concat'),
    gp_less = require('gulp-less'),
    gp_sourcemaps = require('gulp-sourcemaps'),
    gp_typescript = require('gulp-typescript'),
    gp_uglify = require('gulp-uglify');

/// Define paths
var srcPaths = {
    app: ['Scripts/app/main.ts', 'Scripts/app/**/*.ts'],
    js: [
        'Scripts/js/**/*.js',
        'node_modules/core-js/client/shim.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system.src.js',
        'node_modules/typescript/lib/typescript.js',
        'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js',
        'node_modules/moment/moment.js'
    ],
    js_angular: [
        'node_modules/@angular/**'
    ],
    js_rxjs: [
        'node_modules/rxjs/**'
    ]
};

var destPaths = {
    app: 'wwwroot/app/',
    js: 'wwwroot/js/',
    js_angular: 'wwwroot/js/@angular/',
    js_rxjs: 'wwwroot/js/rxjs/'
};

// Compile, minify and create sourcemaps all TypeScript files 
// and place them to wwwroot/app, together with their js.map files.
gulp.task('app', ['app_clean'], function () {
    return gulp.src(srcPaths.app)
        .pipe(gp_sourcemaps.init())
        .pipe(gp_typescript(require('./tsconfig.json').compilerOptions))
        .pipe(gp_uglify({ mangle: false }))
        .pipe(gp_sourcemaps.write('/'))
        .pipe(gulp.dest(destPaths.app));
});

// Delete wwwroot/app contents
gulp.task('app_clean', function () {
    return gulp.src(destPaths.app + "*", { read: false })
    .pipe(gp_clean({ force: true }));
});

// Copy all JS files from external libraries to wwwroot/js
gulp.task('js', function () {
    gulp.src(srcPaths.js_angular)
        .pipe(gulp.dest(destPaths.js_angular));
    gulp.src(srcPaths.js_rxjs)
        .pipe(gulp.dest(destPaths.js_rxjs));
    return gulp.src(srcPaths.js)
        .pipe(gulp.dest(destPaths.js));
});


// Watch specified files and define what to do upon file changes
gulp.task('watch', function () {
    gulp.watch([srcPaths.app, srcPaths.js], ['app', 'js']);
});

// Define the default task so it will launch all other tasks
gulp.task('default', ['app', 'js', 'watch']);

It contains five tasks:

  • app_clean - This task deletes the existing files from the destination folders we defined.
  • app - This task compiles, uglify and create sourcemaps for all TypeScript files and place them to wwwroot/app folder, together with their js.map files.
  • js - This task will copy all JavaScript files from external libraries which is located within the node_modules folder and place them to wwwroot/js folder.
  • watch - This task watches files defined in app and js tasks that are changed.
  • default - Define the default task so it will launch all other tasks.

Angular2 App

Now it’s time to start client code of our app. A skeleton of Angular 2 consists of:

  1. An Angular 2 component file
  2. An Angular 2 Module file
  3. An Angular 2 bootstrap file
  4. An HTML file

1) Component File

Angular 2 is entirely component based. Controllers and $scope are no longer used. They have been replaced by components and directives. The Component is the most basic and fundamental concept in Angular 2. Think of it like a class that controls a specific piece of a web page where we can either display some data to each user and/or respond to them. The Angular 2 App will be almost entirely built upon multiple components serving specific purposes: most of them will be reusable; others will be only used once.

Under project, create "scripts" folder. Under "scripts" folder, create "app" folder.

Now add a new TypeScript file by right-clicking on the "app" folder to Add New Item. Under Client-Side option from the left pane of the dialog, select "TypeScript File" as shown in the figure below.

Image 15

Name the file as "app.component.ts". We just write the very basic Hello World code for now.

import { Component } from "@angular/core";

@Component({
    selector: 'masterchef2',
    template: '

<h1>Master Chef</h1><div>Best recipes from AngularJS 2.</div>'
})
 export class AppComponent { }

The first line of code basically imports the Component function from Angular 2 library, which is @angular/core. The Component function is what we need to define a Component’s metadata for a class. The code block under @Component is the creation of the component. The @Component denotes a TypeScript instruction that tells Angular that this class is an Angular component. Notice the export keyword, which will allow us to import it from other components.

2) Module File

Angular Modules provides a powerful way to organize and bootstrap any Angular2 application: they help developers to consolidate their own set of components, directives and pipes into reusable blocks.

Every Angular2 application must have at least one module, which is conventionally called root module and given the AppModule class name.

Now, create a new TypeScript file and name the file "app.module.ts".

Image 16

///<reference path="../../typings/index.d.ts">
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpModule } from "@angular/http";
import "rxjs/Rx";

import { AppComponent } from "./app.component";

@NgModule({
    // directives, components, and pipes
    declarations: [
        AppComponent
    ],
    // modules
    imports: [
        BrowserModule,
        HttpModule
    ],
    // providers
    providers: [
    ],
    bootstrap: [
        AppComponent
    ]
})
export class AppModule { }  

The first line from the config above, adds a reference to the type definitions to ensure our TypeScript compiler will find it. We then import the basic Angular2 modules that we will need for our app. You can add more Angular 2 module reference in this file when needed. We have also imported the rxjs library definition file(s), which will be useful to compile some Angular2 libraries. We then imported our component "AppComponent". Finally, we have declared our root NgModule: as we can see it consists in an array of named arrays, each one containing a set of Angular2 objects that serves a common purpose: directives, components, pipes, modules and providers. The last one of them contains the component(s) we want to bootstrap, which in our case, the AppComponent file.

3) Bootstrap File

Now that we have our main component, let's add another TypeScript file to create a bootstrap to run the app. Right click on the "app" folder and then select TypeScript File. Name the file as "boot.ts".

Image 17

import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app.module";

platformBrowserDynamic().bootstrapModule(AppModule);  

In boot.ts, we reference the new Angular bundle, also reference the new AppModule that we have created previously.

4) Index.html

Now it’s the time to create index.html to be an entry point for the browser so it can load the client-script files and execute the application, and laying out the DOM structure used by Angular 2 to display it. Right-clicking on the "wwwroot" folder and then selecting Add New Item, and select HTML Page from Client Side. Name the file as "index.html".

Image 18

<html>
<head>
    <title>Master Chef2</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Step 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="js/shim.min.js"></script>
    <script src="js/zone.js"></script>
    <script src="js/Reflect.js"></script>
    <script src="js/system.src.js"></script>

    <!-- Angular2 Native Directives -->
    <script src="/js/moment.js"></script>

    <!-- Step 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>
<!-- Step 3. Display the application -->
<body>
    <!-- Application PlaceHolder -->
    <masterchef2>Please wait...</masterchef2>
</body>
</html>

5) SystemJs File

SystemJs is configuration API. Once SystemJS has loaded, configuration can be set on SystemJS by using the configuration function SystemJS.config. This is a helper function which normalizes configuration and sets configuration properties on the SystemJS instance.

SystemJS.config({ prop: 'value' }) is mostly equivalent to SystemJS.prop = value except that it will extend configuration objects, and certain properties will be normalized to be stored correctly.

The SystemJS config loads our app modules and components.

Now, let’s add the systemjs configuration file. Right-click on the wwwroot folder and then select Add New Item. Under Client-side templates, select "JavaScript File".

Image 19

Copy the below code to systemjs.config.js.

(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': 'js/'
        },
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',

            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

            // angular testing umd bundles
            '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
            '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
            '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
            '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
            '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
            '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
            '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
            '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',

            // other libraries
            'rxjs': 'npm:rxjs',
            'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './boot.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            },
            'angular2-in-memory-web-api': {
                defaultExtension: 'js'
            }
        }
    });
})(this);

6) Run Application

Right click "gulpfile.js" and select "Task Runner Explorer". Click "Refresh" button to load task. Then right click "default" task to select "run".

Image 20

 

After finished the task, all ts files should be compiled to js files under wwwroot folder. App ts files are compiled to wwwroot\app folder, and all other ts packages go to wwwroot\js folder.

 

Image 21

Now just click "IIS Express" to run it.

Image 22

It’s working. That basically tested our Angular 2 component and module get loaded.

Client ViewModel

A view model represents the data that you want to display on your view/page, whether it be used for static text or for input values (like textboxes and dropdown lists) that can be added to the database (or edited). It is something different than your domain model. It is a model for the view. We are going to use ViewModels as our data transfer objects: sending data from client to server and/or vice-versa.

We use TypeScript to define a set of class for us to work with type definitions. In other words, we will not be dealing with raw JSON data and anonymous objects; instead we will be using typed objects: an actual instance of classes.

Create "viewmodels" folder under "scripts/app". Then right click "viewmodels" to add new type script file. It’s named "recipe", which is recipe view model we use to display on the view.

Image 23

export class Recipe {
    constructor(
        public Id: string,
        public Name: string,
        public Comments: string
    ) { }
}

Please note in our view model class, you don’t have to include all properties. What you need include is what you need in your type script.

Client-Side Service

Now, we need to setup a client-service to fetch the required data from the Web API: issuing a request to the API Controllers. Communicate via XMLHttpRequest (XHR), which provides client functionality for transferring data between a client and a server by Angular Http client.

Create a "services" folder under "Scripts/app/". Right click "services" to add a new type script file, and name it as "app.service.ts".

Image 24

import { Injectable } from "@angular/core";
import { Http, Response } from "@angular/http";
import { Recipe } from "../viewmodels/recipe";
import { Observable } from "rxjs/Observable";

@Injectable()
export class AppService {
    constructor(private http: Http)
    { }

    //URL to web api
    private recipeUrl = 'api/recipes/';

    getAllRecipes() {
        return this.http.get(this.recipeUrl)
            .map(response => response.json())
            .catch(this.handleError);
    }

    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || "Server error");
    }
}

It’s a very simple class has only one main method, getAllRecipes, which basically call Recipes web controller we built before.

Change App Component

Change App Component to display a list of recipes which retrieved from the server.

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Recipe } from "./viewmodels/recipe";
import { AppService } from "./services/app.service";

@Component({
    selector: 'masterchef2',
    templateUrl: '../partials/recipes.html'
})

//export class AppComponent { } 

export class AppComponent implements OnInit {

    title: string;
    items: Recipe[];
    errorMessage: string;
    constructor(private appService: AppService) {
        //called first time before the ngOnInit()
        //this.title = "Master Chef Favorite Recipes";
    }

    ngOnInit() {
        //called after the constructor and called  after the first ngOnChanges()
        this.title = "Master Chef Recipes";
        var service = this.appService.getAllRecipes();
        service.subscribe(
            items => this.items = items,
            error => this.errorMessage = <any>error
        );
    }
}

In the top of the file, we have imported the Angular classes that we need: since we’re creating a Component, we need the Component base class by referencing the @angular/core, and we also need to implement the OnInit interface because our component needs to execute something upon its initialization. We have referenced the service that we have created earlier to communicate with our server to get some data. Finally, we have imported the recipe viewmodel for storing the values.

The @component block is where we setup the UI for our Component, including the selector, template and styles. We used templateUrl to make the partial html.

The AppComponent is a class written in TypeScript. This class contains some properties, a constructor which makes use of DI to instantiate the AppService. The ngOnInit() method is where we get the data from the service which fires on initialization of the component.

Add Recipes Template

Under "wwwroot", create a new folder "partials". Right click "partials" to add a new HTML which is named "recipes".

Image 25

 

<h2>{{title}}</h2>
<ul>
    <li *ngFor="let recipe of items">
        <p> {{recipe.name}} - {{recipe.comments}}</p>
        <ul>
            <li *ngFor="let step of recipe.recipeSteps">
                <p> step {{step.stepNo}} : {{step.instructions}}</p>
                <ul>
                    <li *ngFor="let item of step.recipeItems">
                        <p> {{item.name}}  {{item.quantity}} {{item.measurementUnit}}</p>
                    </li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

Please note in recipes.html, bind data from AppComponent class, one is {{title}}, the other is {{items}}. These two properties have been defined in AppComponent class, title is a string, and items is an array of recipe view model.

Change App Module

Because we add new client side service, we need change app.module.ts to load it.

///<reference path="../../typings/index.d.ts">
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
import { FormsModule } from "@angular/forms";
import "rxjs/Rx";

import { AppComponent } from "./app.component";
import { AppService } from "./services/app.service";

@NgModule({
    // directives, components, and pipes
    declarations: [
        AppComponent,
    ],
    // modules
    imports: [
        BrowserModule,
        HttpModule,
        FormsModule,
        RouterModule

    ],
    // providers
    providers: [
        AppService
    ],
    bootstrap: [
        AppComponent
    ]
})
export class AppModule { }  

Launch Master Chef2

Right click "gulpfile.js" and select "Task Runner Explorer". Click "Refresh" button to load task. Then right click "default" task to select "run". After finished the task, click "IIS Express" to run the app.

 

Image 26

Apply Bootstrap Style to Angular 2 Front UI

1) Install Bootstrap

We use bower package manager to grab our client-side bootstrap files. So the first thing to do is right-click on our project to Add a New Item. On the Client-side to select a Bower Configuration File, "bower.json".

Image 27

In bower.json, add "bootstrap" in dependencies section.

{
	"name": "asp.net",
	"private": true,
  "dependencies": {
    "bootstrap": "3.3.7"
  }
}

After restoring finish, Visual Studio install bootstrap under wwwroot\lib folder.

Image 28

2) Add boostrap link style in index.html

Add the below line into index.html.

<link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" media="screen">

3) Apply bootstrap styles in recipes.html

Now we change recipes.html a little bit to apply some bootstrap styles.

<div>
    <h2>{{title}}</h2>

    <div *ngFor="let recipe of items">
        <div class="btn-group">
            <button class="btn-outline-info pull-left"><h5>{{recipe.name}} - {{recipe.comments}}</h5></button>
        </div>

        <div *ngFor="let step of recipe.recipeSteps">
            <div class="row breadcrumb">
                <span>step {{step.stepNo}} : {{step.instructions}}</span>
            </div>
            <ul>
                <li *ngFor="let item of step.recipeItems">
                    <p> {{item.name}}  {{item.quantity}} {{item.measurementUnit}}</p>
                </li>
            </ul>
        </div>
    </div>
</div>

Run our project from IIS Express.

Image 29

Expand/Collapse DOM Element in Angular 2

In previous master chef articles, I have shown you how to do it in Angular 1. Now I show you Angular 2 way.

First add expand function in App.Component.ts to toggle "show" property.

export class AppComponent implements OnInit {

    title: string;
    items: Recipe[];
    errorMessage: string;
    show: boolean;

    constructor(private appService: AppService) {
        //called first time before the ngOnInit()
        //this.title = "Master Chef Favorite Recipes";
    }

    ngOnInit() {
        //called after the constructor and called  after the first ngOnChanges()
        this.title = "Master Chef Recipes";
        var service = this.appService.getAllRecipes();
        service.subscribe(
            items => this.items = items,
            error => this.errorMessage = <any>error
        );
    }

    public Expand() {
        this.show = !this.show;
    }
}

 

In recipes.html, I add click event handler to call expand function.

<div class="btn-group">
            <button class="btn-outline-info pull-left" (click)="Expand()"><h5>{{recipe.name}} - {{recipe.comments}}</h5></button>
</div>

There are two things you need notice.

  1. The parenthesis indicate that we're expecting an event. Instead of ng-click="fn()" and ng-mouseover="fn()" we simply use (click)="fn()" and (mouseover)="fn()".
  2. The brackets indicate a property. Rather than ng-src="ctrl.value" and ng-style="{ 'width': ctrl.value }" we can now easily do [src]="value" and [width]="value".

Then use ng-if to check "show" property to hide or show the child elements.

<div *ngIf="show">
    <div *ngFor="let step of recipe.recipeSteps">
        <div class="row breadcrumb">
            <span>step {{step.stepNo}} : {{step.instructions}}</span>
        </div>
        <ul>
            <li *ngFor="let item of step.recipeItems">
                <p> {{item.name}}  {{item.quantity}} {{item.measurementUnit}}</p>
            </li>
        </ul>
    </div>
</div>

How to use the sample code

Make sure your VS2015 update 3 has installed the latest ASP.NET Web Tools and TypeScript 2. Download the source code. Open MasterChef2WebApp.sln, and rebuild the solution. In Task Runner Explorer, run the default task. After all tasks finish successfully, launch the project by clicking "IIS Express".

Conclusion

In this article, I have shown you how to build a data-driven Angular 2 app from scratch within the context of ASP.NET Core. We have also learned how to create and communicate with Web API in our Angular2 app. In my next Master Chef article, I’ll show you how to build Single Page CRUD Application with Angular 2.

License

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