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

DotnetCore and Angular2: Create a Users Management Interface

4.88/5 (13 votes)
24 Jan 2017CPOL5 min read 38.1K   586  
You will learn how you can build a management user interface using DotnetCore, Angular2, typescript and web API.

Introduction

The main idea through this demo is to initiate the development of an Angular2 application in .NET core web application.

In this post, I try to explain how to:

  • Create a web API in .NET Core
  • Reverse engineer using Entity Framework Database approach
  • Create services in Angular2 project
  • Create component in Angular2 project
  • Create separate HTML template in Angular2 project
  • Validate form in Angular2 project using ReactiveFormsModule module

After running this project, you will get the following render:

Image 1

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

Background

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

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

Prerequisites

To run this example, you need to install:

Before you start reading the rest of this article, I recommend you read my last article: CRUD in ASP.NETCore MVC with Angular 2 and Web API, it contains more details about configuration, installation and start-up of Angular2 & .NET CORE web application.

Using the Code

In this section, I will proceed step by step to explain how to build such a web application:

A) Setup DataBase

1) Create Database

I used SQL Server 2014 to locally host my database.

Our database will contain the following entities:

User Entity: will contain a list of users, composed by:

  • userId: unique identifier for user is an auto increment, type: int
  • userName: is the first name, type: varchar
  • userBirthDate: is the birth date, type: datetime.
  • userBirthLocation: is a reference key to the country entity, type: varchar
  • userPicture: it will store the absolute URL of image in the server, type: varchar

Country Entity: will store different countries name. It is used by userBirthLocation attribute.

The following steps will help you to prepare your database:

  • Create a database named ‘cp_UsersDataBase
  • Execute the following SQL Script:
SQL
create table Country(
id varchar(250) primary key
);
create table cp_users
(
userId int identity(1,1),
userName varchar(250),
userBirthDate DateTime, 
userBirthLocation varchar(250), 
userPicture varchar(max),
Primary Key(userId),
Foreign Key(userBirthLocation) references Country(id),
);
 
insert into Country values ('france'),('italy'),('russia'),('germany'),('spain')

2) Using Entity Framework Database Approach

In this section, you will do a reverse engineering to import model of your existing database. To do that, you should follow the steps given below:

  • Navigate to the root folder of application
  • Import required dependencies and tools: to do that, you should configure the project.json by:

*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",

*Adding tools:

C#
"Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final",
"Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final",

*Using cmd.exe, navigate to project root and:

dotnet -restore

*Start the reverse engineering, to load the model for your existing database:

Scaffold-DbContext "Data Source=LFRUL-013;Initial Catalog=cp_UsersDataBase;
Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models/DataBase

B) Setup Restful WebApi

Create a web API controller inside controller folder named ‘ServicesController’ that will contain the following actions:

  • api/Services/GetCountries: returns list of available countries from database
  • api/Services/GetUsers: returnd list of user from database, this service uses a pagination system, it will return only a set of elements from remaining data based on two pieces of information: ‘Index of last loaded element’ and 'Page Size'
  • api/Services/AddUser: creates a new user into database
  • api/Services/DeleteUser: removes an existing user from database using unique identifier
