1. 程式人生 > >ASP.NET Core 2.0和Angular 4:從頭開始構建用於車輛管理的Web應用程式

ASP.NET Core 2.0和Angular 4:從頭開始構建用於車輛管理的Web應用程式

 

目錄

介紹

背景

使用程式碼

I)伺服器端

a)先決條件

b)設定專案

c)設定資料庫

d)使用AutoMapper

e)使用Swagger

f)執行API

II)客戶端

a)先決條件

b)設定專案

c)實現服務

d)實現元件

III)執行專案

參考


ASP.NET Core 2.0&Angular 4:通過本教程,您將學習如何從頭開始構建用於車輛管理的Web應用程式

 

  1. 下載演示dotnetcore - 811 KB
  2. 下載演示Angular - 92.3 KB

 

介紹

本文的主要目的是發現有關Angular4.NET CORE Framework更多功能,例如:

  1. 使用WebApi v2建立RestFul伺服器
  2. 使用Entity框架進行.NET Core程式碼優先(code first)方法
  3. AutoMapper
  4. 使用.NET Core依賴注入
  5. .NET Core專案中使用Swagger API
  6. 使用Angular建立單頁應用程式
  7. Angular建立元件,服務和類
  8. Angular路由系統
  9. Angular表單驗證
  10. 使用主題模式在元件之間進行通訊
  11. 使用Angular-datatables

背景

為了更好地理解本文,建議您具備以下方面的良好知識:

  1. C#程式設計
  2. SQL語言
  3. 實體框架
  4. Visual Studio程式碼

使用程式碼

I)伺服器端

a)先決條件

在開始實施WebApi服務專案之前,您將在下面找到所有有用的連結:

  1. Visual Studio程式碼(連結
  2. DotNetCore 2.0.0連結
  3. NET核心命令列工具(連結
  4. AutoMapper連結
  5. Swagger Api連結

b)設定專案

使用Visual Studio程式碼整合終端,我們執行以下命令列來建立一個新的

DotNet Core MVC專案,其中demobackend資料夾中包含單獨的身份驗證系統。

mkdir demobackend
dotnet new mvc -au Individual -f netcoreapp2.0

這是專案結構:

https://www.codeproject.com/KB/aspnet/1210559/capture1.PNG

要僅將WebApi用於身份系統,您應該在專案中引入一些更改:

替換啟動程式:

       public void ConfigureServices(IServiceCollection services)
       {
           services.AddDbContext<ApplicationDbContext>(options =>
           options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")))
           services.AddIdentity<applicationuser, identityrole="">()
               .AddEntityFrameworkStores<applicationdbcontext>()
               .AddDefaultTokenProviders();
           // Add application services.
           services.AddMvc();
       }
       // This method gets called by the runtime.
       // Use this method to configure the HTTP request pipeline.
       public void Configure(IApplicationBuilder app, IHostingEnvironment env)
       {
           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
           }
            // Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseMvc();
       }

AccountController類外,刪除所有存在於控制器資料夾中的controller

用下面的程式碼替換AccountController,使其僅具有諸如loginlogoutsignup等僅返回http狀態程式碼(200,400)的有用操作。

namespace demobackend.Controllers
{
    [Authorize]
    [Route("[controller]/[action]")]
    public class AccountController : Controller
    {
        private readonly UserManager<applicationuser> _userManager;
        private readonly SignInManager<applicationuser> _signInManager;
        private readonly ILogger _logger;

        public AccountController(
            UserManager<applicationuser> userManager,
            SignInManager<applicationuser> signInManager,
            ILogger<accountcontroller> logger)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _logger = logger;
        }

        [TempData]
        public string ErrorMessage { get; set; }     
        [HttpPost]
        [AllowAnonymous]
        public async Task<iactionresult> Login([FromBody]LoginViewModel model)
        {
             if (ModelState.IsValid)
            {
                // This doesn't count login failures towards account lockout
                // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                var result = await _signInManager.PasswordSignInAsync
                    (model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
                if (result.Succeeded)
                {
                    var msg = "User logged in.";
                    return Ok(msg);
                }
            }
            // If we got this far, something failed, redisplay form
            return BadRequest("Fail to login with this account");
        }
        [HttpPost]
        [AllowAnonymous]
        public async Task<iactionresult> Register([FromBody] RegisterViewModel model)
        {
            var msg = "Bad Request";
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
                var result = await _userManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    _logger.LogInformation("User created a new account with password."); 

                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                     //await _signInManager.SignInAsync(user, isPersistent: false);
                    _logger.LogInformation("User created a new account with password.");
                    msg = "User created a new account with password.";
                    return Ok(msg);
                }
            }
            // If we got this far, something failed, redisplay form
            return BadRequest(msg);
       }

       [HttpGet]
       [AllowAnonymous]
        public async Task<iactionresult> Logout()
        {
            await _signInManager.SignOutAsync();
          //  await HttpContext.SignOutAsync("MyCookieAuthenticationScheme");
            _logger.LogInformation("User logged out.");
            var msg = "User logged out.";
            return Ok(msg);
        }
    }

