介绍 在Master Chef(第1部分)和Master Chef(第2部分)中,我介绍了如何使用ASP。Net Core和Angular JS。在Master Chef(第3部分)中,我开始介绍如何创建ASP。NET Core和Angular 2应用程序。在这篇文章中,我将继续讨论如何使用ASP。NET Core MVC, Entity Framework Core和Angular 2来实现一个CRUD SPA(单页应用)。 服务器数据模型 创建、读取、更新和删除(CRUD的首字母缩写)是持久性存储的四个基本功能。 我们需要首先在我们的repository类中实现数据库级的CRUD。添加一个基本实体类。 隐藏,复制Code

    public class Entity
{
public virtual Guid Id { get; set; } public virtual Guid? ParentId { get; set; }
}

然后让Recipe、RecipeStep和RecipeItem继承实体类,并使用这些通用名称Id和ParentId替换相应的键和引用。 隐藏,收缩,复制Code

    public partial class Recipe : Entity
{
public Recipe()
{
RecipeSteps = new HashSet<RecipeStep>();
} public string Name { get; set; }
public DateTime ModifyDate { get; set; }
public string Comments { get; set; } public virtual ICollection<RecipeStep> RecipeSteps { get; set; }
} public partial class RecipeStep : Entity
{
public RecipeStep()
{
RecipeItems = new HashSet<RecipeItem>();
} public int StepNo { get; set; }
public string Instructions { get; set; } public virtual ICollection<RecipeItem> RecipeItems { get; set; }
[JsonIgnore]
public Recipe Recipe { get; set; }
}
public partial class RecipeItem : Entity
{
public string Name { get; set; }
public decimal Quantity { get; set; }
public string MeasurementUnit { get; set; }
[JsonIgnore]
public RecipeStep RecipeStep { get; set; }
}

现在我们需要更改DbContext类以应用Id和ParentId。 隐藏,复制Code

