65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Core 2.0 & Angular 4: 从零开始构建车辆管理 Web 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (23投票s)

2017年11月5日

CPOL

9分钟阅读

viewsIcon

96113

downloadIcon

3502

ASP.NET Core 2.0 & Angular 4: 通过本教程,您将学会如何从零开始构建一个车辆管理 Web 应用程序

引言

本文的主要目标是探索 Angular4 和 .NET CORE 框架 的更多功能,例如

  • 使用 WebApi v2 创建 RestFul 服务器
  • 使用 Entity Framework for .NET Core 的 Code First 方法
  • AutoMapper
  • 使用 .NET Core 依赖注入
  • 在 .NET Core 项目中使用 Swagger API
  • 使用 Angular 创建单页应用程序
  • Angular 中创建组件、服务和类
  • Angular 路由系统
  • Angular 表单验证
  • 通过 Subject 模式在组件之间进行通信
  • 使用 Angular-datatables

背景

为了更好地理解本文,建议您对以下内容有扎实的了解

  • C# 编程
  • SQL 语言
  • 实体框架
  • Visual Studio Code

Using the Code

I) 服务器端

a) 先决条件

在开始实现 WebApi 服务项目之前,您可以在下方找到所有有用的链接

b) 项目设置

使用 Visual Studio Code 的集成终端,我们执行以下命令行,在 demobackend 文件夹中创建一个具有单独身份验证系统的新的 DotNet Core MVC 项目。

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

项目结构如下

若要仅使用带身份验证系统的 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();  
        }

删除 Controller 文件夹中所有 controller 类,除了 AccountController 类。

AccountController 替换为以下代码,使其仅包含登录、注销和注册等有用操作,并且仅返回 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

最后,刷新数据库服务器后,您将看到以下结果

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.csConfigureServices 方法中添加以下行,以创建和初始化 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 创建汽车管理 API

创建一个名为 ManageCarController 的新控制器,其中包含每个 CRUD 方法的端点。

  • Get():返回包含所有可用汽车的 HttpResponseMessage
  • Get(int id):返回包含由 id 参数标识的特定 car 对象的 HttpResponseMessage
  • Post([FromBody] CarViewModel _car):它将创建一个新汽车,如果操作已检查,则返回 Http OK 状态码(http 200),否则返回(http 400)。
  • Put(int id, [FromBody] CarViewModel value):它将修改特定汽车(由 id 参数标识)。如果 car 不存在,则返回 Http Not Found 代码,如果更新操作有效,则返回 Http 200 状态码,否则返回 Http Bad Request。
  • 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

为了测试我们 ManageCar WebApi 的每个操作,我们将使用 swagger API。

首先,我们应该安装它

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。

https://:5000/swagger/

最后,您将看到 Swagger UI 界面来测试您的 API。

以下是使用 Account & ManageCar 服务的一些示例。

测试订阅服务

结果

测试身份验证服务

结果

测试创建新汽车

结果

测试获取所有可用汽车

II ) 客户端

a) 先决条件

在开始实现客户端项目之前,您可以在下方找到所有有用的链接。

  • 安装 Visual Studio Code (链接)
  • 安装 nodejs + npm (链接)
  • 设置开发环境 (链接)
  • 安装 Angular-Datatables (链接)
  • 安装 Angular2-busy (链接)
  • Angular 表单验证 (链接)
  • Subject 模式 (链接)

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:用于保存有关已抛出通知的信息。
    • Type:它是一个警报类型,它可以接受值为:‘Error’ 或 ‘Success’。
    • 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