c)設定資料庫

首先,您應該使用SSMS建立一個命名為demoangulardatabase 的空資料庫。

接下來,您應該修改appsettings.json中的預設連線字串:

"ConnectionStrings": {

"DefaultConnection": "Server=(LocalDb)\\MSSQLLocalDB;Database=demoangulardatabase;
                        Trusted_Connection=True;MultipleActiveResultSets=true"
},

對於此示例,您只需要Car實體,為此,您需要在Data資料夾中建立Car類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace demobackend.Data
{
    public class Car
    {        
        [Key]
        public int Id {get; set;}
        [StringLength(50),Required]
        public string Name {get; set;}
        [StringLength(50),Required]
        public string Mark {get; set;}
        [StringLength(50),Required]
        public string Model {get; set;}
        [Required]
        public DateTime Registered { get; set; }
    }
}

要更新資料庫架構,我們將使用Entity Framework Core遷移工具:

  1. 新增新的遷移指令碼:
    dotnet ef migrations add initialMigration -c ApplicationDbContext -v
  2. 更新資料庫架構:
    dotnet ef database update -c ApplicationDbContext -v

最後,當您重新整理資料庫伺服器時,您將獲得以下結果:

https://www.codeproject.com/KB/aspnet/1210559/Capture2.PNG

d)使用AutoMapper

目的是建立從ViewModel物件到Entities物件的對映,反之亦然:

首先,在Model資料夾中建立CarViewModel類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace demobackend.Models
{
    public class CarViewModel
    {
        public int Id {get; set;}
        [StringLength(50),Required]
        public string Name {get; set;}
        [StringLength(50),Required]
        public string Mark {get; set;}
        [StringLength(50),Required]
        public string Model {get; set;}
        [Required]
        public DateTime Registered { get; set; }
    }
}

其次,通過AutoMapper Profile Configuration檔案配置對映:在AutoMapperProfile資料夾中建立此檔案:

using demobackend.Data;
using demobackend.Models;
using System.Collections.Generic;
using AutoMapper;
using System;

namespace demobackend.AutoMapperProfile
{
    public class AutoMapperProfileConfiguration : Profile
    {
        public AutoMapperProfileConfiguration()
        : this("MyProfile")
        {
        }
        protected AutoMapperProfileConfiguration(string profileName)
        : base(profileName)
        {
          
            CreateMap<Car, CarViewModel>();
            CreateMap<CarViewModel, Car>();
        }
    }
}

最後,在startup.cs,在ConfigureServices方法中新增以下行以建立和啟動IMapper服務,其將注入控制器中:

var config = new AutoMapper.MapperConfiguration(cfg =>
            {
                cfg.AddProfile(new AutoMapperProfileConfiguration());
            });

var mapper = config.CreateMapper();
services.AddSingleton(mapper);

使用ASP.NET Core Web API 2.0建立Car Management API