modelBuilder.Entity<RecipeItem>(entity =>
{
entity.HasKey(e => e.Id)
.HasName("PK_RecipeItems"); entity.Property(e => e.Id).ValueGeneratedNever().HasColumnName("ItemId");
entity.Property(e => e.ParentId).HasColumnName("RecipeStepId");
entity.Property(e => e.MeasurementUnit)
.IsRequired()
.HasColumnType("varchar(20)"); entity.Property(e => e.Name)
.IsRequired()
.HasColumnType("varchar(255)"); entity.Property(e => e.Quantity).HasColumnType("decimal"); entity.HasOne(d => d.RecipeStep)
.WithMany(p => p.RecipeItems)
.HasForeignKey(d=>d.ParentId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_RecipeItems_RecipeSteps");
});

对于RecipeItem实体,我们使用“HasColumnName”来告诉模型构建器映射,“Id”映射到“ItemId”和“ParentId”映射到“RecipeStepId”。然后在引用定义中,将HasForeignKey(d=>d. recipestepid)改为HasForeignKey(d=>d. parentid)。 对于RecipeStep也有同样的解决方法: 隐藏,复制Code

modelBuilder.Entity<RecipeStep>(entity =>
{
entity.HasKey(e => e.Id)
.HasName("PK_RecipeSteps"); entity.Property(e => e.Id).ValueGeneratedNever().HasColumnName("RecipeStepId");
entity.Property(e => e.ParentId).HasColumnName("RecipeId");
entity.Property(e => e.Instructions).HasColumnType("text"); entity.HasOne(d => d.Recipe)
.WithMany(p => p.RecipeSteps)
.HasForeignKey(d => d.ParentId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_RecipeSteps_Recipes");
});

DeleteBehavior.Cascade是什么?这是在删除父对象时删除子对象的选项。对于我们的示例,删除一个配方将删除该配方的所有配方步骤和配方项,删除一个步骤将删除该步骤的所有项。 Recipe类没有ParentId。所以我们需要告诉model builder忽略映射。 隐藏,复制Code

modelBuilder.Entity<Recipe>(entity =>
{
entity.HasKey(e => e.Id)
.HasName("PK_Recipes");
entity.Ignore(e => e.ParentId);
entity.Property(e => e.Id).ValueGeneratedNever().HasColumnName("RecipeId"); entity.Property(e => e.Comments).HasColumnType("text"); entity.Property(e => e.ModifyDate).HasColumnType("date"); entity.Property(e => e.Name)
.IsRequired()
.HasColumnType("varchar(255)");
});

在应用这些更改之后,现在我们可以在存储库类中使用泛型来实现、创建、读取、更新和删除Recipe、RecipeStep和RecipeItem的功能。 隐藏,收缩,复制Code

public T GetEntity<T>(Guid id) where T : Entity
{
try
{
return _dbContext.Find<T>(id);
}
catch (Exception ex)
{
throw ex;
}
} public T AddEntity<T>(T entity) where T : Entity
{
_dbContext.Add<T>(entity);
_dbContext.SaveChanges();
var result = GetEntity<T>(entity.Id);
return result;
} public void UpdateEntity<T>(T entity) where T : Entity
{
_dbContext.Update<T>(entity);
_dbContext.SaveChanges();
} public void DeleteEntity<T>(Guid id) where T : Entity
{
var entity = GetEntity<T>(id);
_dbContext.Remove<T>(entity);
_dbContext.SaveChanges();
}

Web API控制器 在RecipesController类中,我们设置了处理基本CRUD请求的函数。这里有一个GET请求,要求所有食谱。这里还有另一个获取id的Get函数,因此用户可以请求返回特定的菜谱。我们这里还有更多的功能——允许用户创建一个新的食谱。我们还可以更新现有的食谱。最后,删除——可以删除特定的菜谱。 隐藏,收缩,复制Code

[HttpGet("{id}")]
public IActionResult Get(Guid id)
{
var recipe = _repository.GetEntity<Recipe>(id);
if (recipe != null)
return new ObjectResult(recipe);
else
return new NotFoundResult();
} [HttpPost]
public IActionResult Post([FromBody]Recipe recipe)
{
if (recipe.Id == Guid.Empty)
{
recipe.Id = Guid.NewGuid();
recipe.ModifyDate = DateTime.Now;
return new ObjectResult(_repository.AddEntity<Recipe>(recipe));
}
else
{
var existingOne = _repository.GetEntity<Recipe>(recipe.Id);
existingOne.Name = recipe.Name;
existingOne.Comments = recipe.Comments;
existingOne.ModifyDate = DateTime.Now;
_repository.UpdateEntity<Recipe>(existingOne);
return new ObjectResult(existingOne);
}
} [HttpPut("{id}")]
public IActionResult Put(Guid id, [FromBody]Recipe recipe)
{
var existingOne = _repository.GetEntity<Recipe>(recipe.Id);
existingOne.Name = recipe.Name;
existingOne.Comments = recipe.Comments;
_repository.UpdateEntity<Recipe>(existingOne);
return new ObjectResult(existingOne);
} [HttpDelete("{id}")]
public IActionResult Delete(Guid id)
{
_repository.DeleteEntity<Recipe>(id);
return new StatusCodeResult(200);
}

那么RecipeStep和RecipeItem呢?我们能把不同的HttpGet, HttpPost和HttpDelete放到一个API控制器中吗? 路由是Web API将URI匹配到操作的方式。Web API 2支持一种新的路由类型,称为属性路由。顾名思义,属性路由使用属性来定义路由。属性路由使您可以对web API中的uri进行更多的控制。例如,您可以轻松地创建描述资源层次结构的uri。 现在我们使用属性路由在一个API控制器中定义多个HTTPGet、HTTPPost和HTTPDelete。 隐藏,收缩,复制Code

//GET api/recipes/step/:id
[HttpGet]
[Route("step/{id}")]
public IActionResult GetStep(Guid id)
{
var recipeStep = _repository.GetEntity<RecipeStep>(id);
if (recipeStep != null)
return new ObjectResult(recipeStep);
else
return new NotFoundResult(); } //POST api/recipes/step
[HttpPost]
[Route("step")]
public IActionResult UpdateStep([FromBody]RecipeStep recipeStep)
{
if (recipeStep.Id == Guid.Empty)
{
recipeStep.Id = Guid.NewGuid();
return new ObjectResult(_repository.AddEntity<RecipeStep>(recipeStep));
}
else
{
var existingOne = _repository.GetEntity<RecipeStep>(recipeStep.Id);
existingOne.StepNo = recipeStep.StepNo;
existingOne.Instructions = recipeStep.Instructions;
_repository.UpdateEntity<RecipeStep>(existingOne);
return new ObjectResult(existingOne);
}
} //DELETE api/recipes/step/:id
[HttpDelete]
[Route("step/{id}")]
public IActionResult DeleteStep(Guid id)
{
_repository.DeleteEntity<RecipeStep>(id);
return new StatusCodeResult(200);
} // GET api/recipes/item/:id
[HttpGet]
[Route("item/{id}")]
public IActionResult GetItem(Guid id)
{
var recipeItem = _repository.GetEntity<RecipeItem>(id);
if (recipeItem != null)
return new ObjectResult(recipeItem);
else
return new NotFoundResult(); } //POST api/recipes/item
[HttpPost]
[Route("item")]
public IActionResult UpdateItem([FromBody]RecipeItem recipeItem)
{
if (recipeItem.Id == Guid.Empty)
{
recipeItem.Id = Guid.NewGuid();
if (recipeItem.MeasurementUnit == null)
recipeItem.MeasurementUnit = "";
return new ObjectResult(_repository.AddEntity<RecipeItem>(recipeItem));
}
else
{
var existingOne = _repository.GetEntity<RecipeItem>(recipeItem.Id);
existingOne.Name = recipeItem.Name;
existingOne.Quantity = recipeItem.Quantity;
existingOne.MeasurementUnit = recipeItem.MeasurementUnit;
_repository.UpdateEntity<RecipeItem>(existingOne);
return new ObjectResult(existingOne);
}
} //DELETE api/recipes/item/:id
[HttpDelete]
[Route("item/{id}")]
public IActionResult DeleteItem(Guid id)
{
_repository.DeleteEntity<RecipeItem>(id);
return new StatusCodeResult(200);
}

客户端视图模型 在上一篇文章中,我们创建了一个菜谱视图模型。现在我们继续创建recipestep和recipeitem。 右键点击“viewmodels”添加新的类型脚本文件。它被命名为“recipeStep”,这是一个我们用来在视图中显示的配方步骤视图模型。 隐藏,复制Code

export class RecipeStep {
public parentId: string;
public id: string;
public stepNo: number;
public instructions: string;
constructor() { }
}

右键单击“viewmodels”添加另一个类型脚本文件。它名为“recipeItem”,这是一个用于在视图中显示的菜谱项视图模型。 客户端服务 在我们的客户端服务“app.service”。我们需要添加更多的方法来实现CRUD功能。 首先导入客户端视图模型类。 隐藏,复制Code

import { Recipe } from "../viewmodels/recipe";
import { RecipeStep } from "../viewmodels/recipeStep";
import { RecipeItem } from "../viewmodels/recipeItem";
import { Observable } from "rxjs/Observable";

请注意,我们在web API控制器中实现的URL对于recipe、step和item是不同的。 在服务类中,我们定义了三个常量URL字符串。 隐藏,复制Code

    //URL to web api
private recipeUrl = 'api/recipes/';
private stepUrl = 'api/recipes/step/';
private itemUrl = 'api/recipes/item/';

获取、更新和删除菜谱方法: 隐藏,复制Code

    getRecipe(id: string) {
if (id == null) throw new Error("id is required.");
var url = this.recipeUrl + id;
return this.http.get(url)
.map(response => <Recipe>response.json())
.catch(this.handleError);
} saveRecipe(recipe: Recipe) {
if (recipe == null) throw new Error("recipe is required.");
var url = this.recipeUrl;
return this.http.post(url, recipe)
.map(response => <Recipe>response.json())
.catch(this.handleError);
} deleteRecipe(id:string) {
if (id == null) throw new Error("id is required.");
var url = this.recipeUrl + id;
return this.http.delete(url)
.catch(this.handleError);
}

获取、更新和删除配方步骤方法: 隐藏,收缩,复制Code

    getStep(id: string) {
if (id == null) throw new Error("id is required.");
var url = this.stepUrl + id;
return this.http.get(url)
.map(response => <RecipeStep>response.json())
.catch(this.handleError);
} saveStep(step: RecipeStep) {
if (step == null) throw new Error("recipe step is required.");
var url = this.stepUrl;
return this.http.post(url, step)
.map(response => <RecipeStep>response.json())
.catch(this.handleError);
} deleteStep(id: string) {
if (id == null) throw new Error("id is required.");
var url = this.stepUrl + id;
return this.http.delete(url)
.catch(this.handleError);
} Get, update and delete recipe item methods:
getItem(id: string) {
if (id == null) throw new Error("id is required.");
var url = this.itemUrl + id;
return this.http.get(url)
.map(response => <RecipeItem>response.json())
.catch(this.handleError);
} saveItem(item: RecipeItem) {
if (item == null) throw new Error("recipe item is required.");
var url = this.itemUrl;
return this.http.post(url, item)
.map(response => <RecipeItem>response.json())
.catch(this.handleError);
} deleteItem(id: string) {
if (id == null) throw new Error("id is required.");
var url = this.itemUrl + id;
return this.http.delete(url)
.catch(this.handleError);
}

客户端路由 在ASP中使用MVC。当你指定一个特定的URL时,当你期望你的代码击中什么控制器时,你使用路由。我们还可以选择指定要传递到控制器方法中的参数。这就是服务器端路由。 在SPA中,客户端路由的作用基本相同。唯一的区别是,我们不必调用服务器。这使得我们所有的“页面”都是虚拟的。而不是要求我们的访问者总是从我们的主页开始,并浏览到我们的网站的其余部分;而不是为我们网站的每个页面在服务器上创建一个单独的页面;我们可以预先加载所有站点,用户可以导航到他们想要的页面。它们甚至可以直接链接到该页面,而客户端将适当地处理页面的显示。 通常,在实现了所有公共代码之后,路由会在应用程序的顶部启用。所以,在您希望路由生效的位置,添加以下标记: & lt; router-outlet> & lt; / router-outlet> 应用程序组件 现在我们改变我们的应用程序组件,使客户端路由实现单页应用程序。 隐藏,复制Code

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Recipe } from "./viewmodels/recipe";
import { AppService } from "./services/app.service"; @Component({
selector: 'masterchef2',
template: `
<h1>{{title}}</h1>
<router-outlet></router-outlet>
`
}) export class AppComponent {
title = "Master Chef Recipes";
}

现在可以看到App组件非常简单。只显示标题。& lt; router-outlet> & lt; / router-outlet>将根据路径带来不同的模板。 菜谱列表组件 在上一篇文章中,我们将食谱列表放入app组件中。因为我们需要实现更复杂的功能,所以我把它从app component中取出来。 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“recipe-list.component.ts”。 隐藏,收缩,复制Code

import { Recipe } from "../viewmodels/recipe";
import { AppService } from "../services/app.service"; @Component({
selector: 'recipe-list',
templateUrl: '../partials/recipes.html'
}) export class RecipeListComponent implements OnInit { items: Recipe[];
errorMessage: string; constructor(private appService: AppService) {
//called first time before the ngOnInit()
} ngOnInit() {
//called after the constructor and called after the first ngOnChanges()
var service = this.appService.getAllRecipes();
service.subscribe(
items => {
this.items = items;
},
error => this.errorMessage = <any>error
);
} public Expand(recipe:Recipe) {
recipe.show = !recipe.show;
} }

请注意Expand方法中的更改。现在“show”属性已经不在组件级别了。它被移动到配方视图模型。那是因为我想控制每一个食谱,而不是所有的食谱。 详细配方成分 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“recipe-detail.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { Recipe } from "../viewmodels/recipe";
import { AppService } from "../services/app.service"; @Component({
selector: 'recipe-detail',
templateUrl: '../partials/edit.html'
}) export class RecipeDetailComponent implements OnInit {
item: Recipe;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var id = params['id'];
this.AppService.getRecipe(id).subscribe(item => this.item = item);
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public editRecipe() {
this.AppService.saveRecipe(this.item).subscribe(
item => { this.item = item; this.router.navigate(['/recipes']); },
error => console.log(error)
)
}
}

在这个类中,我们首先调用getRecipe服务函数来获取菜谱信息,然后调用saveRecipe服务函数来更新菜谱。 配方新组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“reci- new.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { Recipe } from "../viewmodels/recipe";
import { AppService } from "../services/app.service"; @Component({
selector: 'recipe-new',
templateUrl: '../partials/add.html'
}) export class RecipeNewComponent implements OnInit {
item: Recipe;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.item = new Recipe();
} ngOnDestroy() {
} public addRecipe() {
this.AppService.saveRecipe(this.item).subscribe(
item => { this.item = item; this.router.navigate(['/recipes']); },
error => console.log(error)
)
} }