JavaScript
[Route("api/Services")]
    public class ServicesController : Controller
    {
        private readonly IHostingEnvironment _hostingEnvironment;

        public ServicesController(IHostingEnvironment hostingEnvironment)
        {
            _hostingEnvironment = hostingEnvironment;
        }

        //api/Services/GetCountries
        [Route("GetCountries")]
        [HttpGet]
        public IEnumerable<string> GetCountries()
        {
            cp_UsersDataBaseContext databaseContext = new cp_UsersDataBaseContext();
            return databaseContext.Country.Select(p => p.Id.ToString()).ToList();
        }

        //api/Services/GetUsers
        [Route("GetUsers")]
        [HttpGet]
        public IEnumerable<UserMessage> GetUsers(int currentLength, int pageSize)
        {
 
            cp_UsersDataBaseContext databaseContext = new cp_UsersDataBaseContext();

            List<UserMessage> users = databaseContext.CpUsers.Select(p => new UserMessage() {
                userId = p.UserId,
                userBirthDate = p.UserBirthDate,
                userPictureSrc = Tools.convertImageToBase64(p.UserPicture),
                userBirthLocation = p.UserBirthLocation,
                userName = p.UserName
            }).OrderBy(p => p.userId).Skip(currentLength).Take(pageSize).ToList();
            return users;
        }
        //api/Services/AddUser
        [Route("AddUser")]
        [HttpPost]
        public HttpResponseMessage AddUser()
        {
            try
            {
                string pictureServerPath = "";
                if (Request.Form.Files.Count > 0)
                {
                    string contentRootPath = _hostingEnvironment.ContentRootPath;
                    var formFile = Request.Form.Files[0];
                    var pictureRelativePath = Path.Combine("Content", "pictures", 
                           Path.GetRandomFileName() + Path.GetExtension(formFile.FileName));
                    pictureServerPath = Path.Combine(contentRootPath, pictureRelativePath);

                    FileStream fileStream = new FileStream(pictureServerPath, FileMode.Create);
                    formFile.CopyTo(fileStream);
                    fileStream.Flush();
                    GC.Collect();
                }

                cp_UsersDataBaseContext databaseContext = new cp_UsersDataBaseContext();
                CpUsers cpUser = new CpUsers()
                {

                    UserName = Request.Form["userName"],
                    UserBirthLocation = Request.Form["userBirthLocation"],
                    UserBirthDate = DateTime.ParseExact(Request.Form["userBirthDate"], 
                                    "dd/MM/yyyy", CultureInfo.InvariantCulture),
                    UserPicture = pictureServerPath,
                };

                databaseContext.CpUsers.Add(cpUser);
                databaseContext.SaveChanges();
            }catch(Exception ex)
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);
            }
      
            return new HttpResponseMessage(System.Net.HttpStatusCode.OK);

        }
        //api/Services/DeleteUser
        [Route("DeleteUser")]
        [HttpDelete]
        public HttpResponseMessage DeleteUser(int id)
        {
            try{ 
                cp_UsersDataBaseContext databaseContext = new cp_UsersDataBaseContext();
                CpUsers cpUser = new CpUsers() { UserId = id};
                databaseContext.CpUsers.Attach(cpUser);
                databaseContext.Entry(cpUser).State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
                databaseContext.SaveChanges();
            }catch(Exception ex)
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);
            }
            return new HttpResponseMessage(System.Net.HttpStatusCode.OK);
        }
    }

After running the web API application, you should get the below result when you navigate to the following URL: 'api/Services/GetCountries':

Image 2

C) Implement Frontend Part

At the end, what it looks like is the treeview of the front-end project:

Image 3

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

1) DatePipe.ts

Used to create a custom filter that transforms a date to specific string format (dd/mm/yyyy).

JavaScript
 import { Pipe, PipeTransform } from '@angular/core'
import * as moment from 'moment';
@Pipe({
    name: 'formatDate'
})
export class DatePipe implements PipeTransform {
    transform(date: any, args?: any): any {
        let d = new Date(date)
        return moment(d).format('DD/MM/YYYY')
    }
}

2) User.ts

Used to deserialize json data into a user object.

JavaScript
import { IUser } from '../model/IUser';
export class User implements IUser {
    public userId: number;
    public userName: string;
    public userBirthDate: Date;
    public userBirthLocation: string;
    public userPictureFile: any;
    public userPictureSrc: string;

    constructor( )
    {
    }
}

3) index.service.ts

This is our service class that implements needed methods such as:

  • loadCountries method: Loads list of countries from an external database, by calling an existing web service [/api/Services/GetCountries]
  • loadUsers method: Loads list of user from an external database, by calling an existing web service [/api/Services/GetUsers]
  • addUser method: Invokes an external web service [/api/Services/AddUser] that adds a new user into database
  • deleteUser method: Deletes a specific product from database by calling an existing web service [/api/Services/DeleteUser]
JavaScript
import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { User } from '../model/User';

@Injectable()
export class IndexService {
    constructor(private _http: Http) { }

    loadCountries(): Promise<string[]> {
        return this._http.get('/api/Services/GetCountries')
            .toPromise()
            .then(response => this.extractArray(response))
            .catch(this.handleErrorPromise);
    }      

    loadUsers(skipSize: number, pageSize: number): Promise<User[]> {
        return this._http.get
          ('/api/Services/GetUsers?currentLength=' + skipSize +'&pageSize=' + pageSize)
            .toPromise()
            .then(response => this.extractArray(response))
            .catch(this.handleErrorPromise);
    }      

