Building a simple CRUD app with ASP.NET Core and Angular 2 using Visual Studio 2017

In this tutorial we’re going to build a simple CRUD app with ASP.NET Core and Angular 2 using Visual Studio 2017. This application will manage a collection of books.

You can find the final app here. It would look like:

books list

book item

Prerequisites

  • Visual Studio 2017 ( make sure ASP.NET and web development workload is installed)

web module

  • Node.js ( version 6 or later)

Creating the project

Microsoft.AspNetCore.SpaTemplates is a package that provides project templates for Single-Page Applications (SPAs) frameworks with ASP.NET Core. So, we need to install this package to get a project template for Angular 2.

Open the command Prompt and run the following command:

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

Once the installation is complete, you can see the SPAs templates:

templts

Create a directory for the project:

mkdir BookStore

Change to the directory we just created:

cd BookStore

Generate an Angular 2 project template:

dotnet new angular

Install all the dependencies:

dotnet restore
npm install

Open the project with Visual Studio 2017:

start BookStore.csproj

stucture

Run the app:

project launch

Preparing the project

This project is a starter seed project to get up and running with ASP.NET Core and Angular 2. It contains items that we don’t need for application. So, we’ll make a few changes to the project.

Delete the following folders:

  • counter
  • fetchdata

Delete SampleDataController.cs

Update the following files:

app.module.ts to:

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal';
import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';

 @NgModule({
    bootstrap: [ AppComponent ],
    declarations: [
        AppComponent,
        NavMenuComponent,
        HomeComponent
    ],
    imports: [
        UniversalModule, 
        RouterModule.forRoot([
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: 'home', component: HomeComponent },
            { path: '**', redirectTo: 'home' }
        ])
    ]
})
export class AppModule {
}

home.component.html to:

<h1>Welcome to BookStore Application</h1>

navmenu.component.html to:

<div>
<div>
<div>
            
                <span>Toggle navigation</span>
                <span></span>
                <span></span>
                <span></span>
            
            <a>BookStore</a></div>
<div></div>
<div>
            <ul></ul>
</div>
</div>
</div>

Run the app:

new prepar

Now we’re ready to start building our application. First, we’ll create a REST API and then we’ll build the client side.

Setting up the server

We‘ll store the data in SQL server database using Entity Framework Core (EF Core) code-first approach.

Let’s create a folder named Models. Inside this folder add a Book class:

Book.cs

namespace BookStore.Models
{
    public class Book
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public decimal Price { get; set; }
    }
}

We need to install SQL Server Provider for EF Core. Open the Package Manager Console (PMC) and run:

Install-Package Microsoft.EntityFrameworkCore.SqlServer

Create a BookStoreContext class inside the Models folder:

BookStoreContext.cs

using Microsoft.EntityFrameworkCore;

namespace BookStore.Models
{
    public class BookStoreContext : DbContext
    {
        public DbSet Books { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=BookStoreDB;Trusted_Connection=True;MultipleActiveResultSets=true");
        }
    }
}

To maintain the database you need to install EF Core Tools. Open PMC and run:

Install-Package Microsoft.EntityFrameworkCore.Tools

To generate the database, run the following commands:

Add-Migration InitialCreate
update-database

To see the generated database, add a data connection in Server Explorer:

db

Now, it’s time to create a REST API that performs CRUD operations.

Http Verb Route Designation
Get /api/Books Get all books
Get /api/Books/id Get book by id
Post /api/Books Add a new book
Put /api/Books/id Update an existing book
Delete /api/Books/id Delete a book

Create a BooksController class inside the Controllers folder:

BooksController.cs

using System;
using System.Net;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using BookStore.Models;

namespace BookStore.Controllers
{
    [Route("api/[controller]")]
    public class BooksController : Controller
    {
        private readonly BookStoreContext _context = new BookStoreContext();

        [HttpGet]
        public IActionResult Get()
        {
            try
            {
                var books = _context.Books.ToList();
                if (books.Count > 0)
                    return Ok(books);
                else
                    return NotFound();

            }
            catch (Exception)
            {
                return StatusCode((int)HttpStatusCode.InternalServerError);
            }
        }

        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            try
            {
                var book = _context.Books.FirstOrDefault(e => e.BookId == id);
                if (book != null)
                    return Ok(book);
                else
                    return NotFound();

            }
            catch (Exception)
            {
                return StatusCode((int)HttpStatusCode.InternalServerError);
            }
        }

