DotnetCore 和 Angular2:创建用户管理界面
您将学习如何使用 DotnetCore、Angular2、Typescript 和 Web API 构建管理用户界面。
引言
此演示的主要思想是在 .NET Core Web 应用程序中启动 Angular2 应用程序的开发。
在这篇文章中,我将尝试解释如何
- 在 .NET Core 中创建 Web API
- 使用 Entity Framework 数据库方法进行逆向工程
- 在 Angular2 项目中创建服务
- 在 Angular2 项目中创建组件
- 在 Angular2 项目中创建单独的 HTML 模板
- 使用
ReactiveFormsModule
模块在 Angular2 项目中验证表单
运行此项目后,您将获得以下渲染结果
在本文中,我参考了以下链接来构建我的应用程序
- ASP.NET Core 文档
- Angular2 文档
- Webpack 文档
- EF 命令行工具
- 如何在 Angular2 中实现条件验证
- 在带有 Angular 2 和 Web API 的 ASP.NET Core MVC 中进行 CRUD
背景
为了更好地理解这个演示,您最好具备以下知识
- 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:初始版本