Performing CRUD Operation In SPA Using ASP.NET Core And Angular 2

Introduction
 
In my previous article, I explained How to create SPA using Asp.net Core and Angular 2. In this article, I will explain how to perform CRUD operation with SPA, using ASP.NET Core and Angular 2. Here, I will continue with an example, which was explained in my previous article.
 
In this example, I am using an Entity Framework Core with Code First approach and I am also doing manual database migration. To use an Entity Framework and MVC, we need to include certain references in project.json file (VS 2015) or .csproj file (VS 2017).
 
Project.json
  1. "dependencies": {    
  2.         "Microsoft.NETCore.App": {    
  3.           "type""platform",    
  4.           "version""1.1.0"    
  5.         },    
  6.         "Microsoft.AspNetCore" : "1.1.0",    
  7.         "Microsoft.AspNetCore.Mvc" : "1.1.0",    
  8.         "Microsoft.AspNetCore.SpaServices”: "1.1.0",    
  9.         "Microsoft.AspNetCore.StaticFiles""1.1.0",    
  10.         "Microsoft.Extensions.Logging.Debug":"1.1.0",    
  11.         "Microsoft.EntityFrameworkCore.SqlServer":"1.1.0 " ,  
  12.         "Microsoft.EntityFrameworkCore.SqlServer.Design ":"1.1.0 " ,  
  13. }  
.csproj file  
  1. <ItemGroup>  
  2.     <PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />  
  3.     <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />  
  4.     <PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />  
  5.     <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />  
  6.     <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />  
  7.     <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />  
  8.     <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.0" />  
  9. </ItemGroup>  
To demonstrate the fundamentals, I have created a table given below to the database. The column Id is defined as Primary and it is auto-incremented.
  1. CREATE TABLE [dbo].[MyDetails](  
  2.     [Id] [int] IDENTITY(1,1) NOT NULL,  
  3.     [FirstName] [varchar](50) NULL,  
  4.     [LastName] [varchar](50) NULL,  
  5.     [AdharNumber] [varchar](50) NULL,  
  6.     [Email] [varchar](250) NULL,  
  7.     [PhoneNumber] [varchar](50) NULL,  
  8.  CONSTRAINT [PK_MyDetails] PRIMARY KEY CLUSTERED   
  9. (  
  10.     [Id] ASC  
  11. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ONON [PRIMARY]  
  12. ON [PRIMARY]  
The next step is to create an Entity model and an entity.
 
EntityMoidel.cs
  1. namespace test  
  2. {  
  3.     using Microsoft.EntityFrameworkCore;  
  4.   
  5.     public class EntityModelContext : DbContext  
  6.     {  
  7.         public EntityModelContext(DbContextOptions<EntityModelContext> options) : base(options)  
  8.         {  
  9.   
  10.         }  
  11.         public DbSet<MyDetails> MyDetails { get; set; }  
  12.     }  
  13. }  
MyDetails.cs
  1. namespace test  
  2. {  
  3.     public class MyDetails  
  4.     {  
  5.         public int Id { get; set; }  
  6.         public string FirstName { get; set; }  
  7.         public string LastName { get; set; }  
  8.         public string AdharNumber { get; set; }  
  9.         public string Email { get; set; }  
  10.         public string PhoneNumber { get; set; }  
  11.     }  
  12. }  
Now, I am adding appsettings.json file, which holds various settings for the Application. I have added the database connection string in this file.
 
appsettings.json
  1. {  
  2.   "ConnectionStrings": {     
  3.     "DefaultConnection""Data Source=.\\SqlExpress;Initial Catalog=Test;User ID=sa; Password=Passwd@12"      
  4.   },    
  5.   "Logging": {  
  6.     "IncludeScopes"false,  
  7.     "LogLevel": {  
  8.       "Default""Debug",  
  9.       "System""Information",  
  10.       "Microsoft""Information"  
  11.     }  
  12.   }  
  13. }  
Next step is to register various Services like MVC, EF in startup class and also configure some global setting such as the connection string assigned to dbcontext.
 
Startup.cs
  1. using Microsoft.AspNetCore.Builder;  
  2. using Microsoft.AspNetCore.Hosting;  
  3. using Microsoft.AspNetCore.SpaServices.Webpack;  
  4. using Microsoft.Extensions.Configuration;  
  5. using Microsoft.Extensions.DependencyInjection;  
  6. using Microsoft.Extensions.Logging;  
  7. using Microsoft.EntityFrameworkCore;  
  8. using Microsoft.EntityFrameworkCore.Infrastructure;   
  9.   
  10. namespace test  
  11. {  
  12.     public class Startup  
  13.     {  
  14.         public Startup(IHostingEnvironment env)  
  15.         {  
  16.             var builder = new ConfigurationBuilder()  
  17.                 .SetBasePath(env.ContentRootPath)  
  18.                 .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)  
  19.                 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)  
  20.                 .AddEnvironmentVariables();  
  21.             Configuration = builder.Build();  
  22.         }  
  23.   
  24.         public IConfigurationRoot Configuration { get; }  
  25.   
  26.         // This method gets called by the runtime. Use this method to add services to the container.  
  27.         public void ConfigureServices(IServiceCollection services)  
  28.         {  
  29.             // Add framework services.  
  30.             services.AddMvc();  
  31.             services.AddEntityFramework()    
  32.             .AddDbContext<EntityModelContext>(    
  33.                 options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));  
  34.         }  
  35.   
  36.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  37.         public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
  38.         {  
  39.   
  40.             if (env.IsDevelopment())  
  41.             {  
  42.                 app.UseDeveloperExceptionPage();  
  43.                 app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {  
  44.                     HotModuleReplacement = true  
  45.                 });  
  46.             }  
  47.             else  
  48.             {  
  49.                 app.UseExceptionHandler("/Home/Error");  
  50.             }  
  51.   
  52.             app.UseStaticFiles();  
  53.   
  54.             app.UseMvc(routes =>  
  55.             {  
  56.                 routes.MapRoute(  
  57.                     name: "default",  
  58.                     template"{controller=Home}/{action=Index}/{id?}");  
  59.   
  60.                 routes.MapSpaFallbackRoute(  
  61.                     name: "spa-fallback",  
  62.                     defaults: new { controller = "Home", action = "Index" });  
  63.             });  
  64.         }  
  65.     }  
  66. }  