        [HttpPost]
        public IActionResult Post([FromBody]Book book)
        {
            if (ModelState.IsValid == false) return BadRequest(ModelState);
            try
            {
                _context.Books.Add(book);
                _context.SaveChanges();
                return Created("created", book);
            }
            catch (Exception)
            {
                return StatusCode((int)HttpStatusCode.InternalServerError);
            }
        }

        [HttpPut("{id}")]
        public IActionResult Put(int id, [FromBody]Book book)
        {
            if (id != book.BookId) return NotFound();
            if (ModelState.IsValid == false) return BadRequest(ModelState);
            try
            {
                _context.Update(book);
                _context.SaveChanges();
                return Ok(book);

            }
            catch (Exception)
            {
                return StatusCode((int)HttpStatusCode.InternalServerError);
            }
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            try
            {
                var book = _context.Books.FirstOrDefault(e => e.BookId == id);
                if (book == null)
                    return NotFound();
                else
                {
                    _context.Books.Remove(book);
                    _context.SaveChanges();
                    return Ok();
                }

            }
            catch (Exception)
            {
                return StatusCode((int)HttpStatusCode.InternalServerError);
            }
        }
    }
}

Setting up the client

Now we’re going to create the client side that will consume the REST API we just created. It’s placed in ClientApp folder.

Create a folder called models and inside it add a book class:

book.ts

export class Book {
    bookId: number;
    title: string;
    author: string;
    price: number;
}

Let’s a create a service to communicate with the back-end. Create a folder named services and inside it add a BookService class:

book.service.ts

import { Injectable } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Book } from "../models/book";

@Injectable()
export class BookService {

    private _bookStoreApi = '/api/Books';

    constructor(private http: Http) { }

    getAll(): Promise {
        return this.http.get(this._bookStoreApi)
            .toPromise()
            .then(response => response.json())
            .catch(error => Promise.reject(error.message || error));
    }

    get(id: Number) {
        return this.http.get(this._bookStoreApi + '/' + id)
            .toPromise()
            .then(response => response.json())
            .catch(error => Promise.reject(error.message || error));
    }

    save(book: Book): Promise {
        if (book.bookId)
            return this.put(book);
        return this.post(book);
    }

    delete(book: Book) {
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');

        let url = `${this._bookStoreApi}/${book.bookId}`;

        return this.http
            .delete(url, headers)
            .toPromise()
            .catch(error => Promise.reject(error.message || error));
    }

    private post(book: Book): Promise {
        return this.http
            .post(this._bookStoreApi, JSON.stringify(book), {
                headers: new Headers({
                    'Content-Type': 'application/json'
                })
            })
            .toPromise()
            .then(response => response.json().data)
            .catch(error => Promise.reject(error.message || error));
    }

    private put(book: Book) {
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');

        let url = `${this._bookStoreApi}/${book.bookId}`;

        return this.http
            .put(url, JSON.stringify(book), { headers: headers })
            .toPromise()
            .then(() => book)
            .catch(error => Promise.reject(error.message || error));
    }
}

Update app.module.ts to:

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal';
import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { BookService } from "./services/book.service";

 @NgModule({
    bootstrap: [ AppComponent ],
    declarations: [
        AppComponent,
        NavMenuComponent,
        HomeComponent
    ],
    imports: [
        UniversalModule, 
        RouterModule.forRoot([
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: 'home', component: HomeComponent },
            { path: '**', redirectTo: 'home' }
        ])
    ],
    providers: [BookService]
})
export class AppModule {
}

Now, we’ll add two components in our application:

  • books component: displays a collection of books and allows us to view a book’s details, remove a book and navigate to book form.
  • book-form component: allows adding a new book or updating a existing book.

Create a new folder inside components folder named books and add the following files:

books.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { BookService } from "../../services/book.service";
import { Book } from "../../models/book";

