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 :
Use your browser to navigate to the given url (http://localhost:5000), you should get the same display as below :
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 :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
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.
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 :
- Adding the following dependencies :
"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",
- Adding tools :
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
- 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:
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 .
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:
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.
<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.
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);
})
}
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.
(function (global) {
System.config({
paths: {
'npm:': '../node_modules/'
},
map: {
app: 'app',
'@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',
'rxjs': 'npm:rxjs',
},
meta: {
'./app/main.js': {
format: 'global'
}
},
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>
<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">
<!--
<script src="js/core.js"></script>
<script src="js/zone.js"></script>
<script src="js/reflect.js"></script>
<script src="js/system.js"></script>
<!--
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<!--
<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:
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 :
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