包含管理与服务器会话的方法。

  • loginSubject:它实现了 Subject 模式。用于跟踪会话状态,当用户创建新会话时,他将收到通知(loginSubject.next(1))以在页面顶部显示电子邮件地址,当他注销时,电子邮件将消失。
  • login(currentUser : User):向 Account/Login 服务发送 POST HTTP 请求以尝试与服务器建立新连接。作为参数,它将传递一个 User 对象。
  • logout():发送 POST HTTP 请求到 Account/Logout 服务,尝试结束当前会话。
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 = "https://: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 数据的方法。

  • getCars():向 /api/ManageCar 服务发送 Get HTTP 请求以获取所有可用汽车。
  • getCar(id : number):向 /api/ManageCar 服务发送 Get HTTP 请求以获取特定汽车(由 id 参数标识)。
  • addNewCar(_car : ICar):向 /api/ManageCar 服务发送 POST HTTP 请求,并将 _car 对象作为参数,以在 CAR 表中创建新行。
  • updateCar(_car : ICar):向 /api/ManageCar 服务发送 PUT HTTP 请求,并将 _car 对象作为参数,以更新现有汽车(由 _car.id 标识)。
  • 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 = "https://: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
  • login():它将调用 LoginService 中的 login 方法,通过 model 对象传递 usernamepassword

如果操作成功,它将创建一个新的服务器会话,并使用路由器服务导航到列表页面。否则,它将通过调用 notifService 中的 error 方法来显示错误消息。

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!#$%&'*+\/=?^_`{|}~.-]+@[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
  • init():通过配置 paginTypepageLength 属性来初始化 datatable 对象。然后,它将通过调用 CarService 中的 getCars 方法来加载可用的 car。如果引发异常,将通过调用 NofifService 中的 error 方法在页面顶部显示错误消息。
  • searchCar ():它通过 id 过滤显示的汽车。当用户从下拉菜单中选择汽车名称时调用它。
  • 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 Observable,它将监听来自其他组件(观察者)的所有通知,并显示相应的消息。

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
  • complexForm:用于表单验证,在提交之前验证以下输入:名称、品牌和型号。
  • newCar(model: ICar):将通过调用 CarServiceaddNewCar 操作在数据库中创建新的汽车条目。

如果引发异常,将通过调用 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
  • constructor(…):获取特定汽车的详细信息(使用来自路由 URL 参数的 id)并初始化 FormGroup 对象(complexForm)以填充和验证表单字段。
  • updateCar(model: ICar):将通过调用 CarServiceupdateCar 操作来更新特定汽车。

如果引发异常,将通过调用 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';
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 表单中显示特定汽车的详细信息,该表单允许用户修改某些信息(如名称、品牌、型号)。

App 组件
App.component.ts

用作 app.html 模板的代码隐藏,您将在其中实现

  • constructor:用于初始化我们的主页面(app.html),并声明一个路由器监听器,该监听器根据当前会话值控制用户导航(会话由 cookie 管理)。
  • 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 路由组件
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. 使用 Router Module 定义路由:“RouterModule
  2. 通过关键字“imports”导入所需的 Angular 模块。
  3. 通过关键字“declarations”声明组件。
  4. 通过关键字“providers”声明服务。
  5. 通过关键字“bootstrap”指定要包含在 index.html 文件中的根组件。
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 编写以下命令行,但首先请确保您位于应用程序的根目录。

  • npm start:转译 TS 文件为 JavaScript 文件并启动应用程序。

当您尝试通过提供的 URL(https://:4200)打开应用程序时,您将看到以下结果。

注意:以上步骤用于从头开始项目。如果您想使用附带的项目,首先需要安装所需的包,为此,您需要在每个项目根文件夹中执行以下命令行。

  • dotnet restore 用于 dotnet 项目。
  • npm install 用于 angular 项目。

在运行 dotnetcore 项目之前:您需要使用 SSMS 创建一个数据库,并通过执行以下命令行运行可用的迁移脚本:“dotnet ef database update -c ApplicationDbContext -v”。

参考文献

关注点

希望您喜欢这篇帖子。尝试下载源代码,不要犹豫留下您的问题和评论。

历史

  • v1 2017/11/05:初始版本
© . All rights reserved.