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

CRUD in ASP.NETCore MVC with Angular 2 and Web API

4.79/5 (35 votes)
16 Jan 2017CPOL6 min read 172.3K   6.2K  
A good example of how to build a CRUD web application using ASP.NET CORE with Angular2 and WebApi

Introduction

Through this article, you will learn about implementation of web CRUD application. CRUD application means an application that can Create, Read, Update and Delete records on a data source (such as a Database, XML File, etc.).

The main goal of this article is to teach you the following points :

  • create an ASP.NET Core MVC application.
  • install required packages (Angular2, Typings, etc.) using npm.
  • reverse engineering of an existing database installed on SQL Server (using Entity Framework model first approach).
  • create a RestFul Server using Web Api.
  • create components, templates, services, and classes on Angular 2.
  • compile Angular2 project using webpack.
  • run the ASP.NETCORE MVC web application.

In this article, I relied on the following links to build my application:

Background

To better understand this demo, it's preferable that you have a good knowledge’s of:

  • Programming in C#, JavaScript and HTML.
  • MVC architecture.
  • SQL language.
  • Data Binding.
  • Entity Framework.
  • Visual Studio Code.

Prerequisites

Before we start the implementation of the web application, you need to install :

Using the Code

Building the Web Application

In this section, i will proceed step by step to explain how you can easily build a CRUD web application :

A) Setup ASP.NETCore MVC Project

Open your CMD console (I recommend you to execute it as Administrator) and run the following commands :

  • mkdir dotnetcoreapp.
  • cd dotnetcoreapp.
  • dotnet new -t web to create web application.
  • dotnet restore to load dependencies.
  • dotnet run to start application.

the result will be like the picture below  :

Image 1

Use your browser to navigate to the given url (http://localhost:5000), you should get the same display as below :

Image 2

B) Create and Configure Angular2 Project

Based on the official Angular2 documentation, you neet to create the same configuration files:

  • package.json
  • tsconfig.json
  • typings.json

Next, you should install typescript, typings and webpack using npm:

  • Npm install –g typescript
  • Npm install -g typings
  • Npm install -g webpack

Finally, you need to introduce some change in the Startup.cs file to support Angular2 Single Page Application. You have to change the default ASP.NETCore MVC routing to point on the index page of the Angular2 project (wwwroot/index.html), to do that, you need to introduce some change into the Configure method specially on routing code :

C#
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
     
           if (env.IsDevelopment())
            {
                //read error details
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            } 
//point on the index page of the Angular2 project
           app.Use(async (context, next) =>
            {
                await next();

                if (context.Response.StatusCode == 404
                    && !Path.HasExtension(context.Request.Path.Value))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }
            }); 
            app.UseStaticFiles();
            app.UseMvc();
        }

C) Setup DataBase

1) Create Database

The following steps will help you to create an non-empty database (in my case, i have used SQL Server 2014 to locally host my database) :

  • Create a new database named DataBaseDotnetCore to your sql server.
  • Execute the following SQL Scripts to create a table named Product and insert some set of data.
    SQL
    CREATE TABLE [dbo].[Product](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [_name] [varchar](250) NULL,
    [_description] [varchar](250) NULL,
    Primary key(id),
    );
    
    insert into Product values ('Juice','Juice description'), _
    ('Orange','Orange description')
    
2) Using Entity Framework model first approach

In this section, you will do a reverse engineering to create an Entity Framework model from your existing database. To do that, you need to follow the steps below:

  • Navigate to the root folder of the application (dotnetcoreapp folder).
  • Import required dependencies and tools: you have to modify the project.json by :
    1. Adding the following dependencies :
      C#
      "Microsoft.EntityFrameworkCore":"1.0.0",
      "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
      "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0",
      "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
      
    2. Adding tools :
      C#
      "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
    3. Saving the changes and executing the following command line :
dotnet -restore
  • Write the following command-line to start the process of reverse engineering:
    dotnet ef dbcontext scaffold "Server=[serverName];Database=[databaseName];
    Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models

    For example:

    dotnet ef dbcontext scaffold "Server=LFRUL-013;Database=DataBaseDotnetCore;
    Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models
  • Finally, modify the ConfigureServices method on Startup.cs file to create a connection to your database:
    C#
    public void ConfigureServices(IServiceCollection services)
    {            
        var connection = @"Server=LFRUL-013;Database=DataBaseDotnetCore;Trusted_Connection=True;";
        services.AddDbContext<DataBaseDotnetCoreContext>(options => options.UseSqlServer(connection));
        services.AddMvc();
    }