在这个类中,我们首先创建一个新菜谱,然后调用saveRecipe服务函数来添加菜谱。 配方删除组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“reci- delete.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { Recipe } from "../viewmodels/recipe";
import { AppService } from "../services/app.service"; @Component({
selector: 'recipe-delete',
templateUrl: '../partials/delete.html'
}) export class RecipeDeleteComponent implements OnInit {
item: Recipe;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var id = params['id'];
this.AppService.getRecipe(id).subscribe(item => this.item = item);
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public deleteRecipe() {
this.AppService.deleteRecipe(this.item.id).subscribe(
() => this.router.navigate(['/recipes']),
error => console.log(error)
)
} }

在这个类中,我们首先调用getRecipe服务函数来获取菜谱信息,然后调用deleteRecipe服务函数来删除菜谱。 步骤详细的组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“step-detail.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { RecipeStep } from "../viewmodels/recipestep";
import { AppService } from "../services/app.service"; @Component({
selector: 'step-detail',
templateUrl: '../partials/editStep.html'
}) export class StepDetailComponent implements OnInit {
item: RecipeStep;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var id = params['id'];
this.AppService.getStep(id).subscribe(item => this.item = item);
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public editRecipeStep() {
this.AppService.saveStep(this.item).subscribe(
item => { this.item = item; this.router.navigate(['/recipes']); },
error => console.log(error)
)
} }

