适用Zero版本:ASP.NET Core & Angular 2+ (aspnet-zero-core-3.1.0)。
该版本官方有两个solution文件夹:Angular(前端) 和 aspnet-core(后台服务)。
在开始以下步骤之前需要能够成功发布程序,对于后台服务只要能运行即可,如有报错可根据提示安装需要的插件。Angular 则比较麻烦,装的东西较多,官方建议用yarn,这个要下载,顺藤摸瓜都安装完即可。
我没有改解决方案名称,仍用的默认solution的名称MyCompanyName.AbpZeroTemplate,所以下面有的文件名跟官网的phonebook示例文档有区别。
修改1版将aspnet-core后台服务project和Angular前端project的步骤分类,这样不需要跳跃性的操作,阅读操作效率更高。本文仅谈实操,不详细解释理论性的东西,因为本文观点认为,实操没问题了,初学者的信心就有了,理解仅仅是时间问题。
一、aspnet-core后台Project步骤如下:
1、 src/***.core/***CoreModule.cs文件中临时禁用多租户。
[DependsOn(typeof(AbpZeroCoreModule))]
public class PhoneBookCoreModule : AbpModule
{
public override void PreInitialize()
{
//some other code...
//Enable this line to create a multi-tenant application.
Configuration.MultiTenancy.IsEnabled = false;
//some other code...
}
}
2、src/***.core/Localization/AbpZeroTemplate/AbpZeroTemplate.xml (默认的英文字体)中加入代码。如有对应中文,可在对应的中文文件中加入中文名称。其他语言中没加的都默认用英文的。
<text name="PhoneBook">Phone Book</text>
3、创建实体类person。在.Core内新建文件夹People,然后在People文件夹内新建如下Person.cs类。
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Abp.Domain.Entities.Auditing;
namespace Acme.PhoneBook.People
{
[Table("PbPersons")]
public class Person : FullAuditedEntity
{
public const int MaxNameLength = 32;
public const int MaxSurnameLength = 32;
public const int MaxEmailAddressLength = 255;
[Required]
[MaxLength(MaxNameLength)]
public virtual string Name { get; set; }
[Required]
[MaxLength(MaxSurnameLength)]
public virtual string Surname { get; set; }
[MaxLength(MaxEmailAddressLength)]
public virtual string EmailAddress { get; set; }
}
}
4、在.EntityFramework内的****DbContext.cs文件增加如下黄色标记代码。
public class ******DbContext : AbpZeroDbContext<Tenant, Role, User>
{
public virtual IDbSet<Person> Persons { get; set; }
//...other code
}
5、用EntityFramework的code first迁移功能更新数据库创建PdPersons表。
在windows command prompt 命令行工具定位到.EntityFramework文件夹。输入:“dotnet ef migrations add "Added_Persons_Table”并回车。
这会在Migrations文件夹中增加一个***Added_Persons_Table.cs类。然后在命令行中输入:“dotnet ef database update”命令,即可在数据库中生成新表。
我在执行这步的时候报错了。提示C:\Program Files\dotnet\shared\Microsoft.NETCore.App 目录里没有1.1.0版本的。原来是我没安装command line版的
.net core 1.1 sdk.官网直接下载即可下载地址:https://www.microsoft.com/net/core#windowscmd 。安装好即可成功更新数据库。
6、为新创建的PdPersons表造点初始数据。
在.EntityFramework空间内的Migrations/seed/host 内新建InitialPeopleCreator.cs类
类代码:
using System.Linq;
using Acme.PhoneBook.EntityFramework;
using Acme.PhoneBook.People;
namespace Acme.PhoneBook.Migrations.Seed.Host
{
public class InitialPeopleCreator
{
private readonly PhoneBookDbContext _context;
public InitialPeopleCreator(PhoneBookDbContext context)
{
_context = context;
}
public void Create()
{
var douglas = _context.Persons.FirstOrDefault(p => p.EmailAddress == "douglas.adams@fortytwo.com");
if (douglas == null)
{
_context.Persons.Add(
new Person
{
Name = "Douglas",
Surname = "Adams",
EmailAddress = "douglas.adams@fortytwo.com"
});
}
var asimov = _context.Persons.FirstOrDefault(p => p.EmailAddress == "isaac.asimov@foundation.org");
if (asimov == null)
{
_context.Persons.Add(
new Person
{
Name = "Isaac",
Surname = "Asimov",
EmailAddress = "isaac.asimov@foundation.org"
});
}
}
}
}
7、在.EntityFramework空间内的Migrations/seed/host 内的InitialHostDbBuilder.cs类里新增如下黄色标记代码。
public class InitialHostDbBuilder
{
//existing codes...
public void Create()
{
//existing code...
new InitialPeopleCreator(_context).Create();
_context.SaveChanges();
}
}
然后在命令行中执行代码:“dotnet ef database update ” 即可。查看PdPersons表里已有数据。
8、创建person应用程序服务-----------新建一个接口类。
应用程序服务对应.Application空间,在MyCompanyName.AbpZeroTemplate.Application下面建立文件夹People。
然后在People文件夹中新建IPersonAppService.cs接口类.代码如下:
using Abp.Application.Services; using Abp.Application.Services.Dto; namespace Acme.PhoneBook.People { public interface IPersonAppService : IApplicationService { ListResultDto<PersonListDto> GetPeople(GetPeopleInput input); } }
9、在People文件夹下面再建立一个文件夹Dto,然后在此新建PersonListDto.cs文件。代码如下:
using Abp.Application.Services.Dto; using Abp.AutoMapper; namespace MyCompanyName.AbpZeroTemplate.People.Dto { [AutoMapFrom(typeof(Person))] public class PersonListDto : FullAuditedEntityDto { public string Name { get; set; } public string Surname { get; set; } public string EmailAddress { get; set; } } public class GetPeopleInput { public string Filter { get; set; } } }
10、在Pepole文件夹下再建立一个PersonAppService.cs文件。代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Abp.Application.Services.Dto; using Abp.AutoMapper; using Abp.Domain.Repositories; using Abp.Extensions; using Abp.Linq.Extensions; using MyCompanyName.AbpZeroTemplate.People.Dto; namespace MyCompanyName.AbpZeroTemplate.People { public class PersonAppService:AbpZeroTemplateAppServiceBase,IPersonAppService { private readonly IRepository<Person> _personRepository; public PersonAppService(IRepository<Person> personRepository) { _personRepository = personRepository; } public async Task CreatePerson(CreatePersonInput input) { var person = input.MapTo<Person>(); await _personRepository.InsertAsync(person); } public ListResultDto<PersonListDto> GetPeople(GetPeopleInput input) { var persons = _personRepository .GetAll() .WhereIf( !input.Filter.IsNullOrEmpty(), p => p.Name.Contains(input.Filter) || p.Surname.Contains(input.Filter) || p.EmailAddress.Contains(input.Filter) ) .OrderBy(p => p.Name) .ThenBy(p => p.Surname) .ToList(); return new ListResultDto<PersonListDto>(persons.MapTo<List<PersonListDto>>()); } } }
11、增加Person。
首先再IpersonAppService.cs文件里新增如下代码:
Task CreatePerson(CreatePersonInput input);
然后在Dto文件内新增类CreatePersonInput.cs类,代码如下:
using Abp.AutoMapper; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; namespace MyCompanyName.AbpZeroTemplate.People.Dto { [AutoMapTo(typeof(Person))] public class CreatePersonInput { [Required] [MaxLength(Person.MaxNameLength)] public string Name { get; set; } [Required] [MaxLength(Person.MaxSurnameLength)] public string Surname { get; set; } [EmailAddress] [MaxLength(Person.MaxEmailAddressLength)] public string EmailAddress { get; set; } } }
12、允许创建person对象的授权。
首先加入允许进入phonebook页的授权。在src/***.core/Authorization/AppPermissions.cs文件中加入常量定义:
public const string Pages_Tenant_PhoneBook = "Pages.Tenant.PhoneBook";
在src/MyCompanyName.AbpZeroTemplate.Application/People里的PersonAppService.cs里加入如下黄色标记代码:
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook)] public class PersonAppService : PhoneBookAppServiceBase, IPersonAppService { //... }
在src/***.core/Authorization/AppAuthorizationProvider.cs类中定义许可,代码如下:
var phoneBook = pages.CreateChildPermission(AppPermissions.Pages_Tenant_PhoneBook, L("PhoneBook"), multiTenancySides: MultiTenancySides.Tenant); phoneBook.CreateChildPermission(AppPermissions.Pages_Tenant_PhoneBook_CreatePerson, L("CreateNewPerson"), multiTenancySides: MultiTenancySides.Tenant);
在同目录下的AppPermissions.cs文件中加入常量定义:
public const string Pages_Tenant_PhoneBook_CreatePerson = "Pages.Tenant.PhoneBook.CreatePerson";
13、增加ABP授权属性。
在src/MyCompanyName.AbpZeroTemplate.Application/People里的PersonAppService.cs文件中增加属性标记代码如下
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_CreatePerson)] public async Task CreatePerson(CreatePersonInput input) { //... }
14、删除person实体对象。
在IPersonAppService.cs文件中增加代码:
Task DeletePerson(EntityDto input);
在PersonAppService.cs文件中增加代码:
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_DeletePerson)] public async Task DeletePerson(EntityDto input) { await _personRepository.DeleteAsync(input.Id); }
另外提醒一下, 要跟创建person的权限设置一样在AppPermissions.cs文件中定义常量Pages_Tenant_PhoneBook_DeletePerson,
且在src/***.core/Authorization/AppAuthorizationProvider.cs类中定义许可,代码如下:
phoneBook.DeleteChildPermission(AppPermissions.Pages_Tenant_PhoneBook_DeletePerson, L("DeletePerson"), multiTenancySides: MultiTenancySides.Tenant);
以后这类增删查改不再赘述权限功能的实现。
15、创建Phone 实体。
在src/*******.core下创建Phone.cs类代码如下
[Table("PbPhones")] public class Phone : CreationAuditedEntity<long> { public const int MaxNumberLength = 16; [ForeignKey("PersonId")] public virtual Person Person { get; set; } public virtual int PersonId { get; set; } [Required] public virtual PhoneType Type { get; set; } [Required] [MaxLength(MaxNumberLength)] public virtual string Number { get; set; } }
16、在Person.cs中增加phones字段。
[Table("PbPersons")] public class Person : FullAuditedEntity { //...other properties public virtual ICollection<Phone> Phones { get; set; } }
17、在.core下增加PhoneType.cs类
public enum PhoneType : byte { Mobile, Home, Business }
18、在Entity Framework下的AbpZeroTemplateDbContext.cs增加代码
public virtual IDbSet<Phone> Phones { get; set; }
且用命令行命令:dotnet ef migrations add "Added_Phone" 增加数据库phone表的迁移类。
然后更新数据库,该部分类似操作在上文已有介绍,不赘述。
该部分未完成待续。。。
19、
20、
二、Angular前端Project步骤如下:
1、增加一个新的菜单项。app\shared\layout\side-bar.component.ts (展开side-bar.component.html文件)在dashboard下面加入如下代码
new SideBarMenuItem("PhoneBook", null, "icon-notebook", "/app/main/phonebook")
2、app\main\main-routing.module.ts 文件中加路由
{ path: 'dashboard', component: DashboardComponent, data: { permission: 'Pages.Tenant.Dashboard' } },
{ path: 'phonebook', component: PhoneBookComponent }
此时phoneBookComponent报错,先忽略不管。
3、在app/main中新建一个phonebook文件夹并创建phonebook.component.ts文件,代码如下:
import { Component, Injector } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import { appModuleAnimation } from '@shared/animations/routerTransition';
@Component({
templateUrl: './phonebook.component.html',
animations: [appModuleAnimation()]
})
export class PhoneBookComponent extends AppComponentBase {
constructor(
injector: Injector
) {
super(injector);
}
}
4、解决第2步的报错问题。在main-routing.module.ts加入import代码:
import { PhoneBookComponent } from './phonebook/phonebook.component';
5、在app/main/phonebook中新建 phonebook.component.html文件,代码如下:
<div [@routerTransition]>
<div class="row margin-bottom-5">
<div class="col-xs-12">
<div class="page-head">
<div class="page-title">
<h1>
<span>{{l("PhoneBook")}}</span>
</h1>
</div>
</div>
</div>
</div>
<div class="portlet light margin-bottom-0">
<div class="portlet-body">
<p>PHONE BOOK CONTENT COMES HERE!</p>
</div>
</div>
</div>
6、在app/main/main.module.ts文件中添加下面有黄色标记的代码。
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { ModalModule, TabsModule, TooltipModule } from 'ng2-bootstrap';
import { UtilsModule } from '@shared/utils/utils.module'
import { AppCommonModule } from '@app/shared/common/app-common.module'
import { MainRoutingModule } from './main-routing.module'
import { MainComponent } from './main.component'
import { DashboardComponent } from './dashboard/dashboard.component';
import { PhoneBookComponent } from './phonebook/phonebook.component';
@NgModule({
imports: [
BrowserModule,
CommonModule,
FormsModule,
ModalModule.forRoot(),
TabsModule.forRoot(),
TooltipModule.forRoot(),
UtilsModule,
AppCommonModule,
MainRoutingModule
],
declarations: [
MainComponent,
DashboardComponent,
PhoneBookComponent
]
})
export class MainModule { }
保存后刷新前端页面点击phone book后可出来如下页面。
7、更新服务端后台服务(后台新增功能代码时执行)。 在dos命令行工具里先定位到angular所在目录路径,然后输入命令:nswag/refresh.bat 执行更新命令。
8、获取person列表。
首先在 shared/service-proxies/service-proxy.module.ts文件中的对应地方加入如下代码:
ApiServiceProxies.PersonServiceProxy
修改phonebook.component.ts里的代码如下面的黄色标记处所示:
import { Component, Injector, OnInit } from '@angular/core'; import { AppComponentBase } from '@shared/common/app-component-base'; import { appModuleAnimation } from '@shared/animations/routerTransition'; import { PersonServiceProxy, PersonListDto, ListResultDtoOfPersonListDto } from '@shared/service-proxies/service-proxies'; @Component({ templateUrl: './phonebook.component.html', animations: [appModuleAnimation()] }) export class PhoneBookComponent extends AppComponentBase implements OnInit { people: PersonListDto[] = []; filter: string = ''; constructor( injector: Injector, private _personService: PersonServiceProxy ) { super(injector); } ngOnInit(): void { this.getPeople(); } getPeople(): void { this._personService.getPeople(this.filter).subscribe((result) => { this.people = result.items; }); } }
再替换phonebook.component.html的 <p>PHONE BOOK CONTENT COMES HERE!</p>代码为下面的黄色标记代码:
<div [@routerTransition]> <div class="row margin-bottom-5"> <div class="col-xs-12"> <div class="page-head"> <div class="page-title"> <h1> <span>{{l("PhoneBook")}}</span> </h1> </div> </div> </div> </div> <div class="portlet light margin-bottom-0"> <div class="portlet-body"> <h3>{{l("AllPeople")}}</h3> <div class="list-group"> <a *ngFor="let person of people" href="javascript:;" class="list-group-item"> <h4 class="list-group-item-heading"> {{person.name + ' ' + person.surname}} </h4> <p class="list-group-item-text"> {{person.emailAddress}} </p> </a> </div> </div> </div> </div>
刷新页面
9、新建一个person对象。
首先在phonebook文件夹内创建一个组件create-person-modal.component.ts文件。代码如下:
import { Component, ViewChild, Injector, ElementRef, Output, EventEmitter } from '@angular/core'; import { ModalDirective } from 'ng2-bootstrap'; import { PersonServiceProxy, CreatePersonInput } from '@shared/service-proxies/service-proxies'; import { AppComponentBase } from '@shared/common/app-component-base'; @Component({ selector: 'createPersonModal', templateUrl: './create-person-modal.component.html' }) export class CreatePersonModalComponent extends AppComponentBase { @Output() modalSave: EventEmitter<any> = new EventEmitter<any>(); @ViewChild('modal') modal: ModalDirective; @ViewChild('nameInput') nameInput: ElementRef; person: CreatePersonInput; active: boolean = false; saving: boolean = false; constructor( injector: Injector, private _personService: PersonServiceProxy ) { super(injector); } show(): void { this.active = true; this.person = new CreatePersonInput(); this.modal.show(); } onShown(): void { $(this.nameInput.nativeElement).focus(); } save(): void { this.saving = true; this._personService.createPerson(this.person) .finally(() => this.saving = false) .subscribe(() => { this.notify.info(this.l('SavedSuccessfully')); this.close(); this.modalSave.emit(this.person); }); } close(): void { this.modal.hide(); this.active = false; } }
然后创建同名的html文件 create-person-modal.component.html,代码如下:
<div bsModal #modal="bs-modal" (onShown)="onShown()" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="modal" aria-hidden="true" [config]="{backdrop: 'static'}"> <div class="modal-dialog"> <div class="modal-content"> <form *ngIf="active" #personForm="ngForm" novalidate (ngSubmit)="save()"> <div class="modal-header"> <button type="button" class="close" (click)="close()" aria-label="Close"> <span aria-hidden="true">×</span> </button> <h4 class="modal-title"> <span>{{l("CreateNewPerson")}}</span> </h4> </div> <div class="modal-body"> <div class="form-group form-md-line-input form-md-floating-label no-hint"> <input #nameInput class="form-control" type="text" name="name" [(ngModel)]="person.name" required maxlength="32"> <label>{{l("Name")}}</label> </div> <div class="form-group form-md-line-input form-md-floating-label no-hint"> <input class="form-control" type="email" name="surname" [(ngModel)]="person.surname" required maxlength="32"> <label>{{l("Surname")}}</label> </div> <div class="form-group form-md-line-input form-md-floating-label no-hint"> <input class="form-control" type="email" name="emailAddress" [(ngModel)]="person.emailAddress" required maxlength="255" pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$"> <label>{{l("EmailAddress")}}</label> </div> </div> <div class="modal-footer"> <button [disabled]="saving" type="button" class="btn btn-default" (click)="close()">{{l("Cancel")}}</button> <button type="submit" class="btn btn-primary blue" [disabled]="!personForm.form.valid" [buttonBusy]="saving" [busyText]="l('SavingWithThreeDot')"><i class="fa fa-save"></i> <span>{{l("Save")}}</span></button> </div> </form> </div> </div> </div>
接着在main.module.ts类里加入如下黄色标记代码:
...previous imports import { CreatePersonModalComponent } from './phonebook/create-person-modal.component'; @NgModule({ imports: [ ...existing module imports... ], declarations: [ MainComponent, DashboardComponent, PhoneBookComponent, CreatePersonModalComponent ] }) export class MainModule { }
最后,在phonebook.component.html文件里加入如下黄色标记代码:
<div [@routerTransition]> <div class="row margin-bottom-5"> <div class="col-xs-6"> <div class="page-head"> <div class="page-title"> <h1> <span>{{l("PhoneBook")}}</span> </h1> </div> </div> </div> <div class="col-xs-6 text-right"> <button class="btn btn-primary blue" (click)="createPersonModal.show()"><i class="fa fa-plus"></i> {{l("CreateNewPerson")}}</button> </div> </div> <div class="portlet light margin-bottom-0"> <div class="portlet-body"> <h3>{{l("AllPeople")}}</h3> <div class="list-group"> <a *ngFor="let person of people" href="javascript:;" class="list-group-item"> <h4 class="list-group-item-heading"> {{person.name + ' ' + person.surname}} </h4> <p class="list-group-item-text"> {{person.emailAddress}} </p> </a> </div> </div> </div> <createPersonModal #createPersonModal (modalSave)="getPeople()"></createPersonModal> </div>
10、隐藏未授权的button按钮。
打开phonebook.component.html文件。在button处加入下面的黄色标记代码:
<button *ngIf="isGranted('Pages.Tenant.PhoneBook.CreatePerson')" class="btn btn-primary blue" (click)="createPersonModal.show()"><i class="fa fa-plus"></i> {{l("CreateNewPerson")}}</button>
11、删除peson对象。
修改phonebook.component.html文件,增加如下黄色标记处代码:
... <h3>{{l("AllPeople")}}</h3> <div class="list-group"> <a *ngFor="let person of people" href="javascript:;" class="list-group-item"> <h4 class="list-group-item-heading"> {{person.name + ' ' + person.surname}} <button (click)="deletePerson(person)" title="{{l('Delete')}}" class="btn btn-circle btn-icon-only red delete-person" href="javascript:;"> <i class="icon-trash"></i> </button> </h4> <p class="list-group-item-text"> {{person.emailAddress}} </p> </a> </div> ...
12、使用LESS样式将按钮移至右边。
在phonebook文件夹下创建phonebook.component.less样式文件,代码如下:
.list-group-item-heading { button.delete-person { float: right; } }
在 phonebook.component.ts 文件中引入样式文件,代码如黄色标记:
@Component({ templateUrl: './phonebook.component.html', styleUrls: ['./phonebook.component.less'], animations: [appModuleAnimation()] })
13、在phonebook.component.ts内添加删除功能代码:
deletePerson(person: PersonListDto): void { this.message.confirm( this.l('AreYouSureToDeleteThePerson', person.name), isConfirmed => { if (isConfirmed) { this._personService.deletePerson(person.id).subscribe(() => { this.notify.info(this.l('SuccessfullyDeleted')); _.remove(this.people, person); }); } } ); }
并且在@Component标记前增加引入代码:
import * as _ from 'lodash';
14、
15、
16、
未完待续。。。