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

DotnetCore 和 Angular2:创建用户管理界面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (13投票s)

2016年12月31日

CPOL

5分钟阅读

viewsIcon

38772

downloadIcon

657

您将学习如何使用 DotnetCore、Angular2、Typescript 和 Web API 构建管理用户界面。

引言

此演示的主要思想是在 .NET Core Web 应用程序中启动 Angular2 应用程序的开发。

在这篇文章中,我将尝试解释如何

  • 在 .NET Core 中创建 Web API
  • 使用 Entity Framework 数据库方法进行逆向工程
  • 在 Angular2 项目中创建服务
  • 在 Angular2 项目中创建组件
  • 在 Angular2 项目中创建单独的 HTML 模板
  • 使用 ReactiveFormsModule 模块在 Angular2 项目中验证表单

运行此项目后,您将获得以下渲染结果

在本文中,我参考了以下链接来构建我的应用程序

背景

为了更好地理解这个演示,您最好具备以下知识

  • C#、JavaScript 和 HTML 编程
  • MVC 架构
  • SQL 语言
  • 数据绑定
  • 实体框架
  • Visual Studio

必备组件

要运行此示例,您需要安装

在阅读本文的其余部分之前,我建议您阅读我的上一篇文章:《在带有 Angular 2 和 Web API 的 ASP.NET Core MVC 中进行 CRUD》,它包含有关 Angular2 和 .NET CORE Web 应用程序的配置、安装和启动的更多详细信息。

Using the Code

在本节中,我将逐步解释如何构建这样一个 Web 应用程序

A) 设置数据库

1) 创建数据库

我使用 SQL Server 2014 在本地托管我的数据库。

我们的数据库将包含以下实体

用户实体:将包含用户列表,由以下组成

  • userId:用户的唯一标识符,自动递增,类型:int
  • userName:是名字,类型:varchar
  • userBirthDate:是出生日期,类型:datetime
  • userBirthLocation:是对 country 实体的引用键,类型:varchar
  • userPicture:它将存储服务器中图像的绝对 URL,类型:varchar

国家实体:将存储不同的国家名称。它由 userBirthLocation 属性使用。

以下步骤将帮助您准备数据库

  • 创建一个名为“cp_UsersDataBase”的数据库
  • 执行以下 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) 使用 Entity Framework 数据库方法

在本节中,您将进行逆向工程以导入现有数据库的模型。为此,您应该遵循以下步骤

  • 导航到应用程序的根文件夹
  • 导入所需的依赖项和工具:为此,您应该通过以下方式配置 project.json

*添加以下依赖项

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

*添加工具

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

*使用 cmd.exe,导航到项目根目录并

dotnet -restore

*启动逆向工程,加载现有数据库的模型

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

B) 设置 Restful WebApi

controller 文件夹中创建一个名为“ServicesController”的 Web API 控制器,它将包含以下操作

  • api/Services/GetCountries:从数据库返回可用国家列表
  • api/Services/GetUsers:从数据库返回用户列表,此服务使用分页系统,它将根据两个信息:“最后加载元素的索引”和“页面大小”仅从剩余数据中返回一组元素
  • api/Services/AddUser:在数据库中创建一个新用户
  • api/Services/DeleteUser:使用唯一标识符从数据库中删除现有用户
[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);
        }
    }

运行 Web API 应用程序后,当您导航到以下 URL:“api/Services/GetCountries”时,您应该会得到以下结果

C) 实现前端部分

最后,前端项目的 treeview 看起来像这样

wwwroot/app 文件夹中,创建以下文件

1) DatePipe.ts

用于创建一个自定义过滤器,将日期转换为特定的 string 格式 (dd/mm/yyyy)。

 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

用于将 json 数据反序列化为用户对象。

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

这是我们的服务类,它实现了所需的方法,例如

  • loadCountries 方法:通过调用现有 Web 服务 [/api/Services/GetCountries] 从外部数据库加载国家列表
  • loadUsers 方法:通过调用现有 Web 服务 [/api/Services/GetUsers] 从外部数据库加载用户列表
  • addUser 方法:调用外部 Web 服务 [/api/Services/AddUser],将新用户添加到数据库中
  • deleteUser 方法:通过调用现有 Web 服务 [/api/Services/DeleteUser] 从数据库中删除特定产品
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

这是“myhw”组件的核心。它表示“apptemplate.component.html”模板的代码。

HwComponent 类是“apptemplate.component.html”的视图模型,由以下方法组成

  • loadMore 方法:通过调用“IndexService 变量”的“loadUsers 方法”来加载更多用户并将其添加到当前列表。它通过在每次调用后从数据库中剩余的数据中检索 6 个条目来实现渐进式加载逻辑。
  • ngOnInit 方法:此方法在组件刷新时调用,它将所有现有国家数据加载到“newUserForm”表单的 select 输入中,并通过调用 loadMore 方法加载“CpUsers”的前 6 个条目。
  • addUser 方法:通过将“newUserForm”表单的输入数据传递给“IndexService 变量”的“Delete 方法”来添加新用户。
  • deleteUser 方法:通过调用“IndexService 变量”的“deleteUser 方法”来删除由其“唯一键”标识的现有用户。
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

这是 HTML 模板,它包含

  • newUserForm (表单):用于创建一个新用户,其特征为:姓名(文本)、出生日期(文本)、出生地点(选择框)、图片(文件)。
  • grid (div):是一个滚动视图,用于显示用户详细信息。
  • loadMoreUsers (按钮):用于将更多条目加载到网格中。

注意registerForm FormGroup ,用于为我们的表单分配验证系统,以确保当所有输入都符合“验证规则”时(参见 HwComponent 类的构造函数方法中 registerForm 的初始化),添加按钮将启用。

 <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) 运行 Web 应用程序

要运行此演示,您应该使用 CMD 编写以下命令行,但首先请确保您位于应用程序的根目录中

  • webpack:将 TS 文件转译为 JavaScript 文件
  • dotnet run:编译项目并运行服务器

参考文献

关注点

我希望您喜欢这篇文章。请尝试下载源代码,并随时留下您的问题和评论。

历史

  • v1 - 2017/01/01:初始版本
© . All rights reserved.