    addUser(formData: any): Promise<any>{
        return new Promise(function (resolve, reject) {

            let xhr = new XMLHttpRequest();
            xhr.open('POST', '/api/Services/AddUser', true);

            xhr.onload = function (e) {
                resolve(JSON.parse(xhr.response));
            };
            xhr.onerror = function (e) {
                reject(e);
            }

            xhr.send(formData);
        });
    }
  
    deleteUser(id: number) {
        return this._http.delete
           ('/api/Services/DeleteUser?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) {
        }

        
        return Promise.reject(error);
    }
}

4) app.component.ts

This is the core of 'myhw' component. It represents the code behind of 'apptemplate.component.html' template.

The HwComponent class is the view model for the 'apptemplate.component.html', is composed by the following methods:

  • loadMore method: loads more users and adds them to current list by invoking an external service via the call of " loadUsers method" of "IndexService variable". It implements a progressive load logic by retrieving 6 entries from remaining data in database after each call.
  • ngOnInit method: This method is called when the component is refreshed, it will load all existing countries data into select input of ‘newUserForm’ form, and load the 6 first entries from ‘CpUsers’ by calling loadMore method.
  • addUser method: Adds a new user by passing the input data of ‘newUserForm’ form to "Delete method" of "IndexService variable".
  • deleteUser method: Deletes an existing user identified by its "unique key" by calling "deleteUser method" of "IndexService variable".
JavaScript
import { Component, OnInit, Pipe, PipeTransform } from '@angular/core';
import { FormBuilder, FormGroup, Validators} from '@angular/forms';
import { User } from './model/User';
import { DatePipe } from './model/DatePipe';
import { IndexService } from './services/index.service';
 
@Component({
    selector: 'myhw',
    templateUrl: './app/template/apptemplate.component.html'
})
export class HwComponent extends OnInit {
    public registerForm: FormGroup;
    private model: User;
    private listCountries : string[];
    private listUsers: User[];
    private pageSize: number = 6;
    private isInserting: boolean;
    
    constructor(private indexService: IndexService, private formBuilder: FormBuilder) {
        super();
        this.model = new User();
//validation rules

        const dateFormatRegex = '^([0-9]{2})/([0-9]{2})/([0-9]{4})$';
        this.registerForm = this.formBuilder.group({
            userName: ['', Validators.required],
            userBirthDate: ['', Validators.compose
                           ([Validators.required, Validators.pattern(dateFormatRegex)])],
            userBirthLocation: ['', Validators.required],
            picture: ['', Validators.required],
        });
    }

    loadMore() {
        let _self = this;
        this.indexService.loadUsers((_self.listUsers != null) ? _self.listUsers.length : 0, 
                     _self.pageSize).then(response => {
            if (_self.listUsers != null)
                _self.listUsers = _self.listUsers.concat(response);
            else {
                _self.listUsers = response;
            }
        });
    }
    ngOnInit() {
        //load available user from service.
        this.indexService.loadCountries().then(response => {
            this.listCountries = response;
        });
        this.loadMore();
     }
    onChange(event) {
        var file = event.srcElement.files[0];
        this.model.userPictureFile = file;
        if (file != null && file.name.length > 0) {
            (this.registerForm.controls["picture"]).setValue("azfazf");
        }
    }
    addUser() {
         let _self = this;
        this.isInserting = false;
        var datePipe = new DatePipe();
        let formData = new FormData();
        formData.append('userName', _self.model.userName);
        formData.append('userBirthDate', datePipe.transform(_self.model.userBirthDate, 'dd/MM/yyyy'));
        formData.append('userBirthLocation', _self.model.userBirthLocation);
        formData.append('userPictureFile', _self.model.userPictureFile, 
                                           _self.model.userPictureFile.name);
       
        this.indexService.addUser(formData).then(response => {
            //add User will bring me back a user message with image in base64
            //add it manually to current user list
            // _self.listUsers.push(JSON.parse(response));
            console.log(response);
            if (response.statusCode == 200) {
                alert("Inserting Operation successfully done");
            } else {
                alert("Fail to insert, reason :" + response.reasonPhrase);
            }
            _self.isInserting = false;
            
        }).catch(response => {
            _self.isInserting = false;
            console.log(response);
        });
    }
    deleteUser(id: number) {
        let _self = this;
        _self.isInserting = false;
     
        _self.indexService.deleteUser(id).then(response => {
           
            if (response.status == 200) {
                
                //reresh the current list by removing a specific element.
                _self.listUsers.map(function (obj, index) {
                    if (obj.userId == id) {
                        _self.listUsers.splice(index,1);
                    }
                });
                _self.isInserting = false;
                alert("Deleting Operation successfully done");
            } else {
                alert("Fail to delete");
            }
            
        }).catch(response => {
            console.log(response);
        });
    }    
}