建立一個名為ManageCarController的新控制器,它將包含每個CRUD方法的端點:

  1. Get():返回包含所有可用汽車的HttpResponseMessage
  2. Get(int id):返回由id引數標識的包含特定car物件的HttpResponseMessage
  3. Post([FromBody] CarViewModel _car):它將建立一個新車,如果檢查操作,其將返回一個Http ok狀態程式碼(http 200),否則它將返回(http 400
  4. Put(int id, [FromBody] CarViewModel value):它將修改特定的汽車(由id引數標識)。如果car不存在,它將返回Http未找到的程式碼,如果更新操作有效則返回http 200狀態程式碼,否則返回Http錯誤請求。
  5. Delete(int id):它將刪除特定的car(由id引數標識),如果操作有效,將返回Http ok狀態程式碼(http 200)。
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using demobackend.Data;
using demobackend.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;

namespace demobackend.Controllers
{
        [Authorize]
        [Route("api/[controller]")]
        public class ManageCarController : Controller
        {
            private IMapper _mapper;
            private ApplicationDbContext dbContext;
            public ManageCarController(IMapper mapper, ApplicationDbContext context)
            {
                this._mapper = mapper;
                this.dbContext = context;
            }
            // GET api/values
            [HttpGet]
            public IEnumerable<carviewmodel> Get()
            {
                IEnumerable<carviewmodel> list = 
                    this._mapper.Map<ienumerable<carviewmodel>>(this.dbContext.cars.AsEnumerable());
                return list;
            }
            // GET api/values/5
            [HttpGet("{id}")]
            public  IActionResult Get(int id)
            {
                var _car = this._mapper.Map<carviewmodel>(this.dbContext.cars.Find(id));
                return Ok(_car);
            }

            // POST api/values
            [HttpPost]
            public IActionResult Post([FromBody] CarViewModel _car)
            {
               if (ModelState.IsValid)
                {
                    _car.Registered = DateTime.Now;
                    var newcar = this._mapper.Map<car>(_car);
                    this.dbContext.cars.Add(newcar);
                    this.dbContext.SaveChanges();
                    return Ok();
                }else{
                    return BadRequest();
                }
            }

            // PUT api/values/5
            [HttpPut("{id}")]
            public IActionResult Put(int id, [FromBody] CarViewModel value)
            {   
                if (ModelState.IsValid)
                {
                    var existingCar = this.dbContext.cars.Find(id);
                    if(existingCar == null){
                          return NotFound();
                     }else{ 
                        existingCar.Name = value.Name;
                        existingCar.Mark = value.Mark;
                        existingCar.Model = value.Model;
                        this.dbContext.cars.Update(existingCar);
                        this.dbContext.SaveChanges();
                        return Ok();
                    }
                }else{
                    return BadRequest();
                }
            }

            // DELETE api/values/5
            [HttpDelete("{id}")]
            public IActionResult Delete(int id)
            {
                this.dbContext.cars.Remove(this.dbContext.cars.Find(id));
                this.dbContext.SaveChanges();
                return Ok();
            }
    }
}

e)使用Swagger

要測試ManageCarWebApi中的每個操作,我們將使用swaggerAPI

首先,我們應該安裝它:

dotnet add package Swashbuckle.AspNetCore --version 1.0.0

接下來,在startup.cs

  • ConfigureServices部分中新增以下行:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
  • Configure部分中新增以下行:
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
})

f)執行API

要執行專案,請執行以下命令列:

dotnet run

開啟瀏覽器並編寫以下URL

http://localhost:5000/swagger/

最後,您將獲得Swagger用於測試APIUI介面:

https://www.codeproject.com/KB/aspnet/1210559/Capture3.PNG

以下是如何使用AccountManageCar服務的一些示例:

測試訂閱服務

https://www.codeproject.com/KB/aspnet/1210559/Capture4.PNG

結果:

https://www.codeproject.com/KB/aspnet/1210559/Capture5.PNG

測試認證服務

https://www.codeproject.com/KB/aspnet/1210559/Capture6.PNG

結果:

https://www.codeproject.com/KB/aspnet/1210559/Capture7.PNG

測試新車的建立

https://www.codeproject.com/KB/aspnet/1210559/Capture8.PNG

結果:

https://www.codeproject.com/KB/aspnet/1210559/Capture9.PNG

測試獲取所有可用汽車

https://www.codeproject.com/KB/aspnet/1210559/Capture10.PNG

II)客戶端

a)先決條件