D) Setup restful web Api

In this step, you will create a controller named ProductsController.cs that implements some HTTP verbs:

  • Get: retrieve a list of product from database.
  • Post: create a new Product.
  • Put: update a specific Product.
  • Delete: delete a specific Product by passed id .
C#
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using angularapp.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace WebApplication
{
    [Route("api/[controller]")]
    [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true, Duration = -1)] 
    public class ProductsController : Controller
    {
        private DataBaseDotnetCoreContext _context; 

           public ProductsController(DataBaseDotnetCoreContext context)
        {
            _context = context;
        }

        [HttpGet]
        public IEnumerable<dynamic> Get()
        {
                return _context.Product.ToList();
        }

        [HttpPost]
        public string Post([FromBody] Product product)
        {
            Response.StatusCode = 200;
    
            try{
                    angularapp.Models.Product newProduct = new Product();
                    newProduct.Name = product.Name; 
                    newProduct.Description = product.Description; 
                     _context.Product.Add(newProduct);
                     _context.SaveChanges();
              
            }catch(Exception e){
                Response.StatusCode = 400;
                return e.ToString();
            }
            return "OK";
        }
        [HttpPut]
        public string Put([FromBody] Product product)
        {
            Response.StatusCode = 200;
    
            try{
                   
                    product.Name = product.Name; 
                    product.Description = product.Description; 
                     _context.Product.Attach(product);
                      _context.Entry(product).State = EntityState.Modified;
                     _context.SaveChanges();
              
            }catch(Exception e){
                Response.StatusCode = 400;
                return e.ToString();
            }
            return "OK";
        }

        [HttpDelete]
        public String Delete(int id)
        {
            Response.StatusCode = 200;
              
            try{
                    angularapp.Models.Product newProduct = new Product();
                    newProduct.Id = id; 
                    _context.Product.Remove(newProduct);
                    _context.SaveChanges();
        
            }catch(Exception e){
                 return e.ToString();
            }
            return "OK";
        }
    }
}

When you run the project at this level, the implemented web api will be exposed on the following url : http://localhost:5000/api/products

E) Setup Angular2 Project (frontend part)

In the wwwroot/app folder, create the following files:

a) product.service.ts

It's the angular service class that implements required methods and interfaces that ensures communication with developed web api. it's composed by the following functions :

  • AnnounceChange: sends a notification to observers to call refresh data into a list of view,
  • LoadData: loads data from database, thanks to the call of an existing web service [/api/products],
  • Add: invokes an external web service [/api/products] that add a new Product object into database,
  • Update: updates some content of an existing product into database by calling an existing web service [/api/products],
  • Delete: deletes a specific product from database by calling an existing web service [/api/products],

 

 

import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import { Observable, Subject } from 'rxjs/Rx';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class ProductService {
    constructor(private _http: Http) { }
    private RegenerateData = new Subject<number>();
    // Observable string streams
    RegenerateData$ = this.RegenerateData.asObservable();
   
    AnnounceChange(mission: number) {
            
          this.RegenerateData.next(mission);
    }
    
    LoadData(): Promise<iproduct[]> {
        return this._http.get('/api/products')
            .toPromise()
            .then(response => this.extractArray(response))
            .catch(this.handleErrorPromise);
    }    

    Add(model) {
        let headers = new Headers({ 'Content-Type': 
        'application/json; charset=utf-8' });
        let options = new RequestOptions({ headers: headers });
        delete model["id"];
        let body = JSON.stringify(model);
        return this._http.post('/api/products/', body, 
               options).toPromise().catch(this.handleErrorPromise);
    }
    Update(model) {
        let headers = new Headers({ 'Content-Type': 
        'application/json; charset=utf-8' });
        let options = new RequestOptions({ headers: headers });
        let body = JSON.stringify(model);
        return this._http.put('/api/products/', body, 
               options).toPromise().catch(this.handleErrorPromise);
    }
    Delete(id: number) {  
        return this._http.delete('/api/products/?id=' + 
        id).toPromise().catch(this.handleErrorPromise);
    }    

    protected extractArray(res: Response, showprogress: boolean = true) {
        let data = res.json();
        
        return data || [];
    }

    protected handleErrorPromise(error: any): Promise<void> {
        try {
            error = JSON.parse(error._body);
        } catch (e) {
        }

        let errMsg = error.errorMessage
            ? error.errorMessage
            : error.message
                ? error.message
                : error._body
                    ? error._body
                    : error.status
                        ? `${error.status} - ${error.statusText}`
                        : 'unknown server error';

        console.error(errMsg);
        return Promise.reject(errMsg);
    }
}
b) IProduct.ts :