在这个类中,我们首先调用getStep服务函数来获取配方步骤信息,然后调用saveStep服务函数来更新配方步骤。 一步新组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“step-new.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { RecipeStep } from "../viewmodels/recipeStep";
import { AppService } from "../services/app.service"; @Component({
selector: 'step-new',
templateUrl: '../partials/addStep.html'
}) export class StepNewComponent implements OnInit {
item: RecipeStep;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var parentId = params['id'];
this.item = new RecipeStep();
this.item.parentId = parentId;
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public addRecipeStep() {
this.AppService.saveStep(this.item).subscribe(
item => { this.item = item; this.router.navigate(['/recipes']);},
error => console.log(error)
)
} }

在这个类中,我们首先创建一个新步骤,然后调用saveStep服务函数来添加一个配方步骤。 步删除组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“step-delete.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { RecipeStep } from "../viewmodels/recipeStep";
import { AppService } from "../services/app.service"; @Component({
selector: 'step-delete',
templateUrl: '../partials/deleteStep.html'
}) export class StepDeleteComponent implements OnInit {
item: RecipeStep;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var id = params['id'];
this.AppService.getStep(id).subscribe(item => this.item = item);
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public deleteStep() {
this.AppService.deleteStep(this.item.id).subscribe(
() => this.router.navigate(['/recipes']),
error => console.log(error)
)
} }

在这个类中,我们首先调用getStep服务函数来获取配方步骤信息,然后调用deleteStep服务函数来删除配方步骤。 项目细节的组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“item-detail.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { RecipeItem } from "../viewmodels/recipeitem";
import { AppService } from "../services/app.service"; @Component({
selector: 'item-detail',
templateUrl: '../partials/editItem.html'
}) export class ItemDetailComponent implements OnInit {
item: RecipeItem;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var id = params['id'];
this.AppService.getItem(id).subscribe(item => this.item = item);
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public editRecipeItem() {
this.AppService.saveItem(this.item).subscribe(
item => { this.item = item; this.router.navigate(['/recipes']); },
error => console.log(error)
)
} }

在这个类中,我们首先调用getItem服务函数来获取菜谱项信息,然后调用saveItem服务函数来更新菜谱项。 项新组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“item-new.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { RecipeItem } from "../viewmodels/recipeItem";
import { AppService } from "../services/app.service"; @Component({
selector: 'item-new',
templateUrl: '../partials/addItem.html'
}) export class ItemNewComponent implements OnInit {
item: RecipeItem;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var parentId = params['id'];
this.item = new RecipeItem();
this.item.parentId = parentId;
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public addRecipeItem() {
this.AppService.saveItem(this.item).subscribe(
item => { this.item = item; this.router.navigate(['/recipes']);},
error => console.log(error)
)
} }

在这个类中,我们首先创建一个新项,然后调用saveItem服务函数来添加一个菜谱项。 项删除组件 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“item-delete.component.ts”。 隐藏,收缩,复制Code

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { RecipeItem } from "../viewmodels/recipeItem";
import { AppService } from "../services/app.service"; @Component({
selector: 'item-delete',
templateUrl: '../partials/deleteItem.html'
}) export class ItemDeleteComponent implements OnInit {
item: RecipeItem;
sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) { } ngOnInit() {
this.sub = this.route.params.subscribe(params => {
var id = params['id'];
this.AppService.getItem(id).subscribe(item => this.item = item);
});
} ngOnDestroy() {
this.sub.unsubscribe();
} public deleteItem() {
this.AppService.deleteItem(this.item.id).subscribe(
() => this.router.navigate(['/recipes']),
error => console.log(error)
)
} }

在这个类中,我们首先调用getItem服务函数来获取菜谱步骤信息,然后调用deleteItem服务函数来删除菜谱项。 更改应用程序模块 Angular模块类描述了应用程序的各个部分是如何组合在一起的。每个应用程序都至少有一个Angular模块,就是你引导来启动应用程序的根模块。你想叫它什么都行。所以我们加载所有创建的组件。 隐藏,收缩,复制Code