在開始實施客戶端專案之前,您將在下面找到所有有用的連結:

  1. 安裝Visual Studio程式碼(連結
  2. 安裝nodejs + npm連結
  3. 設定開發環境(連結
  4. 安裝Angular-Datatables連結
  5. 安裝Angular2-busy連結
  6. 角度表單驗證(連結
  7. 主題模式(連結

b)設定專案

要建立新的Angular專案,請在工作區資料夾中執行以下命令列:

首先,安裝Angular CLI工具:

npm install -g @angular/cli

其次,使用模板生成一個新的Angular專案:

ng new demo

最後,執行應用程式:

cd demo
ng serve --open

c)實現服務

在開始實現服務之前,我們應該首先宣告模型類:

  • ICar:用於將json資料反序列化為ICar 物件。
export interface ICar {
id: number,
name: string,
mark: string,
model: string,
registered : Date
}
  • Message:用於儲存有關丟擲通知的資訊:
    1. Type:這是一種警報型別,它可以作為價值:Error'Success'
    2. Text:這是一個警報說明
export class Message {
 constructor(public type : string, public text: string) {
 }
}
  • User:用於將json資料反序列化為user 物件。
export class User {
email : string = "";
password : string = "";
rememberMe : boolean = false;
}

loginService

包含管理與伺服器的會話的方法:

  1. loginSubject:它實現Subject模式。用於跟蹤會話狀態,當用戶建立新會話時,他將收到通知(loginSubject.next(1))以在頁面頂部顯示電子郵件地址,否則當他退出時,電子郵件將消失。
  2. login(currentUser : User)POSTAccount/Login服務傳送http請求以嘗試與伺服器的新連線。作為引數,它將傳遞一個User物件。
  3. logout()POSTAccountLogout服務傳送http請求以嘗試結束當前會話。
import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, Response, RequestMethod } from '@angular/http';
import {User} from '../models/user';
import  'rxjs/add/operator/toPromise';
import { Subject } from 'rxjs';

@Injectable()
export class LoginService {
    public loginSubject = new Subject<any>();
    _baseUrl : string = "http://localhost:5000/Account";
     
    options = new RequestOptions({
           withCredentials : true
    }); 
    constructor(private http: Http) { }
   
    public login(currentUser : User) {     
      
        let _currentUser = JSON.stringify(currentUser);
        return this.http.post(this._baseUrl + '/Login', currentUser, this.options)
         .toPromise()
         .catch(this.handleError);

    }   
    public logout(){ 
        return this.http.get( this._baseUrl + '/Logout', this.options)
        .toPromise()
        .catch(this.handleError);
    }
    private handleError(error: any): Promise<any> {
        return Promise.reject(error.message || error);
      }    
}   

CarService

包含將cars資料管理到資料庫的方法。

  1. getCars()Get/api/ManageCar服務傳送http請求以獲取所有可用的汽車
  2. getCar(id : number)Get/api/ManageCar服務傳送http請求以獲取特定的汽車(由id引數標識)
  3. addNewCar(_car : ICar):使用_car物件作為引數向/api/ManageCar服務傳送POST http請求,以在CAR表中建立新行。
  4. updateCar(_car : ICar):使用_car 物件作為引數向/api/ManageCar服務傳送PUT http請求以更新現有汽車(標識為id)。
  5. deleteCar(id : number):向/api/ManageCar服務傳送Delete http請求以刪除特定的car(由 標識id)。
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs/Rx';
import { Http, Headers, RequestOptions, Response, RequestMethod } from '@angular/http';
import { ICar } from './../models/ICar';

@Injectable()
export class CarService {
    carsList : ICar[];
    _baseUrl : string = "http://localhost:5000/api/";
    _getCarsUrl : string = "ManageCar"; 
    options = new RequestOptions({
        withCredentials : true
    }); 
    constructor(private http: Http) { 
 
    }
    public getCars() {
        return this.http.get(this._baseUrl + this._getCarsUrl, this.options)
        .toPromise();
    }
    public getCar(id : number) {
        return this.http.get(this._baseUrl + this._getCarsUrl + "/"+ id, this.options)
        .toPromise();
    }
    public addNewCar(_car : ICar){
       return this.http.post(this._baseUrl + this._getCarsUrl, _car, this.options)
       .toPromise();
     }
    public updateCar(_car : ICar){
        return this.http.put(this._baseUrl + this._getCarsUrl + "/"+  
                             _car.id, _car, this.options)
        .toPromise();
    }
    public deleteCar(id : number){
         return this.http.delete(this._baseUrl + this._getCarsUrl + "/"+ id, this.options)
        .toPromise();    
    }
}

CanActivateService

其目的是通過檢查使用者是否被授權或者不通過該連結閱讀更多關於這個服務的資訊,來確保某些路由訪問。

它從CanActivate介面實現canActivate方法。

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } 
from '@angular/router';

@Injectable()
export class CanActivateService implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('loggedUser')) {
return true;
}else{
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}
}

NotifService

它實現了通知系統,它將用於在頁面丟擲警報對話方塊中顯示成功和錯誤訊息。

此服務實現Subject模式以捕獲觀察者的通知,以在頁面上顯示或隱藏訊息。

import { Message } from './../models/Message';
import { Injectable } from '@angular/core';
import { Subject, Observable} from 'rxjs/Rx';
import { Router, NavigationStart, Event} from '@angular/router';

@Injectable()
export class NotifService {
subject = new Subject<any>();
constructor(private router: Router) {
router.events.subscribe( event =>
{
if(event instanceof NavigationStart) {
this.subject.next();
}
});
}
success(message: string) {
this.subject.next(new Message('alert-success', message));
}
error(message: string) {
this.subject.next(new Message('alert-danger', message));
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}

d)實現元件