constitutes an interface for Product entity .

export interface IProduct { id : number , name : string , description : string }
c) app.componentHW.ts

This is the main component. It contains the template and implementation of the application:

  • refresh: refreshes the existing list of product view by receiving data from an external service by calling "loadData method" of _service variable ,

  • onUpdate: updates an existing product on the database by calling "Update method" of "_service variable",
  • onDelete: deletes an existing product identified by its "unique key" by calling "Delete method" of "_service variable" .
import { Component, OnInit } from '@angular/core';
import { ProductService, IProduct } from './product.service';
import { ProductForm } from './productForm';
import { Subscription }   from 'rxjs/Subscription';
@Component({
  selector: 'myhw',
  template: `
      <div class='row'>
        <pform></pform>
      </div>
      <div class='row'>
       <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class='panel-heading'>Products List</div>
        <div class='panel-body'>
          <table class='table table-condensed'>
            <thead>
              <th>Id</th>
              <th>Name</th>
              <th>Description</th>
                <th></th>
            </thead>
             <tbody>
              <tr *ngFor="let person of persons"  >
                  <td> {{person.id}}  </td>
                  <td> <input type="text"  
                  [(ngModel)]="person.name" 
                                            name="pname" 
                                            class="form-control" /> </td>
                  <td> <input type="text"  
                  [(ngModel)]="person.description" 
                                            name="pdescription"  
                                            class="form-control" /> </td>
                  <td> <input type="button" 
                  value="update" class="btn btn-default" 
                         (click)="onUpdate(person)"/> 
                         <input type="button" value="remove" 
                          class="btn btn-danger"  
                          (click)="onDelete(person.id)"/></td>
                </tr> 
            <tbody>
           </table>
           </div>
           </div>
        </div>
        `     
      })
export class HwComponent extends OnInit {
    subscription: Subscription;
    
     refresh(){
         this._service.loadData().then(data => {
            this.persons = data;
        })
    }
    constructor(private _service: ProductService) {
        super();
          this.subscription = _service.RegenerateData$.subscribe(
          mission => {
              console.log("Good !! ", mission);
               this.refresh();
           });
    }

    ngOnInit() {
        this.Refresh();
    }
    onUpdate(elem){
        console.log(elem); 
        this._service.Update(elem).then(data => {
         })
    }
    onDelete(elem : number){
        console.log("Delete Form ! ");
        console.log(elem); 
        this._service.Delete(elem).then(data => {
              this.Refresh();
        })
    }
    persons: IProduct[] = [];

     ngOnDestroy() {
    // prevent memory leak when component destroyed
       this.subscription.unsubscribe();
     }
}
d) Product.ts

This class contains details for product item.

export class Product {
      constructor(
        public id : number,      
        public name : string,      
        public description : string
      ){

      }
}
e) productForm.component.html

This is the HTML template used for our Product Form.

HTML
<div>
   <h3>Product Form</h3>
   <form>
     <div class="form-group">
       <label for="name">Name *</label>
       <input type="text" class="form-control"
       [(ngModel)]="model.name" name="name" required>
     </div>
     <div class="form-group">
       <label for="description">Description *</label>
       <input type="text" class="form-control"
       [(ngModel)]="model.description"  name="email">
     </div>
     <button type="button"  (click)="onSubmit()"
     class="btn btn-primary">Add</button>
   </form>
 </div>
f) productForm.ts