///<reference path="../../typings/index.d.ts"/>
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
import { FormsModule } from "@angular/forms";
import "rxjs/Rx"; import { AppComponent } from "./app.component";
import { RecipeListComponent } from "./components/recipe-list.component";
import { RecipeDetailComponent } from "./components/recipe-detail.component";
import { RecipeNewComponent } from "./components/recipe-new.component";
import { RecipeDeleteComponent } from "./components/recipe-delete.component";
import { StepDetailComponent } from "./components/step-detail.component";
import { StepNewComponent } from "./components/step-new.component";
import { StepDeleteComponent } from "./components/step-delete.component";
import { ItemDetailComponent } from "./components/item-detail.component";
import { ItemNewComponent } from "./components/item-new.component";
import { ItemDeleteComponent } from "./components/item-delete.component"; import { AppRouting } from "./app.routing";
import { AppService } from "./services/app.service"; @NgModule({
// directives, components, and pipes
declarations: [
AppComponent,
RecipeListComponent,
RecipeDetailComponent,
RecipeNewComponent,
RecipeDeleteComponent,
StepDetailComponent,
StepNewComponent,
StepDeleteComponent,
ItemDetailComponent,
ItemNewComponent,
ItemDeleteComponent,
],
// modules
imports: [
BrowserModule,
HttpModule,
FormsModule,
RouterModule,
AppRouting ],
// providers
providers: [
AppService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }

另外,我们在这里导入route模块。然后我们可以做一个路由配置。 客户端路由配置 一个路由的Angular应用程序有一个单独的路由器服务实例。当浏览器的URL发生变化时,该路由器会查找相应的路由,从而确定要显示的组件。 路由器没有路由器直到您配置它。我们在app.routing.ts中配置客户端路由。 右键点击“scripts/app/components”文件夹,添加一个新项目。选择”。Net Core/客户端" TypeScript文件。命名为“app.routing.ts”。 隐藏,收缩,复制Code

import { ModuleWithProviders } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { RecipeListComponent } from "./components/recipe-list.component";
import { RecipeDetailComponent } from "./components/recipe-detail.component";
import { RecipeNewComponent } from "./components/recipe-new.component";
import { RecipeDeleteComponent } from "./components/recipe-delete.component";
import { StepDetailComponent } from "./components/step-detail.component";
import { StepNewComponent } from "./components/step-new.component";
import { StepDeleteComponent } from "./components/step-delete.component";
import { ItemDetailComponent } from "./components/item-detail.component";
import { ItemNewComponent } from "./components/item-new.component";
import { ItemDeleteComponent } from "./components/item-delete.component"; const routes: Routes = [
{
path: '',
redirectTo: '/recipes',
pathMatch: 'full'
},
{
path: 'recipes',
component: RecipeListComponent
},
{
path: 'recipes/edit/:id',
component: RecipeDetailComponent
},
{
path: 'recipes/add',
component: RecipeNewComponent
},
{
path: 'recipes/delete/:id',
component: RecipeDeleteComponent
},
{
path: 'recipes/editStep/:id',
component: StepDetailComponent
},
{
path: 'recipes/addStep/:id',
component: StepNewComponent
},
{
path: 'recipes/deleteStep/:id',
component: StepDeleteComponent
},
{
path: 'recipes/editItem/:id',
component: ItemDetailComponent
},
{
path: 'recipes/addItem/:id',
component: ItemNewComponent
},
{
path: 'recipes/deleteItem/:id',
component: ItemDeleteComponent
},
]; export const AppRoutingProviders: any[] = [
]; export const AppRouting: ModuleWithProviders = RouterModule.forRoot(routes);

在这里,我们配置数组中的所有路径和组件,然后app module导入这个数组。 菜谱列表模板 隐藏,收缩,复制Code

<div>
<arouterLink="/recipes/add"class="btn breadcrumb m-2">create a new recipe</a>
<div*ngFor="let recipe of items">
<divclass="btn-group tab-pane mb-2">
<buttonclass="btn-info pull-left"(click)="Expand(recipe)"><h5>{{recipe.name}} - {{recipe.comments}}</h5></button>
</div>
<divclass="btn-group">
<arouterLink="/recipes/edit/{{recipe.id}}"class="breadcrumb-item">edit</a>
<arouterLink="/recipes/delete/{{recipe.id}}"class="breadcrumb-item">delete</a>
</div>
<div*ngIf="recipe.show">
<arouterLink="/recipes/addStep/{{recipe.id}}"class="btn breadcrumb m-2">create a new step</a>
<div*ngFor="let step of recipe.recipeSteps">
<divclass="row ml-2">
<divclass="breadcrumb ml-2">
<span>step {{step.stepNo}} : {{step.instructions}}</span>
</div>
<divclass="btn-group m-2">
<arouterLink="/recipes/editStep/{{step.id}}"class="breadcrumb-item">edit</a>
<arouterLink="/recipes/deleteStep/{{step.id}}"class="breadcrumb-item">delete</a>
</div>
</div>
<arouterLink="/recipes/addItem/{{step.id}}"class="btn breadcrumb ml-4">create a new item</a>
<div*ngFor="let item of step.recipeItems">
<divclass="row ml-4">
<divclass="card-text ml-4">
<p> {{item.name}} {{item.quantity}} {{item.measurementUnit}}</p>
</div>
<divclass="btn-group ml-2">
<arouterLink="/recipes/editItem/{{item.id}}"class="breadcrumb-item">edit</a>
<arouterLink="/recipes/deleteItem/{{item.id}}"class="breadcrumb-item">delete</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

锚标记上的RouterLink指令让路由器控制这些元素。导航路径是固定的,因此可以为routerLink分配一个字符串(“一次性”绑定)。 如果导航路径更加动态,则可以将其绑定到返回路由链接参数数组(链接参数数组)的模板表达式。路由器将该数组解析为一个完整的URL。 在菜谱列表模板中,我们同时拥有固定链接和动态链接。我使用ngIf = "食谱。显示,以展开或折叠相应的配方。我需要提到的一点是,对于所有编辑和删除函数,我们传递对象id,但创建新步骤和新项,我们需要传递父对象id,这意味着创建新步骤,我们需要传递菜谱id;创建一个新项,然后需要传递步骤id。显然,创建一个新菜谱不需要传递任何东西。 配方详细模板(edit.html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“edit.html”。 隐藏,复制Code

<divclass="badge badge-info">
<h4>Edit Recipe</h4>
</div>
<div*ngIf="item"class="card-text">
<form(ngSubmit)="editRecipe()">
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="name">Name</label>
<input[(ngModel)]="item.name"name="name"type="text"class="form-control"/>
</div>
</div>
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="comments">Comments</label>
<input[(ngModel)]="item.comments"name="comments"type="text"class="form-control"/>
</div>
</div>
<divclass="row m-2">
<buttontype="submit"class="btn btn-primary">Save</button>
<arouterLink="/recipes"class="btn btn-default">Cancel</a>
</div>
</form>
</div>

配方详细信息模板实际上是一个提交表单。然而,ngSubmit确保了当处理程序代码抛出(这是提交的默认行为)并导致实际的http post请求时表单不会提交。 为了注册表单控件,我们使用了ngModel指令。通过与name属性的结合,ngModel在幕后为我们创建了一个表单控件抽象。每个注册了ngModel的表单控件都会自动显示在表单中。值,然后可以很容易地用于进一步的后处理。 在这个模板中,ngSubmit与配方细节组件中的eidtRecipe()方法绑定。“取消”按钮就会回到列表中。 Recipe New Template (add.html) 右键点击“wwwroot/partials”文件夹,添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“add.html”。 隐藏,复制Code

<divclass="badge badge-info">
<h4>Add Recipe</h4>
</div>
<div*ngIf="item"class="card-text">
<form(ngSubmit)="addRecipe()">
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="name">Name</label>
<input[(ngModel)]="item.name"name="name"type="text"class="form-control"/>
</div>
</div>
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="comments">Comments</label>
<input[(ngModel)]="item.comments"name="comments"type="text"class="form-control"/>
</div>
</div>
<divclass="row m-2">
<buttontype="submit"class="btn btn-primary">Save</button>
<arouterLink="/recipes"class="btn btn-default">Cancel</a>
</div>
</form>
</div>

在这个模板中,ngSubmit与配方新组件中的addRecipe()方法绑定。“取消”按钮就会回到列表中。 菜谱删除模板(Delete .html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“delete.html”。 隐藏,复制Code

<div*ngIf="item">

    <divclass="row">
<divclass="alert alert-warning">
<p>Do you really want to delete this recipe?</p>
<p> {{item.name}} - {{item.comments}}</p>
</div>
</div>
<button(click)="deleteRecipe()"class="btn btn-danger">Yes</button>
<arouterLink="/recipes"class="btn btn-default">No</a> </div>

配方删除模板不是一个提交表单。“是”按钮直接调用菜谱删除组件的deleteRecipe()。“否”按钮会回到食谱列表。 步骤详细模板(edit .html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“editStep.html”。 隐藏,复制Code

<divclass="badge badge-info">
<h4>Edit Recipe Step</h4>
</div>
<div*ngIf="item"class="card-text">
<form(ngSubmit)="editRecipeStep()">
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="stepNo">Step No.</label>
<input[(ngModel)]="item.stepNo"name="stepNo"type="text"class="form-control"/>
</div>
</div> <divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="instructions">Instructions</label>
<input[(ngModel)]="item.instructions"name="instructions"type="text"class="form-control"/>
</div>
</div>
<divclass="row m-2">
<buttontype="submit"class="btn btn-primary">Save</button>
<arouterLink="/recipes"class="btn btn-default">Cancel</a>
</div>
</form>
</div>

在这个模板中,ngSubmit与Step Detail组件中的editRecipeStep()方法绑定。“取消”按钮就会回到列表中。 新建模板(addStep.html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“addStep.html”。 隐藏,复制Code

<div class="badge badge-info">
<h4>Add a new recipe Step</h4>
</div>
<div *ngIf="item" class="card-text">
<form (ngSubmit)="addRecipeStep()">
<div class="row">
<div class="col-xl-6 form-group">
<label for="stepNo">Step No.</label>
<input [(ngModel)]="item.stepNo" name="stepNo" type="text" class="form-control" />
</div>
</div> <div class="row">
<div class="col-xl-6 form-group">
<label for="instructions">Instructions</label>
<input [(ngModel)]="item.instructions" name="instructions" type="text" class="form-control" />
</div>
</div>
<div class="row m-2">
<button type="submit" class="btn btn-primary">Save</button>
<a routerLink="/recipes" class="btn btn-default">Cancel</a>
</div>
</form>
</div>

在这个模板中,ngSubmit与步骤新组件中的addRecipeStep()方法绑定。“取消”按钮就会回到列表中。 步骤删除模板(deleteStep.html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“deleteStep.html”。 隐藏,复制Code

<div*ngIf="item">

    <divclass="row">
<divclass="alert alert-warning">
<p>Do you really want to delete this recipe step?</p>
<p>Step {{item.stepNo}} - {{item.instructions}}</p>
</div>
</div>
<button(click)="deleteStep()"class="btn btn-danger">Yes</button>
<arouterLink="/recipes"class="btn btn-default">No</a> </div>

步骤删除模板不是一个提交表单。“Yes”按钮直接调用步骤删除组件的deleteStep()。“否”按钮会回到食谱列表。 项目细节模板(editItem.html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“editItem.html”。 隐藏,收缩,复制Code

<divclass="badge badge-info">
<h4>Edit Recipe Item</h4>
</div>
<div*ngIf="item"class="card-text">
<form(ngSubmit)="editRecipeItem()">
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="name">Name</label>
<input[(ngModel)]="item.name"name="name"type="text"class="form-control"/>
</div>
</div>
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="quantity">Quantity</label>
<input[(ngModel)]="item.quantity"name="quantity"type="text"class="form-control"/>
</div>
</div>
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="measurementUnit">Measurement Unit</label>
<input[(ngModel)]="item.measurementUnit"name="measurementUnit"type="text"class="form-control"/>
</div>
</div>
<divclass="row m-2">
<buttontype="submit"class="btn btn-primary">Save</button>
<arouterLink="/recipes"class="btn btn-default">Cancel</a>
</div>
</form>
</div>

在这个模板中,ngSubmit与项目细节组件中的editRecipeItem()方法绑定。“取消”按钮就会回到列表中。 项目新模板(addItem.html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“addItem.html”。 隐藏,收缩,复制Code

<divclass="badge badge-info">
<h4>Add a new recipe Item</h4>
</div>
<div*ngIf="item"class="container-fluid">
<form(ngSubmit)="addRecipeItem()">
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="name">Name</label>
<input[(ngModel)]="item.name"name="name"type="text"class="form-control"/>
</div>
</div>
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="quantity">Quantity</label>
<input[(ngModel)]="item.quantity"name="quantity"type="text"class="form-control"/>
</div>
</div>
<divclass="row">
<divclass="col-xl-6 form-group">
<labelfor="measurementUnit">Measurement Unit</label>
<input[(ngModel)]="item.measurementUnit"name="measurementUnit"type="text"class="form-control"/>
</div>
</div>
<divclass="row m-2">
<buttontype="submit"class="btn btn-primary">Save</button>
<ahref="/"class="btn btn-default">Cancel</a>
</div>
</form>
</div>

在这个模板中,ngSubmit与Item New组件中的addRecipeItem()方法绑定。“取消”按钮就会回到列表中。 项目删除模板(deleteItem.html) 右键单击“wwwroot/partials”文件夹,并添加一个新项目。选择”。Net Core/客户端" HTML页面。命名为“deleteItem.html”。 隐藏,复制Code

<div*ngIf="item">
<divclass="row">
<divclass="alert alert-warning">
<p>Do you really want to delete this recipe item?</p>
<p> {{item.name}} {{item.quantity}} {{item.measurementUnit}}</p>
</div>
</div>
<button(click)="deleteItem()"class="btn btn-danger">Yes</button>
<arouterLink="/recipes"class="btn btn-default">No</a>
</div>

项目删除模板不是一个提交表单。Yes按钮呼叫de项目直接删除组件的leteItem()。“否”按钮会返回到食谱列表。 添加基本标签 我们需要设置基标签,因为它将告诉路由引擎如何组合所有我们的应用程序最终将拥有的导航url。 我们在索引中添加基标签。html,它在wwwroot文件夹下。 隐藏,收缩,复制Code

<html>
<head>
<basehref="/">
<title>Master Chef2</title>
<metaname="viewport"content="width=device-width, initial-scale=1"> <!-- Step 1. Load libraries -->
<!-- Polyfill(s) for older browsers -->
<scriptsrc="js/shim.min.js"></script>
<scriptsrc="js/zone.js"></script>
<scriptsrc="js/Reflect.js"></script>
<scriptsrc="js/system.src.js"></script> <!-- Angular2 Native Directives -->
<scriptsrc="/js/moment.js"></script> <!-- Step 2. Configure SystemJS -->
<scriptsrc="systemjs.config.js"></script>
<script>
System.import('app').catch(function (err) { console.error(err); });
</script>
<linkhref="lib/bootstrap/dist/css/bootstrap.min.css"rel="stylesheet"media="screen">
</head>
<!-- Step 3. Display the application -->
<body>
<divclass="container-fluid">
<!-- Application PlaceHolder -->
<masterchef2>Please wait...</masterchef2>
</div>
</body>
</html>

Angular 2 Typescript找不到名字 当我构建解决方案时,我得到了许多编译错误。例如,错误TS2304: Build:不能找到名称“Promise”。 有两种方法可以修复它。 将飞越器的目标从ES5切换到ES6。为此,更改您的tsconfig。json文件匹配以下值: 隐藏,复制代码{ “compileOnSave”:假的, " compilerOptions ": { “emitDecoratorMetadata”:没错, “experimentalDecorators”:没错, “模块”:“系统”, “moduleResolution”:“节点”, “noImplicitAny”:假的, “noEmitOnError”:假的, “removeComments”:假的, “sourceMap”:没错, “目标”:“es6” }, “排除”:( “node_modules”, “wwwroot” ] } 然而,这样做可能会带来一些问题:你可能无法使用一些还不支持ES6的工具/包/库,比如UglifyJS。 安装类型和core-js类型定义文件。坦克兵的目标仍然是ES5。 隐藏,复制代码{ “compileOnSave”:假的, " compilerOptions ": { “emitDecoratorMetadata”:没错, “experimentalDecorators”:没错, “模块”:“系统”, “moduleResolution”:“节点”, “noImplicitAny”:假的, “noEmitOnError”:假的, “removeComments”:假的, “sourceMap”:没错, “目标”:“es5” }, “排除”:( “node_modules”, “wwwroot” ] } 打开包。json文件(枚举NPM包的那个),并检查类型包是否已经出现在dependencies或devDependencies节点中,以及在脚本块的后安装阶段运行它所需的脚本。如果它们不在这里,添加它们,使您的文件看起来如下: 隐藏,收缩,复制代码{ “版本”:“1.0.0”, “名称”:“asp.net”, “依赖”:{ “@angular /普通”:“2.0.0”, “@angular /编译器”:“2.0.0”, “@angular /核心”:“2.0.0”, “@angular /形式”:“2.0.0”, “@angular / http”:“2.0.0”, :“@angular / platform-browser 2.0.0”, :“@angular / platform-browser-dynamic 2.0.0”, “@angular /路由器”:“3.0.0”, “@angular /升级”:“2.0.0”, :“core-js ^ 2.4.1”, :“reflect-metadata ^ 0.1.8”, :“rxjs 5.0.0-rc.4”, :“systemjs ^ 0.19.41”, “输入”:“^ 1.3.2”, ”区。js”:“^ 0.7.2”, “时刻”:“^ 2.17.0” }, " devDependencies ": { “吞咽”:“^ 3.9.1”, :“gulp-clean ^ 0.3.2”, :“gulp-concat ^ 2.6.1”, :“gulp-less ^ 3.3.0”, :“gulp-sourcemaps ^ 1.9.1”, :“gulp-typescript ^ 3.1.3”, :“gulp-uglify ^ 2.0.0”, :“打印稿^ 2.0.10” }, "脚本":{ "postinstall": "typings install dt~core-js@^0.9.7 -global" } } 请注意,我们必须指定版本为“0.9.7”,否则会安装最新版本,仍然会造成问题。现在,ES6 TypeScript包应该可以顺利编译了。 运行应用程序 首先,重新构建解决方案。然后转到任务运行器资源管理器窗口运行默认任务。 完成所有任务后,点击“IIS Express”。 加入一个新食谱——麻婆豆腐。 保存后,可以为每个步骤添加步骤和项。 在谷歌Chrome中调试Angular代码 虽然Angular 2是TypeScript,但所有的TypeScript文件都被gulp task转换成JavaScript的minify文件。请看下面的截图,相应的JavaScript文件是在wwwroot/app文件夹下创建的。 因此您不能直接调试TypeScript。幸运的是,我们可以转而调试JavaScript文件。 点击“IIS Express”下拉按钮,选择浏览器的谷歌Chrome。然后单击“IIS Express”启动应用程序。应用程序启动后,在谷歌Chrome的“更多工具”中点击“Developer Tools”。然后单击“来源”。现在您可以使用树视图查看所有JavaScript文件。挑选您想要调试的任何JavaScript文件。这里我们以删除食谱为例。所以我取了“recipe-delete.component.js”。 正如我所说的,所有JavaScript文件都是用minify样式创建的,这很难阅读。不过别担心,Chrome可以帮你把这个小文件还原成普通文件。只要点击中间窗口左下角的“{}”,缩小文件就会变成“漂亮打印”文件。我将break品脱放在deleteRecipe()函数上。 点击食谱旁边的“删除”按钮。应用程序显示菜谱删除模板。 然后单击“Yes”以触发断点,您就可以看到您感兴趣的变量。 将断点放在app.service.js的deleteRecipe函数上。然后点击“Resume script”按钮或者按F8, app.service.js的断点也会被触发。 在App服务中,它调用服务器端web API。如果您将断点放置在服务器端Http Delete方法上,那么在恢复脚本时服务器端断点将被触发。 结论 在这些文章中,我已经向你展示了如何在ASP环境下构建Angular 2 CRUD SPA。净的核心。我们还学习了如何使用Angular 2的Route来导航到不同的组件和模板。由于Angular 4已经在3月份发布,Master Chef将会被转移到Visual Studio 2017,在下一篇文章中会提到Angular 4。 我已经在github中创建了一个公共存储库,即Master Chef存储库。请随时参与开发工作。 本文转载于:http://www.diyabc.com/frontweb/news18958.html

最新文章

  1. input-placeholder
  2. file access , argc, argv[ ]
  3. LCS最长公共子序列(最优线性时间O(n))
  4. asp.net关于Cookie跨域(域名)的问题
  5. D题 - A+B for Input-Output Practice (III)
  6. Android内存等信息
  7. FFMPEG图片转视频
  8. MSSQL - 用GUID值来完成数据表行标识
  9. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(11)-验证码实现和底层修改
  10. Deploy Django in Windows
  11. 【腾讯优测干货分享】微信小程序之自动化亲密接触
  12. NFS PersistentVolume - 每天5分钟玩转 Docker 容器技术(151)
  13. Tomcat性能优化及JVM内存工作原理
  14. 取消IDEA默认打开最近的项目(设置打开选择创建页面)
  15. headers 替换脚本
  16. 前端组件库 - 搭建web app常用的样式/组件等收集列表(移动优先)
  17. linux fix the superblock by dumpe2fs fsck
  18. Spring Boot 之 RESTfull API简单项目的快速搭建(二)
  19. Struts2_day03讲义_使用Struts2完成对客户查询的优化操作
  20. 21-js 实现评论时间格式转化

热门文章

  1. Mono生命周期小实验
  2. 喵的Unity游戏开发之路 - 互动环境(有影响的运动)
  3. Rng(求逆元)
  4. Codeforces1348 题解
  5. 为什么要做一款ERP软件——开源软件诞生7
  6. pycharm可以运行但无法debug的解决方法
  7. fake_useragent.errors.FakeUserAgentError: Maximum amount of retries reached解决方法!
  8. Solr专题(四)Solr安全设置
  9. Django+bootstrap启动登录模板页面(Django三)
  10. ajax之---上传图片和预览