登入元件

LoginComponent.ts

  1. login():它將通過model物件傳遞namepassword,從LoginService呼叫login方法

在成功操作的情況下,它將與伺服器建立新會話並使用路由器服務導航到列表頁面。否則,它將通過notifService呼叫錯誤方法顯示錯誤訊息。

import { Headers } from '@angular/http';
import { Component, NgModule, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { User } from '../../models/user';
import { LoginService } from './../../services/login-service.service';
import { NotifService } from './../../services/notif-service.service';

@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
EMAIL_REGEXP = "^[a-z0-9!#$%&'*+\/=?^_`{|}~.-][email protected][a-z0-9]([a-z0-9-]*[a-z0-9])?
                   (\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$";
model : User;
loading = false;
message = "";
busy: Promise<any>;
constructor(
private router: Router,
private notifService : NotifService,
private loginService : LoginService
) {
this.model = new User();
}

ngOnInit() {
localStorage.removeItem('loggedUser');
this.loading = false;
this.loginService.logout().then(resp => {
this.loginService.loginSubject.next(1);
});
}

ngDestroy()
{
}
login() {
//clean notifications message on page
this.notifService.subject.next();
this.loading = true;
this.busy = this.loginService.login(this.model).then(resp => {
this.loading = false;
localStorage.setItem('loggedUser', this.model.email);
this.loginService.loginSubject.next(1);
this.router.navigate(["/list"]);
}).catch(exp => {
this.notifService.error(exp._body);
this.loading = false;
}) ;
}
}

Login.component.html

此頁面提供登入介面,供使用者輸入其憑據(電子郵件和密碼)以開啟與伺服器的新會話並可訪問應用程式。

<form [ngBusy]="busy" class="form-horizontal" (ngSubmit)="f.form.valid && login()" 

        #f="ngForm" novalidate>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<h4>Please Login</h4>
<hr>
</div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<div class="form-group">
<label class="sr-only" for="email">E-Mail Address</label>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon" style="width: 2.6rem">
<i class="fa fa-at"></i></div>
<input type="email" class="form-control" name="email"

[(ngModel)]="model.email" #username="ngModel" [pattern]="EMAIL_REGEXP" required />
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-control-feedback" *ngIf="f.submitted && !username.valid">
<span class="text-danger align-middle">
<i class="fa fa-close"></i> Username is required
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<div class="form-group">
<label class="sr-only" for="password">Password</label>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon" style="width: 2.6rem"><i class="fa fa-key"></i></div>
<input type="password" class="form-control" name="password"

[(ngModel)]="model.password" #password="ngModel" required />
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-control-feedback" *ngIf="f.submitted && !password.valid">
<span class="text-danger align-middle">
<i class="fa fa-close"></i> Password is required
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6" style="padding-top: .35rem">
<div class="form-check mb-2 mr-sm-2 mb-sm-0">
<label class="form-check-label">
<input [(ngModel)]="model.rememberMe" class="form-check-input" name="rememberMe"

type="checkbox" >
<span style="padding-bottom: .15rem">Remember me</span>
</label>
</div>
</div>
</div>
<div class="row" style="padding-top: 1rem">
<div class="col-md-3"></div>
<div class="col-md-6">
<button [disabled]="loading" class="btn btn-primary"><i class="fa fa-sign-in"></i> Login</button>
</div>
</div>
</form>

列表元件

List.component.ts

  1. init():通過配置paginTypepageLength屬性初始化datatable物件。然後,它將通過呼叫CarService中的getCars方法以載入可用的car。如果引發異常,則通過呼叫NofifService中的error方法在頁面頂部顯示錯誤訊息。
  2. searchCar():它按id過濾顯示的汽車。當用戶從下拉選單中選擇汽車名稱時呼叫它。
  3. deleteCar(id : number):它從資料庫中刪除特定項(由引數id標識)並從檢視中刪除它。此方法將呼叫CarService中的deleteCar操作。如果引發異常,則通過呼叫NofifService中的error方法在頁面頂部顯示錯誤訊息。
import { NotifService } from './../../services/notif-service.service';
import { Component, NgModule , OnInit } from '@angular/core';
import { CarService } from '../../services/car-service.service';
import { Subscription } from 'rxjs/Subscription';
import { ICar } from './../../models/ICar';
import { Subject } from 'rxjs/Rx';
import { RouterLink, Event } from '@angular/router';

@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
listCars : any = [];
filtredCars : any = [];
carName : string = "";
selectedItem : number;
dtTrigger = new Subject();
dtOptions: DataTables.Settings = {};

constructor(private _carService : CarService, private notifService : NotifService ) {
this.init();
}

private init()
{
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 10
};

this.selectedItem = -1;
this._carService.getCars() .then( response => {
this.listCars = response.json() as ICar[];
this.filtredCars = this.listCars.slice(0);
// Calling the DT trigger to manually render the table
this.dtTrigger.next();
}).catch((resp) => {
console.log(resp);
this.notifService.error("Server Exception was raised");
});
}
public searchCar ()
{
if(this.selectedItem == -1)
{
this.filtredCars = this.listCars.slice(0);
}else
{
this.filtredCars = this.listCars.filter(
car => car.id == this.selectedItem );
}
}

