在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
这是一篇学习笔记. angular 5 正式版都快出了, 不过主要是性能升级. 我认为angular 4还是很适合企业的, 就像.net一样. 我用的是windows 10 git for windows: 官网很慢, 所以找一个镜像站下载: https://github.com/waylau/git-for-win, 淘宝镜像的速度还是蛮快的: 安装的时候, 建议选择这个, 会添加很多命令行工具: nodejs: 去官网下载就行: https://nodejs.org/en/ 正常安装即可. npm的版本不要低于5.0吧: angular-cli, 官网: https://github.com/angular/angular-cli npm install -g @angular/cli visual studio code: https://code.visualstudio.com/ and visual studio 2017 of course. 建立angular项目进入命令行在某个地方执行命令: ng new client-panel 这就会建立一个client-panel文件夹, 里面是该项目的文件, 然后它会立即执行npm install命令(这里不要使用淘宝的cnpm进行安装, 有bug), 稍等一会就会结束. 使用vscode打开该目录, 然后在vscode里面打开terminal: terminal默认的可能是powershell, 如果你感觉powershell有点慢的话, 可以换成bash(安装git时候带的)或者windows command line等. 第一次打开terminal的时候, vscode上方会提示你配置terminal, 这时就可以更换默认的terminal. 否则的话, 你可以点击菜单file-reference-settings, 自己选择一个terminal应用: 同样可以安装几个vscode的插件: 然后试运行一下项目, 在terminal执行 ng serve, 如果没问题的话, 大概是这样: 浏览器运行: http://localhost:4200
安装bootstrap4等:安装bootstrap4, tether, jquery等: npm install [email protected] tether jquery --save 安装成功后, 打开 .angular-cli.json, 把相关的css和js添加进去: 然后在运行试试 ng serve, 刷新: 字体已经改变, bootstrap起作用了. 建立Components建立dashboard:terminal执行 ng g component components/dashboard 执行成功后会生成4个文件: 并且会自动在app.module.ts里面声明: 建立其他 components:ng g component components/clients ng g component components/clientDetails ng g component components/addClient ng g component components/editClient ng g component components/navbar ng g component components/sidebar 建立Route路由import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AppComponent } from './app.component'; import { DashboardComponent } from './components/dashboard/dashboard.component'; import { ClientsComponent } from './components/clients/clients.component'; import { ClientDetailsComponent } from './components/client-details/client-details.component'; import { AddClientComponent } from './components/add-client/add-client.component'; import { EditClientComponent } from './components/edit-client/edit-client.component'; import { NavbarComponent } from './components/navbar/navbar.component'; import { SidebarComponent } from './components/sidebar/sidebar.component'; import { LoginComponent } from './components/login/login.component'; import { RegisterComponent } from './components/register/register.component'; import { SettingsComponent } from './components/settings/settings.component'; import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; const appRoutes: Routes = [ { path: '', component: DashboardComponent }, { path: 'register', component: RegisterComponent }, { path: 'login', component: LoginComponent } ]; @NgModule({ declarations: [ AppComponent, DashboardComponent, ClientsComponent, ClientDetailsComponent, AddClientComponent, EditClientComponent, NavbarComponent, SidebarComponent, LoginComponent, RegisterComponent, SettingsComponent, PageNotFoundComponent ], imports: [ BrowserModule, RouterModule.forRoot(appRoutes) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } 添加router-outlet:打开app.component.html, 清空内容, 添加一个div(可以输入div.container然后按tab健): <div class="container"> <router-outlet></router-outlet> </div> 现在刷新浏览器, 大约这样: 添加navbar:修改navbar.component.html: <nav class="navbar navbar-expand-md navbar-light bg-light"> <div class="container"> <a class="navbar-brand" href="#">Client Panel</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarsExampleDefault"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="#" routerLink="/">Dashboard </a> </li> </ul> <ul class="navbar-nav ml-auto"> <li class="nav-item"> <a class="nav-link" href="#" routerLink="/register">Register </a> </li> <li class="nav-item"> <a class="nav-link" href="#" routerLink="/login">Login </a> </li> </ul> </div> </div> </nav>
修改app.component.html: <app-navbar></app-navbar> <div class="container"> <router-outlet></router-outlet> </div> 运行: 建立Service建立一个client.service: ng g service services/client 然后在app.module.ts添加引用: // Services Imports import { ClientService } from "./services/client.service"; 并添加在providers里: providers: [
ClientService
],
前端先暂时到这, 现在开始搞后端 web api. 建立asp.net core 2.0 的 Web api项目web api项目源码: https://github.com/solenovex/asp.net-core-2.0-web-api-boilerplate 项目列表如图: AspNetIdentityAuthorizationServer是一个单独的authorization server, 这里暂时还没用到, 它的端口是5000, 默认不启动. CoreApi.Infrastructure 里面有一些基类和接口, 还放了一个公共的工具类等. CoreApi.Models就是 models/entities CoreApi.DataContext 里面就是DbContext相关的 CoreApi.Repositories 里面是Repositories CoreApi.Services 里面就是各种services CoreApi.ViewModels 里面就是各种ViewModels或者叫Dtos CoreApi.Web是web启动项目. SharedSettings是横跨authorization server和 web api的一些公共设置. 上面说的这些都没什么用, 下面开始建立Client的api. 建立Client Model(或者叫Entity)在CoreApi.Models建立文件夹Angular, 然后建立Client.cs: using CoreApi.Infrastructure.Features.Common; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace CoreApi.Models.Angular { public class Client : EntityBase { public decimal Balance { get; set; } public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Phone { get; set; } } public class ClientConfiguration : EntityBaseConfiguration<Client> { public override void ConfigureDerived(EntityTypeBuilder<Client> builder) { builder.Property(x => x.Balance).HasColumnType("decimal(18,2)"); builder.Property(x => x.Email).IsRequired().HasMaxLength(100); builder.Property(x => x.FirstName).IsRequired().HasMaxLength(50); builder.Property(x => x.LastName).IsRequired().HasMaxLength(50); builder.Property(x => x.Phone).HasMaxLength(50); } } } 其中父类EntityBase里面含有一些通用属性,Id, CreateUser, UpdateUser, CreateTime, UpdateTime, LastAction, 这些是我公司做项目必须的, 你们随意. 下面ClientConfiguration是针对Client的fluent api配置类. 他的父类EntityBaseConfiguration实现了EF的IEntityTypeConfiguration接口, 并在父类里面针对EntityBase那些属性使用fluent api做了限制: namespace CoreApi.Infrastructure.Features.Common { public abstract class EntityBaseConfiguration<T> : IEntityTypeConfiguration<T> where T : EntityBase { public virtual void Configure(EntityTypeBuilder<T> builder) { builder.HasKey(e => e.Id); builder.Property(x => x.CreateTime).IsRequired(); builder.Property(x => x.UpdateTime).IsRequired(); builder.Property(x => x.CreateUser).IsRequired().HasMaxLength(50); builder.Property(x => x.UpdateUser).IsRequired().HasMaxLength(50); builder.Property(x => x.LastAction).IsRequired().HasMaxLength(50); ConfigureDerived(builder); } public abstract void ConfigureDerived(EntityTypeBuilder<T> b); } } 弄完Model和它的配置之后, 就添加到DbContext里面. 打开CoreApi.DataContext的CoreContext, 添加Model和配置: protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.HasDefaultSchema(AppSettings.DefaultSchema); modelBuilder.ApplyConfiguration(new UploadedFileConfiguration()); modelBuilder.ApplyConfiguration(new ClientConfiguration()); } public DbSet<UploadedFile> UploadedFiles { get; set; } public DbSet<Client> Clients { get; set; } 然后建立ClientRepository在CoreApi.Repositories里面建立Angular目录, 建立ClientRepository.cs: namespace CoreApi.Repositories.Angular { public interface IClientRepository : IEntityBaseRepository<Client> { } public class ClientRepository : EntityBaseRepository<Client>, IClientRepository { public ClientRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { } } } 图省事, 我把repository和它的interface放在一个文件了. IEntityBaseRepository<T>定义了一些常用的方法: namespace CoreApi.DataContext.Infrastructure { public interface IEntityBaseRepository<T> where T : class, IEntityBase, new() { IQueryable<T> All { get; } IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties); int Count(); Task<int> CountAsync(); T GetSingle(int id); Task<T> GetSingleAsync(int id); T GetSingle(Expression<Func<T, bool>> predicate); Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate); T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties); Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties); IQueryable<T> FindBy(Expression<Func<T, bool>> predicate); void Add(T entity); void Update(T entity); void Delete(T entity); void DeleteWhere(Expression<Func<T, bool>> predicate); void AddRange(IEnumerable<T> entities); void DeleteRange(IEnumerable<T> entities); void Attach(T entity); void AttachRange(IEnumerable<T> entities); void Detach(T entity); void DetachRange(IEnumerable<T> entities); void AttachAsModified(T entity); } } EntityBaseRepository<T>是它的实现: namespace CoreApi.DataContext.Infrastructure { public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new() { #region Properties protected CoreContext Context { get; } public EntityBaseRepository(IUnitOfWork unitOfWork) { Context = unitOfWork as CoreContext; } #endregion public virtual IQueryable<T> All => Context.Set<T>(); public virtual IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties) { IQueryable<T> query = Context.Set<T>(); foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return query; } public virtual int Count() { return Context.Set<T>().Count(); } public async Task<int> CountAsync() { return await Context.Set<T>().CountAsync(); } public T GetSingle(int id) { return Context.Set<T>().FirstOrDefault(x => x.Id == id); } public async Task<T> GetSingleAsync(int id) { return await Context.Set<T>().FirstOrDefaultAsync(x => x.Id == id); } public T GetSingle(Expression<Func<T, bool>> predicate) { return Context.Set<T>().FirstOrDefault(predicate); } public async Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate) { return await Context.Set<T>().FirstOrDefaultAsync(predicate); } public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties) { IQueryable<T> query = Context.Set<T>(); foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return query.Where(predicate).FirstOrDefault(); } public async Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties) { IQueryable<T> query = Context.Set<T>(); foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return await query.Where(predicate).FirstOrDefaultAsync(); } public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate) { return Context.Set<T>().Where(predicate); } public virtual void Add(T entity) { Context.Set<T>().Add(entity); } public virtual void Update(T entity) { EntityEntry<T> dbEntityEntry = Context.Entry(entity); dbEntityEntry.State = EntityState.Modified; dbEntityEntry.Property(x => x.Id).IsModified = false; dbEntityEntry.Property(x => x.CreateUser).IsModified = false; dbEntityEntry.Property(x => x.CreateTime).IsModified = false; } public virtual void Delete(T entity) { Context.Set<T>().Remove(entity); } public virtual void AddRange(IEnumerable<T> entities) { Context.Set<T>().AddRange(entities); } public virtual void DeleteRange(IEnumerable<T> entities) { foreach (var entity in entities) { Context.Set<T>().Remove(entity); } } public virtual void DeleteWhere(Expression<Func<T, bool>> predicate) { IEnumerable<T> entities = Context.Set<T>().Where(predicate); foreach (var entity in entities) { Context.Entry<T>(entity).State = EntityState.Deleted; } } public void Attach(T entity) { Context.Set<T>().Attach(entity); } public void AttachRange(IEnumerable<T> entities) { foreach (var entity in entities) { Attach(entity); } } public void Detach(T entity) { Context.Entry<T>(entity).State = EntityState.Detached; } public void DetachRange(IEnumerable<T> entities) { foreach (var entity in entities) { Detach(entity); } } public void AttachAsModified(T entity) { Attach(entity); Update(entity); } } } 建立Client的ViewModels在CoreApi.ViewModels建立Angular文件夹, 分别针对查询, 新增, 修改建立3个ViewModel(Dto): ClientViewModel: namespace CoreApi.ViewModels.Angular { public class ClientViewModel : EntityBase { public decimal Balance { get; set; } public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Phone { get; set; } } } ClientCreationViewModel: namespace CoreApi.ViewModels.Angular { public class ClientCreationViewModel { public decimal Balance { get; set; } [Required] [MaxLength(100)] public string Email { get; set; } [Required] [MaxLength(50)] public string FirstName { get; set; } [Required] [MaxLength(50)] public string LastName { get; set; } [Required] [MaxLength(50)] public string Phone { get; set; } } } ClientModificationViewModel: namespace CoreApi.ViewModels.Angular { public class ClientModificationViewModel { public decimal Balance { get; set; } [Required] [MaxLength(100)] public string Email { get; set; } [Required] [MaxLength(50)] public string FirstName { get; set; } [Required] [MaxLength(50)] public string LastName { get; set; } [Required] [MaxLength(50)] public string Phone { get; set; } } } 配置AutoMapper针对Client和它的Viewmodels, 分别从两个方向进行配置: DomainToViewModelMappingProfile: namespace CoreApi.Web.MyConfigurations { public class DomainToViewModelMappingProfile : Profile { public override string ProfileName => "DomainToViewModelMappings"; public DomainToViewModelMappingProfile() { CreateMap<UploadedFile, UploadedFileViewModel>(); CreateMap<Client, ClientViewModel>(); CreateMap<Client, ClientModificationViewModel>(); } } } ViewModelToDomainMappingProfile: namespace CoreApi.Web.MyConfigurations { public class ViewModelToDomainMappingProfile : Profile { public override string ProfileName => "ViewModelToDomainMappings"; public ViewModelToDomainMappingProfile() { CreateMap<UploadedFileViewModel, UploadedFile>(); CreateMap<ClientViewModel, Client>(); CreateMap<ClientCreationViewModel, Client>(); CreateMap<ClientModificationViewModel, Client>(); } } } 注册Repository的DI:在web项目的StartUp.cs的ConfigureServices里面为ClientRepository注册DI: services.AddScoped<IClientRepository, ClientRepository>(); 建立Controller在controllers目录建立Angular/ClientController.cs: namespace CoreApi.Web.Controllers.Angular { [Route("api/[controller]")] public class ClientController : BaseController<ClientController> { private readonly IClientRepository _clientRepository; public ClientController(ICoreService<ClientController> coreService, IClientRepository clientRepository) : base(coreService) { _clientRepository = clientRepository; } [HttpGet] public async Task<IActionResult> GetAll() { var items = await _clientRepository.All.ToListAsync(); var results = Mapper.Map<IEnumerable<ClientViewModel>>(items); return Ok(results); } [HttpGet] [Route("{id}", Name = "GetClient")] public async Task<IActionResult> Get(int id) { var item = await _clientRepository.GetSingleAsync(id); if (item == null) { return NotFound(); } var result = Mapper.Map<ClientViewModel>(item); return Ok(result); } [HttpPost] public async Task<IActionResult> Post([FromBody] ClientCreationViewModel clientVm) { if (clientVm == null) { return BadRequest(); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var newItem = Mapper.Map<Client>(clientVm); newItem.SetCreation(UserName); _clientRepository.Add(newItem); if (!await UnitOfWork.SaveAsync()) { return StatusCode(500, "保存客户时出错"); } var vm = Mapper.Map<ClientViewModel>(newItem); return CreatedAtRoute("GetClient", new { id = vm.Id }, vm); } [HttpPut("{id}")] public async Task<IActionResult> Put(int id, [FromBody] ClientModificationViewModel clientVm) { if (clientVm == null) { return BadRequest(); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var dbItem = await _clientRepository.GetSingleAsync(id); if (dbItem == null) { return NotFound(); } Mapper.Map(clientVm, dbItem); dbItem.SetModification(UserName); _clientRepository.Update(dbItem); if (!await UnitOfWork.SaveAsync()) { return StatusCode(500, "保存客户时出错"); } return NoContent(); } [HttpPatch("{id}")] public async Task<IActionResult> Patch(int id, [FromBody] JsonPatchDocument<ClientModificationViewModel> patchDoc) { if (patchDoc == null) { return BadRequest(); } var dbItem = await _clientRepository.GetSingleAsync(id); if (dbItem == null) { return NotFound(); } var toPatchVm = Mapper.Map<ClientModificationViewModel>(dbItem); patchDoc.ApplyTo(toPatchVm, ModelState); TryValidateModel(toPatchVm); if (!ModelState.IsValid) { return BadRequest(ModelState); } Mapper.Map(toPatchVm, dbItem); if (!await UnitOfWork.SaveAsync()) { return StatusCode(500, "更新的时候出错"); } return NoContent(); } [HttpDelete("{id}")] public async Task<IActionResult> Delete(int id) { var model = await |
请发表评论