@Component({
    selector: 'books',
    templateUrl: './books.component.html'
})

export class BooksComponent implements OnInit {

    books: Book[];
    book: Book;
    error: any;

    constructor(private router: Router,
        private bookService: BookService) {
    }

    ngOnInit() {
        this.getAll();
    }

    getAll() {
        this.bookService.getAll()
            .then(books => this.books = books)
            .catch(error => this.error = error);
    }

    viewDetail(book: Book) {
        this.book = book;
    }

    gotoForm(id) {
        this.router.navigate(['/book', id]);
    }

    delete(book: Book, event: any) {
        event.stopPropagation();
        this.bookService
            .delete(book)
            .then(response => {
                this.books = this.books.filter(b => b !== book);
                if (this.book === book) 
                    this.book = null;
            })
            .catch(error => this.error = error);
    }
}

books.component.html

<span>{{error}}</span>
<div>
<h2>Books list</h2>
<div>
<div>
            <a>Create New</a>
            <table>
<thead>
<tr>
<th>Title</th>
<th>Price</th>
<th>Action</th>
</tr>
</thead>
<tbody>
                    <tr>
<td>{{book.title}}</td>
<td>{{book.price}}</td>
<td>
                            Edit
                            Details
                            Delete</td>
</tr>
</tbody>
</table>
</div>
<div>
            <h4>Book details</h4>
<div>
<table>
<tr>
                        <td>Title:</td>
<td>{{book.title}}</td>
</tr>
<tr>
                        <td>Author:</td>
<td>{{book.author}}</td>
</tr>
<tr>
                        <td>Price:</td>
<td>{{book.price}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>

Inside components folder create a new folder called book-form and the following  files:

book-form.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Book } from "../../models/book";
import { BookService } from "../../services/book.service";

@Component({
    selector: 'book-form',
    templateUrl: './book-form.component.html'
})

export class BookFormComponent implements OnInit {
    @Input() book: Book;
    updateMode = false;
    error: any;

    constructor(
        private bookService: BookService,
        private route: ActivatedRoute) {
    }

    ngOnInit() {
        this.route.params.forEach((params: Params) => {
            let id = params['id']; 
            if (id === '') {
                this.updateMode = true;
                this.book = new Book();
            } else {
                this.updateMode = false;
                this.bookService.get(id)
                    .then(book => this.book = book);
            }
        });
    }

    save() {
        this.bookService
            .save(this.book)
            .then(book => {
                this.book = book;
                this.goBack();
            })
            .catch(error => this.error = error); 
    }

    goBack() {
        window.history.back();
    }
}

book-form.component.html

<span>{{error}}</span>
<div>
<div>
        Title: 
<div>
            </div>
</div>
<div>
        Author: 
<div>
            </div>
</div>
<div>
        Price: 
<div>
            </div>
</div>
<div>
<div>
            Back
            Save</div>
</div>
</div>

Let’s define a routes associated to these components:

Update app.module.ts to:

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal';
import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { BooksComponent } from './components/books/books.component';
import { BookFormComponent } from './components/book-form/book-form.component';
import { BookService } from "./services/book.service";

@NgModule({
    bootstrap: [ AppComponent ],
    declarations: [
        AppComponent,
        NavMenuComponent,
        HomeComponent,
        BooksComponent,
        BookFormComponent
    ],
    imports: [
        UniversalModule,
        FormsModule,
        RouterModule.forRoot([
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: 'home', component: HomeComponent },
            { path: 'books', component: BooksComponent },
            { path: 'book/:id', component: BookFormComponent },
            { path: '**', redirectTo: 'home' }
        ])
    ],
    providers: [BookService]
})
export class AppModule {
}

Let’s add an item to the navigation menu that allows navigating to books components:

Update navmenu.component.html to:

<div>
<div>
<div>
            
                <span>Toggle navigation</span>
                <span></span>
                <span></span>
                <span></span>
            
            <a>BookStore</a></div>
<div></div>
<div>
            <ul>
                	<li>
                    <a>
                        <span></span> Books
                    </a></li>
</ul>
</div>
</div>
</div>

Thanks for reading!

 

 

 

 

 

 

 

 

 

 

 

Leave a comment