public deleteCar(id : number)
{
this._carService.deleteCar(id).then( response => {
this.filtredCars = this.filtredCars.filter(
(item : ICar) => {
return (item.id != id)
})
this.notifService.success("Delete was well done");
// this.dtTrigger.next();
}).catch((resp) => {
this.notifService.error("Server Exception was raised");
});
}
ngOnInit()
{
}
}

List.component.html

此頁面顯示所有可用的詳細汽車,並允許使用者使用下拉過濾器或使用datatable元件的本機搜尋框按車輛ID進行過濾。

通過datatable,您可以刪除,更新特定汽車或新增新汽車。

要更新或建立汽車,您將導航到專用頁面。

<div class="row">
<div class="col-lg-8">
<p><b>Filter by car id :</b>
<select [(ngModel)]="selectedItem" (ngModelChange)="searchCar()" >
<option [value]="-1" selected>choose option</option>
<option *ngFor="let item of listCars" [value]="item.id" >{{item.name}}</option>
</select>
</p>
</div>
<div class="col-lg-4">
<a class="btn btn-primary btn-xs pull-right " routerLink="/newcar" 

preserveQueryParams preserveFragment><b>+</b> Add new car </a>
</div>
</div>
<br>
<div class="row">
<div class="col-lg-12">
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="row-border hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Mark</th>
<th>Model</th>
<th>Registred Date</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let car of filtredCars">
<td>{{car.id}} </td>
<td>{{car.name}}</td>
<td>{{car.mark}}</td>
<td>{{car.model}}</td>
<td>{{car.registered | date }}</td>
<td>
<button class="btn btn-link" routerLink="/updatecar/{{car.id}}" 

preserveQueryParams preserveFragment>
<i class="fa fa-pencil" aria-hidden="true"></i> Update
</button>
<button class="btn btn-link" (click)="deleteCar(car.id)">
<i class="fa fa-trash" aria-hidden="true"></i> Delete
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>

Notificationcar元件

Notification.component.ts

它使用notifService訂閱Subject Obsevable,它將偵聽從其他元件(觀察者)傳送的所有通知並顯示相應的訊息。

import { Component, OnInit } from '@angular/core';
import { NotifService } from './../../services/notif-service.service';
import { Message } from './../../models/Message';

@Component({
selector: 'notification',
templateUrl: './notification.component.html',
styleUrls: ['./notification.component.css']
})

export class NotificationComponent implements OnInit {

message : Message;
constructor(public notifService : NotifService ){}
ngOnInit() {
this.notifService.getMessage().subscribe(p =>
{
this.message = p;
});
}
}

新增新車元件

Newcar.component.ts

  1. complexForm:用於表單驗證,在提交之前驗證以下輸入:name, mark model
  2. newCar(model: ICar):將通過呼叫CarService中的addNewCar操作來建立一個新的汽車儲存到資料庫。

如果引發異常,則通過呼叫NofifService例項的 error方法,在頁面頂部顯示錯誤訊息。