Constitutes the code behind for our productForm template, in which you will did the implementation of:

  • onSumbit method: This event is used to invoke the Add method of ProductService.
  • model attribute: used to bind the product form fields with model.
C#
import { Component, OnInit } from '@angular/core';
import { Product } from './Product';
import { ProductService, IProduct } from './product.service';

@Component({
    moduleId: __filename,
    selector: 'pform',
    templateUrl: './app/productForm.component.html'    
 })

export class ProductForm {
    constructor(private _service: ProductService) {
     
    }
    model = new Product(0,'','');
    submitted = false;
    onSubmit() { 
        console.log("Sumbitted Form ! ");
        this.submitted = true; 
        this._service.Add(this.model).then(data => {
           this._service.AnnounceChange(1212);
        })
    }
    
  // TODO: Remove this when we're done
    get diagnostic() { return JSON.stringify(this.model); }
}
g) app.module.ts

This file will be used to :

  • import needed Angular2 modules via the word key : "imports".
  • declare created components via the word key : "declarations".
  • declare required services via the word key : "providers".
  • specify the root component inside "bootstrap array", this component must be included inside index.html file.
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HwComponent }   from './app.componentHW';
import { ProductService } from './product.service';
import {ProductForm } from './productForm';
 
@NgModule({
  imports:      [ BrowserModule,
                  FormsModule,
                  HttpModule],
  declarations: [ HwComponent, ProductForm],
  providers: [
        ProductService
  ],
  bootstrap:    [  HwComponent]
})
export class AppModule { }

Navigate to wwwroot folder in which you will start the configuration of the frontend project:

h) systemjs.config.js

This file is used to load modules compiled using the TypeScript compiler.

JavaScript
/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': '../node_modules/'
        },
        // 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',
            // other libraries
            'rxjs': 'npm:rxjs',
        },
        meta: {
            './app/main.js': {
                format: 'global'
            }
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './main.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            }
        }
    });
})(this); 
i) index.html

This HTML file is the entry point of the application, in which we will include all required JS, CSS files to render our components.

HTML
<html>
    <head>
        <title>Angular 2 QuickStart</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="css/site.css">
        <link rel="stylesheet" href="css/bootstrap.min.css">
        <!-- 1. Load libraries -->
        <script src="js/core.js"></script>
        <script src="js/zone.js"></script>
        <script src="js/reflect.js"></script>
        <script src="js/system.js"></script>
        <!-- 2. Configure SystemJS -->
        <script src="systemjs.config.js"></script>
        <script>
          System.import('app').catch(function(err){ console.error(err); });
        </script>
    </head>
    <!-- 3. Display the application -->
    <body>

        <div class="container">    
          <myhw>Loading ...</myhw>
        <div>
    </body>
</html>

Finally, you should configure webpack to:

  • export required node_modules to wwwroot/js folder
  • transpile  main.ts to JavaScript file named main.js

To do that, navigate to the root folder (dotnetcoreapp/), and create webpack.config.js:

JavaScript
module.exports = [
 {
   entry: {
     core: './node_modules/core-js/client/shim.min.js',
     zone: './node_modules/zone.js/dist/zone.js',
     reflect: './node_modules/reflect-metadata/Reflect.js',
     system: './node_modules/systemjs/dist/system.src.js'
   },
   output: {
     filename: './wwwroot/js/[name].js'
   },
   target: 'web',
   node: {
     fs: "empty"
   }
 },
 {
   entry: {
     app: './wwwroot/app/main.ts'
   },
   output: {
     filename: './wwwroot/app/main.js'
   },
   devtool: 'source-map',
   resolve: {
     extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
   },
   module: {
     loaders: [
       { test: /\.ts$/, loader: 'ts-loader' }
     ]
   }
 }];

F) Running the web application

To run the demo, you should write the following command-lines using CMD, but first be sure that you are in the root directory of your .net core web application:

  • webpack: to transpile TS files to JavaScript files.
  • dotnet run: to compile the project and run the server.

If the compilation is successful, you can open the given url on your browser to see the running web application :

 

Image 3

References

Points of Interest

I hope that you appreciated this post. Try to download the source code and do not hesitate to leave your questions and comments.

History

  • v1 30/10/2016: Initial version

License

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