5) apptemplate.component.html

This is the HTML template, it contains:

  • newUserForm (form): used to create a new user characterized by: name (text), birth date (text), birth location (select box), picture (file).
  • grid (div): is such a scroll view used to display users details.
  • loadMoreUsers (Button): is used to load more entries into grid.

Note: registerForm is FormGroup used to assign a validation system to our form, to be sure that the add button will be enabled when all input respect 'validation rules' (see the initialization of registerForm into constructor method of HwComponent class).

HTML
 <style>
    .scrollView {
        height: 600px;
        overflow-y: auto;
        overflow-x:hidden;
    }
    .fixedSize{
        float: left; width:100px;
    }
    .errorInput{
        color : red;
    }
    
</style> 
<div>
    <div class="col col-lg-4">
        <form id="newUserForm" class="well" 
        [formGroup]="registerForm" novalidate>
            <h3>Add New User </h3>
          <div class="form-group">
            <label for="name">Name *</label>
            <input type="text" class="form-control"  
            [(ngModel)]="model.userName" name="userName"  
            formControlName="userName"  required>
              <p *ngIf="registerForm.controls.userName.errors" 
              class="errorInput">This field is required!</p>
          </div>
          <div class="form-group">
            <label for="description">BirthDate *</label>
            <input type="text" placeholder="dd/mm/yyyy" 
            class="form-control"   [(ngModel)]="model.userBirthDate"  
            name="userBirthDate"  formControlName="userBirthDate" required>
              <p *ngIf="registerForm.controls.userBirthDate.errors" 
              class="errorInput">
                  Required (Must be in format dd/mm/yyyy).
              </p>
          </div>
            <div class="form-group">
                <label for="description">From *</label>
                <select class="form-control" 
                [(ngModel)]="model.userBirthLocation" name="userBirthLocation" 
                formControlName="userBirthLocation" required>
                    <option *ngFor="let item of listCountries" [value]="item">
                        {{item}}
                     </option>
                </select>
                <p *ngIf="registerForm.controls.userBirthLocation.errors" 
                class="errorInput">This field is required!</p>
            </div>
            <div class="form-group">
                <label for="description">Picture *</label>
                <input type="file" class="form-control"  
                (change)="onChange($event)" name="picture"  required>
                <p *ngIf="registerForm.controls.picture.errors" 
                class="errorInput">This field is required!</p>
            </div>
      
          <button type="button" [disabled]="registerForm.valid == false" 
          (click)="addUser()" class="btn btn-primary">Ajouter</button>
        </form>
 
         <button id="loadMoreUsers" [disabled]="isInserting == true" 
         (click)="loadMore()" class="btn btn-primary">
         <span class="glyphicon glyphicon-refresh"></span> Load More Users</button>
          
     </div>
     <div id="grid" class="col col-lg-8 scrollView">
         <div *ngFor="let item of listUsers" >
             <div class="col-md-4" >
                 <div class="thumbnail">
           
                         <img alt="NULL" [src]="item.userPictureSrc" 
                         class="img-responsive" style="height:150px; width:200px" />
                         <div class="caption">
                             <label><span class="fixedSize">Name: 
                             </span>{{item.userName}}</label>
                             <div> <span style="fixedSize">Birth Date: 
                             </span> {{item.userBirthDate | date: 'dd/MM/yyyy'}}</div>
                             <div> <span style="fixedSize">Birth Location: 
                             </span> {{item.userBirthLocation}}</div>
                             <div style="text-align: center;">
                                     <a type="button" style="width: 40px;  
                                     " (click)="deleteUser(item.userId)" 
                                     href="#"><span class="glyphicon 
                                     glyphicon-remove"></span></a>
                             </div>
                         </div>
                 </div>
             </div>
         </div>
     </div>
  
</div>

D) Running the Web Application

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

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

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- 01/01/2017: Initial version

License

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