import { Component,NgModule, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
import { ICar} from "../../models/ICar"
import { CarService } from './../../services/car-service.service';
import { NotifService } from './../../services/notif-service.service';
@Component({
selector: 'app-newcar',
templateUrl: './newcar.component.html',
styleUrls: ['./newcar.component.css']
})
export class Newcar implements OnInit {
complexForm : FormGroup;
constructor(fb: FormBuilder, private carService : CarService
,private notifService : NotifService
){
// Here we are using the FormBuilder to build out our form.
this.complexForm = fb.group({
// We can set default values by passing in the corresponding value or leave blank 
// if we wish to not set the value. For our example, we’ll default the gender to female.
'name' : [null, Validators.required],
'mark': [null, Validators.required],
'model' : [null, Validators.required],
});
}
ngOnInit() {
}
public newCar(model: ICar){
this.carService.addNewCar(model).then(resp => {
this.notifService.success("Insertion operation was well done");
}).catch(exp => {
this.notifService.error("Server Exception was raised");
}) ;
}
}

Newcar.component.html

通過此頁面,使用者可以輸入有關新車的資訊(名稱,標記,型號)並將其提交給伺服器。

所有輸入都是實現此操作所必需的,否則他將獲得驗證錯誤訊息。

<h3> New Car</h3>
<form [formGroup]="complexForm" (ngSubmit)="newCar(complexForm.value)">
<div class="form-group">
<label for=""><b>Name</b></label>
<input type="text" class="form-control" [formControl]="complexForm.controls['name']" />
<div *ngIf="complexForm.controls['name'].hasError('required')" class="has-error">
field required</div>
</div>
<div class="form-group">
<label for=""><b>Mark</b></label>
<input type="text" class="form-control" [formControl]="complexForm.controls['mark']"/>
<div *ngIf="complexForm.controls['mark'].hasError('required')" class="has-error">
field required</div>
</div>
<div class="form-group">
<label for=""><b>Model</b></label>
<input type="text" class="form-control" [formControl]="complexForm.controls['model']"/>
<div *ngIf="complexForm.controls['model'].hasError('required')" class="has-error">
field required</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" [disabled]="!complexForm.valid">Submit</button>
</div>
</form>

更新汽車元件

Update.component.ts

  1. constructor(…):獲取特定car的詳細資訊(使用路由url引數給出的id引數)並初始化FormGroup物件(complexForm)以填充和驗證表單欄位。
  2. updateCar(model: ICar):將通過呼叫CarService中的updateCar操作更新特定的汽車。

如果引發異常,則通過呼叫NofifService例項的錯誤方法,在頁面頂部顯示錯誤訊息。

import { Component,NgModule, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
import { ICar } from "../../models/ICar"
import { CarService } from './../../services/car-service.service';
import { NotifService } from './../../services/notif-service.service';
import { ActivatedRoute } from '@angular/router';

@Component({

selector: 'app-updatecar',
templateUrl: './update.component.html',
styleUrls: ['./update.component.css']
})
export class UpdateCarComponent implements OnInit {
complexForm : FormGroup ;

constructor(private fb: FormBuilder
, private carService : CarService
, private notifService : NotifService
, private route: ActivatedRoute ){
// Here we are using the FormBuilder to build out our form.
this.route.params.subscribe(params => {

let id = +params['id']; // (+) converts string 'id' to a number
this.complexForm = fb.group({
// We can set default values by passing in the corresponding value or leave blank 
// if we wish to not set the value. For our example, we’ll default the gender to female.
'id' : [""],
'name' : ["", Validators.required],
'mark': ["", Validators.required],
'model' : ["", Validators.required],
});
this.carService.getCar(id).then(resp => {
let car = resp.json() as ICar;
this.complexForm = fb.group({
// We can set default values by passing in the corresponding value or leave blank 
// if we wish to not set the value. For our example, we’ll default the gender to female.
'id' : [car.id],
'name' : [car.name, Validators.required],
'mark': [car.mark, Validators.required],
'model' : [car.model, Validators.required],
});
}).catch(exp => {
this.notifService.error("Server Exception was raised");
}) ;
});
}
ngOnInit() {
}

public updateCar(model: ICar){
console.log(model);
this.carService.updateCar(model).then(resp => {
this.notifService.success("Update operation was well done");
}).catch(exp => {
this.notifService.error("Server Exception was raised");
}) ;
}
}

Update.component.html

此頁面將在HTML表單中顯示特定的汽車詳細資訊,允許使用者修改某些資訊,如(name, mark, model)。

應用元件

App.component.ts

用作app.html模板的程式碼,您將在其中實現:

  1. constructor:用於初始化我們的母版頁(app.html),並宣告根據當前會話值控制使用者導航的路由器偵聽器(會話由cookie管理)
  2. logOut:此事件用於通過清除Cookie以及將使用者重定向到登入頁面來釋放當前會話。
import { Component } from '@angular/core';
import { User } from '../../models/user';
import { LoginService } from '../../services/login-service.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Angular 4 Demo';
userEmail : string = "";

constructor(private loginService : LoginService) {
loginService.loginSubject.asObservable().subscribe(p =>
{
this.userEmail = localStorage.getItem('loggedUser') || "";
});
}
ngOnInit() {
//Called after the constructor, initializing input properties, and the first call to ngOnChanges.
//Add 'implements OnInit' to the class.
this.userEmail = localStorage.getItem('loggedUser') || "";
}
}

App.component.html

<div class="container">
<div style="text-align:center">
<h1>
{{title}}
</h1>
</div>
<div *ngIf="userEmail">
<p><b>Welcome</b> {{userEmail}} 
(<a [routerLink]="['/login']">Logout</a>) </p>
</div>
<notification></notification>
<router-outlet></router-outlet>
</div>

應用路由元件

app.routing.component.ts

用於配置路由系統。匿名使用者不允許列出,更新和建立路徑,這就是我們為什麼在路徑宣告中新增canActivate屬性的原因。

import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from '../login/login.component';
import { Newcar } from '../newcar/newcar.component';
import { AppComponent } from '../shared/app.component';
import { ListComponent } from '../list/list.component';
import { CanActivateService } from '../../services/canActivate.service';
import { PageNotFoundComponent } from './../pageNotFound/PageNotFound.component';
import { UpdateCarComponent } from './../updatecar/update.component';

const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'list', component: ListComponent, canActivate: [CanActivateService] },
{ path: 'newcar', component: Newcar , canActivate: [CanActivateService]},
{ path: 'updatecar/:id', component: UpdateCarComponent },
// otherwise redirect to home
{ path: '**', component: PageNotFoundComponent }
];