All the configuration related to an entity frame and MVC is done. It now ready to be used for CRUD operation.

In this example, I have created a client side model, which contains all the properties as Server side model (defined in the above section). The definition of client side model is given.
 
mydetails.model.ts
  1. export class MyDetails {  
  2.     id: number;  
  3.     firstName: string;  
  4.     lastName: string;  
  5.     adharNumber: string;  
  6.     email: string;  
  7.     phoneNumber: string  
  8. }  
I have added MydetailsController class and injected our entity model as a dependency, so that we can perform CRUD operations, using an Entity model.
 
The code given below is used to retrieve the list of all the available records from the database and get perticular record based on an Id.
 
MyDetailsController.cs
  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3. using Microsoft.AspNetCore.Mvc;  
  4.   
  5. namespace test.Controllers  
  6. {  
  7.     [Route("api/[controller]")]  
  8.     [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]  
  9.     public class MyDetailsController : Controller  
  10.     {  
  11.         private EntityModelContext _context = null;  
  12.         public MyDetailsController(EntityModelContext context)  
  13.         {  
  14.             _context = context;  
  15.         }  
  16.         [HttpGet("[action]")]  
  17.         public List<MyDetails> GetData()  
  18.         {  
  19.             List<MyDetails> data = null;  
  20.             data = _context.MyDetails.ToList();  
  21.             return data;  
  22.         }  
  23.   
  24.         [HttpGet]  
  25.         [Route("GetDetails/{id?}")]  
  26.         public MyDetails GetDetails(int? id)  
  27.         {  
  28.             MyDetails data = new MyDetails();  
  29.             if (id.HasValue)  
  30.             {  
  31.                 data = _context.MyDetails.Where(p => p.Id == id.Value).FirstOrDefault();  
  32.                 if (data == null)  
  33.                 {  
  34.                     data = new MyDetails();  
  35.                 }  
  36.             }  
  37.             return data;  
  38.         }  
  39. }  
The next step is to create an Angular side Service, which calls this method and passes the collection of data to the component and componet renders this data on to the view. In the Service, I have make a call to Web API methods, using Angular “http” Service.
 
mydetails.Services.ts
  1. import { Injectable } from '@angular/core';  
  2. import { Http, Response, RequestOptions, Headers } from '@angular/http';  
  3. import { Observable } from 'rxjs/Observable';  
  4. import 'rxjs/add/operator/map';  
  5.   
  6. import { MyDetails } from './mydetails.model'  
  7.   
  8. @Injectable()  
  9. export class MyDetailService {  
  10.   
  11.     public myDetail: MyDetails;  
  12.     public headers: Headers  
  13.   
  14.     constructor(private http: Http) {  
  15.         this.headers = new Headers();  
  16.         this.headers.append('Content-Type''application/json; charset=utf-8');  
  17.     }  
  18.   
  19.     getData() {  
  20.         return this.http.get('/api/MyDetails/GetData');  
  21.     }  
  22.   
  23.     getDetail(id: number) {  
  24.         return this.http.get('/api/MyDetails/getdetails/' + id);  
  25.     }  
  26.   
  27. }  
In the component, I have injected the Service “mydetails.Services” as dependency and call the Service methods to retrieve the data.
 
myDetails.Component.ts
  1. import { Component } from '@angular/core';  
  2.   
  3. import { MyDetailService } from '../../mydetails.services'  
  4. import { MyDetails } from '../../mydetails.model';  
  5.   
  6. @Component({  
  7.     selector: 'mydetail',  
  8.     templateUrl: './mydetails.Component.html',  
  9.     providers: [MyDetailService],  
  10.     styleUrls: ['./myDetails.component.css']  
  11. })  
  12. export class MyDetailsComponent {  
  13.     public myDetails: MyDetails[];  
  14.   
  15.     constructor(private myDetailService: MyDetailService) {  
  16.         this.getListData();  
  17.     }  
  18.   
  19.     getListData() {  
  20.         this.myDetailService.getData().subscribe(data => {  
  21.             this.myDetails = null;  
  22.             this.myDetails = data.json() as MyDetails[];  
  23.         },  
  24.             error => console.log(error)  
  25.         );  
  26.     }  
  27. }  
myDetails.Component.html
  1. <h1>  
  2.     List of People  
  3. </h1>  
  4.   
  5. <p *ngIf="!myDetails"><em>Loading...</em></p>  
  6.   
  7. <table class='table' *ngIf="myDetails">  
  8.     <thead>  
  9.         <tr>  
  10.             <td colspan="7">  
  11.                 <button class="btn btn-primary" [routerLink]="['/detail/0']">  
  12.                     <span class="glyphicon glyphicon-pencil paddingRight"></span> Add  
  13.                 </button>  
  14.             </td>  
  15.         </tr>  
  16.         <tr>  
  17.             <th>Id</th>  
  18.             <th>First Name</th>  
  19.             <th>Last Name</th>  
  20.             <th>Adhar Number</th>  
  21.             <th>Email</th>  
  22.             <th>Phone Number</th>  
  23.             <th></th>  
  24.         </tr>  
  25.     </thead>  
  26.     <tbody>  
  27.         <tr *ngFor="let myDetail of myDetails">  
  28.             <td>{{ myDetail.id }}</td>  
  29.             <td>{{ myDetail.firstName }}</td>  
  30.             <td>{{ myDetail.lastName }}</td>  
  31.             <td>{{ myDetail.adharNumber }}</td>  
  32.             <td>{{ myDetail.email }}</td>  
  33.             <td>{{ myDetail.phoneNumber }}</td>  
  34.             <td>  
  35.                 <button class="btn btn-primary" [routerLink]="['/detail/' + myDetail.id]">  
  36.                     <span class="glyphicon glyphicon-pencil paddingRight"></span> Edit  
  37.                 </button>  
  38.                 <button class="btn btn-primary" (click)="onDelete(myDetail.id)">  
  39.                     <span class="glyphicon glyphicon-remove"></span> Delete  
  40.                 </button>  
  41.             </td>  
  42.         </tr>  
  43.     </tbody>  
  44. </table>  
Output

 
 
To add and edit, I have added another component and its View,  as shown below. This component retrieves the data based on an Id from the Web API and the associated data with View.
 
detail.component.ts
  1. import { Component } from '@angular/core';  
  2. import { Router, ActivatedRoute, Params } from '@angular/router';  
  3.   
  4. import { MyDetailService } from '../../mydetails.services'  
  5. import { MyDetails } from '../../mydetails.model';  
  6.   
  7. @Component({  
  8.     selector: 'detail',  
  9.     templateUrl: './detail.Component.html',  
  10.     providers: [MyDetailService],  
  11.     styleUrls: ['./myDetails.component.css']  
  12. })  
  13. export class DetailComponent {  
  14.   
  15.   
  16.     public detail: MyDetails = new MyDetails();  
  17.     private id: number;  
  18.     constructor(private myDetailService: MyDetailService, private route: ActivatedRoute, private redirect: Router) {  
  19.   
  20.         this.route.params.subscribe((params: Params) => {  
  21.             this.id = params['id'];  
  22.             console.log(this.id)  
  23.         });  
  24.   
  25.         this.myDetailService.getDetail(this.id).subscribe(data => {  
  26.             this.detail = null;  
  27.             this.detail = data.json();  
  28.         },  
  29.             error => console.log(error)  
  30.         );  
  31.     }  
  32. }  
detail.component.html
  1. <div class="row margin-top-05">  
  2.     <h1>Add / Edit Detail</h1>  
  3. </div>  
  4. <div class="margin-top-05">  
  5.     <div class="row margin-top-05">  
  6.         <div class="col-xs-2"><strong> First Name : </strong></div>  
  7.         <div class="col-xs-6"><input [(ngModel)]="detail.firstName" required /> </div>  
  8.     </div>  
  9.     <div class="row margin-top-05">  
  10.         <div class="col-xs-2"><strong> Last Name : </strong></div>  
  11.         <div class="col-xs-6"><input [(ngModel)]="detail.lastName" /> </div>  
  12.     </div>  
  13.     <div class="row margin-top-05">  
  14.         <div class="col-xs-2"><strong> Adhar Number : </strong></div>  
  15.         <div class="col-xs-6"><input [(ngModel)]="detail.adharNumber" /> </div>  
  16.     </div>  
  17.     <div class="row margin-top-05">  
  18.         <div class="col-xs-2"><strong> Email : </strong></div>  
  19.         <div class="col-xs-6"><input [(ngModel)]="detail.email" /> </div>  
  20.     </div>  
  21.     <div class="row margin-top-05">  
  22.         <div class="col-xs-2"><strong> Phone Number : </strong></div>  
  23.         <div class="col-xs-6"><input [(ngModel)]="detail.phoneNumber" /> </div>  
  24.     </div>  
  25.     <div class="row margin-top-05">  
  26.         <div class="col-xs-12">  
  27.             <button class="btn btn-primary" (click)="save()">  
  28.                 <span class="glyphicon glyphicon-saved"></span> Save  
  29.             </button>  
  30.             <button class="btn btn-primary" (click)="list()">  
  31.                 <span class="glyphicon glyphicon-list"></span> List  
  32.             </button>  
  33.         </div>  
  34.     </div>  
  35. </div>  
 
 
To perform add and edit operation, I have added the methods given below to component class, which is called on the click of Save button click. This function internally calls Service “postData” method. This postData method posts the data to the Web API, using Angular http Service.
 
detail.component.ts
  1. save() {  
  2.         this.myDetailService.postData(this.detail)  
  3.             .subscribe(  
  4.             (response) => {  
  5.                 console.log(response);  
  6.                 this.list();  
  7.             },  
  8.             (error) => console.log(error)  
  9.             );  
  10. }  
mydetails.Services.ts
  1. postData(detail: MyDetails) {  
  2.         return this.http.post('/api/MyDetails/Save', detail);  
  3. }  
MyDetailsController.cs
  1. [HttpPost("[action]")]  
  2. public IActionResult Save([FromBody] MyDetails myDetail)  
  3. {  
  4.     bool isNew = false;  
  5.     MyDetails data = _context.MyDetails.Where(p => p.Id == myDetail.Id).FirstOrDefault();  
  6.     if (data == null)  
  7.     {  
  8.         data = new MyDetails();  
  9.         isNew = true;  
  10.     }  
  11.     data.FirstName = myDetail.FirstName;  
  12.     data.LastName = myDetail.LastName;  
  13.     data.AdharNumber = myDetail.AdharNumber;  
  14.     data.Email = myDetail.Email;  
  15.     data.PhoneNumber = myDetail.PhoneNumber;  
  16.     if (isNew)  
  17.     {  
  18.         _context.Add(data);  
  19.     }  
  20.     _context.SaveChanges();  
  21.     return Ok(data);  
  22. }  
I have used the code given above to insert or update the data into the database. If the record is found in the database, then perform an update operation, else perform an add operation.
 
Once the data is committed to the database, system will redirect to the list page. Using the code given below, we can do redirection. I have added this function to detail.component.
 
detail.component.ts
  1. list() {  
  2.     this.redirect.navigateByUrl('/my-detail');  
  3. }  
To perform delete operation, I have added the code given below to Web API. This method only accepts an Id as a parameter and it deletes the data from the database, which is based on an id.
 
MyDetailsController.cs
  1. [HttpDelete("[action]")]  
  2. public IActionResult Delete([FromBody] int id)  
  3. {  
  4.     MyDetails data = _context.Set<MyDetails>().FirstOrDefault(c => c.Id == id);  
  5.     _context.Entry(data).State = Microsoft.EntityFrameworkCore.EntityState.Deleted;  
  6.     _context.SaveChanges();  
  7.     return Ok(true);  
  8. }  
I have added function given below to the Service and the component to perform delete operation.
 
mydetails.Services.ts
  1. deleteData(id: number) {  
  2.     return this.http.delete('/api/MyDetails/Delete/'new RequestOptions({  
  3.         headers: this.headers,  
  4.         body: id  
  5.     }));  
  6. }  
mydetails.Component.ts
  1. onDelete(id: number) {  
  2.     this.myDetailService.deleteData(id).subscribe(data => {  
  3.         this.getListData();  
  4.     },  
  5.         error => console.log(error)  
  6.     );  
  7. }  
After the successful deletion, I just call the list method to synchronize up the list data on the page.
 
Summary

In this article, we learned how to do CRUD operation, using ASP.NET MVC Core and Angular 2.