export const routing = RouterModule.forRoot(appRoutes);

App.module.ts

該檔案用於:

  1. 使用路由器模組定義路由:“ RouterModule
  2. 通過單詞key匯入所需的Angular模組:“ imports
  3. 通過單詞key宣告元件:“ declarations
  4. 通過單詞key宣告服務:“ providers
  5. 通過單詞key 指定要包含在index.html檔案中的根元件:“ bootstrap
import { CanActivate } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Component } from '@angular/core';

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { routing } from './components/app.routing/app.routing.component';
import { CanActivateService } from './services/canActivate.service';
import { NotificationComponent } from './components/notification/notification.component';
import { NotifService } from './services/notif-service.service';
import { LoginService } from './services/login-service.service';
import { CarService } from './services/car-service.service';

import { DataTablesModule } from 'angular-datatables';
import { BusyModule, BusyConfig} from 'angular2-busy';

import { LoginComponent } from './components/login/login.component';
import { ListComponent } from './components/list/list.component';
import { Newcar } from './components/newcar/newcar.component';
import { AppComponent } from './components/shared/app.component';
import { UpdateCarComponent } from './components/updatecar/update.component';

import { PageNotFoundComponent } from './components/pageNotFound/PageNotFound.component';

export function getBusyConfig() {
return new BusyConfig({
message: 'Please wait ...',
backdrop: false,
delay: 300,
minDuration: 800,
wrapperClass: 'ng-busy'
});
}
@NgModule({
declarations: [
AppComponent,
ListComponent,
Newcar,
LoginComponent,
UpdateCarComponent,
NotificationComponent,
PageNotFoundComponent
],
imports: [
BrowserModule,
BusyModule.forRoot(getBusyConfig()),
FormsModule,
ReactiveFormsModule,
HttpModule,
routing,
DataTablesModule
],
providers: [CarService, CanActivateService, NotifService, LoginService ],
bootstrap: [AppComponent, NotificationComponent]
})
export class AppModule { }

III)執行專案

要執行前端專案,您應該使用CMD編寫以下命令列,但首先要確保您位於應用程式的根目錄中:

  1. npm start:將TS檔案轉換為JavaScript檔案並啟動應用程式

當您嘗試通過給定的URLhttp// localhost4200)開啟應用程式時,您將獲得以下結果:

注意:從頭開始建立專案時使用上述步驟,如果要使用附加專案,則應首先安​​裝所需的軟體包,為此,需要在每個專案根資料夾中執行以下命令列:

  1. dotnet restore 對於dotnet專案
  2. npm install 用於angular 專案

在執行dotnetcore專案之前:您需要使用SSMS建立資料庫並通過執行以下命令列執行可用的遷移指令碼:“ dotnet ef database update -c ApplicationDbContext -v

參考

  1. Visual Studio Code
  2. DotNetCore 2.0.0
  3. NET Core Command Line Tools
  4. AutoMapper
  5. Swagger API
  6. install nodejs + npm
  7. Angular
  8. Angular-Datatables
  9. Angular2-busy
  10. Angular form validation
  11. Subject pattern

 

原文地址:https://www.codeproject.com/Articles/1210559/ASP-NET-Core-Angular-Build-from-Scratch-a-We