在Maser Chef第1部分中,我介绍了如何集成ASP。NET Core MVC与Fluent NHibernate和Angular JS。在这篇文章中,我将讨论如何使用ASP。NET Core MVC, Fluent NHibernate和Angular JS来实现一个CRUD SPA(单页应用)。 在存储库中使用泛型 创建、读取、更新和删除(CRUD的首字母缩写)是持久性存储的四个基本功能。 我们需要首先在我们的repository类的数据库级别上实现CRUD。我希望在查询、添加、更新、删除方法中使用泛型,以避免冗余编码。为什么使用泛型?简短的回答是,类型安全、编译时检查、更快并且适用于具有相同底层行为的许多类型。 在以前的数据模型类中,所有成员都具有与数据库字段相同的名称。实际上,数据模型类成员不必与数据库字段相同。例如,Recipe类的Id不必是RecipeId,它可以是任何名称,比如Id。我们需要做的是在映射过程中告诉Fluent NHibernate,如下所示。 隐藏,复制Code
Id(x => x.Id, "RecipeId");
通过这种方式,Fluent NHibernate知道它在映射“Id”到“RecipeId”。 因为我们不必使用相同的名称作为数据库字段,现在我们有机会改变不同的数据模型类,以拥有一些共同的成员。 我们创建了一个基类实体。 隐藏,复制Code
public class Entity { public virtual Guid Id { get; set; } public virtual Guid? ParentId { get; set; } public virtual Type ParentType => null; }
然后将Recipe, RecipeStep和RecipeItem派生Entity,将Recipe的RecipeId替换为Id,将RecipeStep的RecipeStepId替换为Id,将RecipeItem的ItemId替换为Id,将RecipeStep的RecipeId替换为ParentId,将RecipeItem的RecipeStepId替换为ParentId。 隐藏,复制Code
public class Recipe : Entity { public virtual string Name { get; set; } public virtual string Comments { get; set; } public virtual DateTime ModifyDate { get; set; } public virtual IList<RecipeStep> Steps { get; set; } } public class RecipeStep : Entity { public virtual int StepNo { get; set; } public virtual string Instructions { get; set; } public virtual IList<RecipeItem> RecipeItems { get; set; } public override Type ParentType => typeof(Recipe); } public class RecipeItem : Entity { public virtual string Name { get; set; } public virtual decimal Quantity { get; set; } public virtual string MeasurementUnit { get; set; } public override Type ParentType => typeof(RecipeStep); }
现在我们还需要更改映射类。请注意不同名称的映射。 隐藏,收缩,复制Code
public class RecipeMap : ClassMap<Recipe> { public RecipeMap() { Id(x => x.Id, "RecipeId"); Map(x => x.Name); Map(x => x.Comments); Map(x => x.ModifyDate); HasMany(x => x.Steps).KeyColumn("RecipeId").Inverse().Cascade.DeleteOrphan().OrderBy("StepNo Asc"); Table("Recipes"); } } public class RecipeStepMap : ClassMap<RecipeStep> { public RecipeStepMap() { Id(x => x.Id, "RecipeStepId"); Map(x => x.ParentId, "RecipeId"); Map(x => x.StepNo); Map(x => x.Instructions); HasMany(x => x.RecipeItems).KeyColumn("RecipeStepId").Inverse().Cascade.DeleteOrphan(); Table("RecipeSteps"); } } public class RecipeItemMap : ClassMap<RecipeItem> { public RecipeItemMap() { Id(x => x.Id, "ItemId"); Map(x => x.Name); Map(x => x.Quantity); Map(x => x.MeasurementUnit); Map(x => x.ParentId, "RecipeStepId"); Table("RecipeItems"); } }
“Cascade.DeleteOrphan”是什么?此选项在删除父对象时删除子对象。对于我们的示例,删除一个配方将删除该配方的所有配方步骤和配方项,删除一个步骤将删除该步骤的所有项。 然后将Repository的方法改为泛型方法,并放入泛型约束,即T必须是Entity的子类。 隐藏,收缩,复制Code
public T GetEntity<T>(Guid id) where T : Entity { try { return _session.Get<T>(id); } catch (Exception ex) { throw ex; } } public T AddEntity<T>(T entity) where T : Entity { T newOne = null; using (var transaction = _session.BeginTransaction()) { try { _session.SaveOrUpdate(entity); Commit(transaction, entity); RefreshParentObject(entity); newOne = _session.Get<T>(entity.Id) as T; } catch (Exception ex) { throw ex; } return newOne; } } public void UpdateEntity<T>(T entity) where T : Entity { using (var transaction = _session.BeginTransaction()) { try { _session.Update(entity); Commit(transaction, entity); RefreshParentObject(entity); } catch (Exception ex) { throw ex; } } } public void DeleteEntity<T>(Guid id) where T : Entity { using (var transaction = _session.BeginTransaction()) { var entity = _session.Get<T>(id); if (entity != null) { try { _session.Delete(entity); Commit(transaction, entity); RefreshParentObject(entity); } catch (Exception ex) { throw ex; } } } }
对于添加、更新和删除方法,所有调用RefreshParentObject()。这是什么意思?当我们改变RecipeStep或RecipeItem时,它的父对象缓存并不知道这个改变。我们需要刷新父对象缓存。 隐藏,复制Code
void RefreshParentObject(Entity entity) { if (!entity.ParentId.HasValue) return; var parentObj = _session.Get(entity.ParentType, entity.ParentId.Value); if (parentObj != null) _session.Refresh(parentObj); }
现在我们更新web API控制器。 隐藏,收缩,复制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.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>(recipe); return new ObjectResult(existingOne); } [HttpDelete("{id}")] public IActionResult Delete(Guid id) { _repository.DeleteEntity<Recipe>(id); return new StatusCodeResult(200); }
角端路由 现在,我们需要在Master Chef应用程序中设置客户机路由,以便根据客户机提供的URL替换动态视图。我们可以从角度路由模块中获取角度路由特征。 使用ngRoute模块,您可以在单个页面应用程序中导航到不同的页面,而无需重新加载页面。$route用于将url深链接到控制器和视图(HTML部分)。它监视$location.url()并尝试将该路径映射到现有的路由定义。 $route中有两个依赖项,即$location和$routeParams。 1)注入ngRoute 打开app.js,在masterChefApp模块中注入ngroute。 隐藏,复制Code
(function () { 'use strict'; angular.module('masterChefApp', [ // Angular modules 'ngRoute', // Custom modules 'recipesService' // 3rd Party Modules ]); })();
2)配置Angular路由 为我们的Angular app模块定义一个配置函数——masterChefApp。并且,在该配置函数中,使用来自ngRoute模块的路由提供程序服务来定义客户端路由 隐藏,复制Code
angular.module('masterChefApp').config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) { $routeProvider .when('/', { templateUrl: 'partials/recipes.html', controller: 'recipesController' }) .when('/recipes/add', { templateUrl: 'partials/add.html', controller: 'recipesAddController' }) .when('/recipes/edit/:id', { templateUrl: 'partials/edit.html', controller: 'recipesEditController' }) .when('/recipes/delete/:id', { templateUrl: 'partials/delete.html', controller: 'recipesDeleteController' }); $locationProvider.html5Mode(true); }]);
第一个只是一个默认的路由-正斜杠。第二个是/recipes/add。第三个是/recipes/edit/,并传递:id作为路由参数,它允许我们采用一个动态id,我可以匹配其中一个菜谱。最后一个route /recipes/delete/:id也需要采用动态id参数。这个默认路由只会列出所有的菜谱。“添加”路由处理添加,“编辑”路由处理编辑或更新,“删除”路由处理删除或删除。CRUD函数由这四个客户端路由表示。对于每个路由,我们需要定义一个模板URL(它表示一些应该为此路由呈现的HTML)和一个单独的控制器(它将处理此路由)。 在最底部,使用$locationProvider,它的html5Mode函数,设置为true,以确保我可以使用友好和自然的url,避免使用hash bangs进行客户端路由。 Angular JS客户端控制器 我们已经配置了默认路由、添加路由、编辑路由和删除路由。然后需要相应的控制器,recipesController、recipesAddController、recipesEditController和recipesDeleteController。我们在recipesController.js中定义了所有这些控制器。 1)注入“添加”、“编辑”和“删除”控制器 隐藏,复制Code
angular .module('masterChefApp') .controller('recipesController', recipesController) .controller('recipesAddController', recipesAddController) .controller('recipesEditController', recipesEditController) .controller('recipesDeleteController', recipesDeleteController);
2)实现食谱添加控制器 隐藏,复制Code
recipesAddController.$inject = ['$scope', 'Recipe', '$location']; function recipesAddController($scope, Recipe, $location) { $scope.recipe = new Recipe(); $scope.addRecipe = function () { $scope.recipe.$save(function () { $location.path('/'); }); } }
因此,recipesAddController需要一个$作用域和食谱服务,它还需要$location服务。recipesAddController创建或提供了允许用户向应用程序添加菜谱的功能。为此,使用菜谱服务创建一个新的$scope变量recipe。它还在这里创建了一个$scope函数——addRecipe,该函数将使用recipe services保存方法向服务器提交菜谱。在提交食谱后的回调中,我们将把应用程序重定向到它的主页。 3)实现菜谱编辑控制器 隐藏,复制Code
recipesEditController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesEditController($scope, Recipe, $location, $routeParams) { $scope.recipe = Recipe.get({ id: $routeParams.id }); $scope.editRecipe = function () { $scope.recipe.$save(function () { $location.path('/'); }); } }
recipesEditController需要一个$作用域和菜谱服务$location服务。它还需要$routeParameter来传递id. recipesEditController创建或提供允许某人向应用程序更新菜谱的功能。我们将使用routeParams服务来更新菜谱。通过从route参数获取菜谱的ID。然后,我们将进入服务器,通过调用菜谱服务get函数获取适当的菜谱——这次是提供ID的get方法。该ID将被提供给前端。用户可以做出任何。 最后,我们将更新后的食谱记录提交给服务器。 4)实现菜谱删除控制器 隐藏,复制Code
recipesDeleteController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesDeleteController($scope, Recipe, $location, $routeParams) { $scope.recipe = Recipe.get({ id: $routeParams.id }); $scope.deleteRecipe = function () { $scope.recipe.$remove({ id: $scope.recipe.id }, function () { $location.path('/'); }); }; }
recipesDeleteController使用$routeParams获取ID并检索特定的菜谱。然后提供这个函数deleteRecipe,在这个函数中我们可以使用菜谱服务的$remove方法告诉服务器我们想要删除一个特定的菜谱。 局部视图模板 1)修改Index.html使用ng-view 修改index.html以使用部分视图。首先添加一个“base”标签和它的href属性到/。这对于$locationProvider能够正常工作是必要的,因为它需要一个基础。现在转到正文内容。摆脱所有这些,只需使用ng-view指令。 隐藏,复制Code
<!DOCTYPEhtml> <htmlng-app="masterChefApp"> <head> <basehref="/"> <metacharset="utf-8"/> <title>Master Chef Recipes</title> <scriptsrc="lib/angular/angular.min.js"></script> <scriptsrc="lib/angular-resource/angular-resource.min.js"></script> <scriptsrc="lib/angular-route/angular-route.min.js"></script> <scriptsrc="app.js"></script> </head> <bodyng-cloak> <div> <ng-view></ng-view> </div> </body> </html>
基于这个ng-view指令和我们已经设置的路由的使用,ng-view将能够交付正确的部分视图和正确的控制器,以在客户端路由上使用$routeProvider来为视图提供电源。 我们在app.js文件中指定了四个控制器。这些控制器给我们CRUD操作。route URL /将从服务器检索所有菜谱。/recipes/add将创建一个新菜谱。使用变量id的recipes/edit将更新现有的菜谱,而/recipes/delete也使用变量id将从服务器删除或删除特定的菜谱。 现在我们在wwwroot文件夹下创建“partials”文件夹。然后可以逐个添加模板。 2)检索模板—Recipes.html 右键点击wwwroot下的“partials”文件夹。添加一个新项目。在客户端模板部分,选择HTML Page。我们给它起名叫“recipes.html”。 html,它将检索并显示菜谱列表。 隐藏,收缩,复制Code
<div> <h2>Master Chief Recipes</h2> <ul> <ling-repeat="recipe in recipes"> <div> <h5>{{recipe.name}} - {{recipe.comments}}</h5> </div> <div> <ahref="recipes/edit/{{recipe.id}}">edit</a> </div> <div> <ahref="recipes/delete/{{recipe.id}}">delete</a> </div> <ul> <ling-repeat="step in recipe.steps"> <p> step {{step.stepNo}} : {{step.instructions}}</p> <ul> <ling-repeat="item in step.recipeItems"> <p> {{item.name}} {{item.quantity}} {{item.measurementUnit}}</p> </li> </ul> </li> </ul> </li> </ul> <p><ahref="recipes/add"> Add a new recipe </a></p> </div>
请注意,这不是完整的html。我们只是定义了一个部分视图它将在AngularJS应用中被替换。 现在如果我们运行它,我们应该看到所有的食谱。 3)引导风格 虽然它工作,但它是一个完全普通的html。所以我们需要应用一些CSS样式。 Bootstrap是一个非常流行的前端框架,它包括基于HTML和CSS的排版设计模板,表单,按钮,表格,导航,模板,图像旋转木马和许多其他的,以及可选的JavaScript插件。应用bootstrap样式可以使我们的master chef web应用程序更漂亮。 我们已经在bower配置中添加了引导包。 隐藏,复制Code
{ "name": "asp.net", "private": true, "dependencies": { "jquery": "3.1.0", "bootstrap": "3.1.0", "angular": "1.5.8", "angular-route": "1.5.8", "angular-resource": "1.5.8" } }
所以bootstrap已经安装在wwwrootlib文件夹中。现在我们将它包含在index.html中。 隐藏,复制Code
<link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" media="screen">
我们将应用下面的引导样式。 我们在index.html中应用主div与.container-fluid(全宽度)来进行适当的对齐和填充。 我们为ui应用所有的.list-group,为recipes.html中的li应用.list-group-item。我们还为添加链接应用“btn-primary”,为编辑链接应用“btn-default”,为删除链接应用“btn-delete”。我还想把recipe显示为一个徽章,所以也要应用。badge样式。 再跑一次大厨,看看现在是什么样子。 Bootstrap包括一个强大的移动优先网格系统,用于构建各种形状和大小的布局。它基于12列布局,有多个层次,每个媒体查询范围一个。有三个主要组件——容器、行和列。容器-。固定宽度的容器或。容器-流体的全宽度-中心你的网站内容,并帮助对齐你的网格内容。行是列的水平分组,确保列被正确地排列。列类表示您希望在每行可能的12列中使用的列数。如果你想要三个等宽的列,你可以用。cole -xs-4。 我们使用bootstrap网格系统中的主厨模板。 4)使用Angular JS实现展开/折叠 我知道有很多方法可以用jQuery展开/折叠来改变DOM。记住,我们使用的是MVVM模式。所以我热衷于通过改变控制器(视图模型)中的模型来实现展开/折叠。 在recipesController中添加expand()函数。在expand()函数中,我们设置了recipe对象的show属性。 隐藏,复制Code
recipesController.$inject = ['$scope', 'Recipe']; function recipesController($scope, Recipe) { $scope.recipes = Recipe.query(); $scope.expand = function (recipe) { recipe.show = !recipe.show; } }
我们在recipesController中添加了一个ng-click来调用expand()函数。 隐藏,复制Code
<divclass="btn-group"> <buttonclass="btn badge pull-left"ng-click="expand(recipe)"><h5>{{recipe.name}} - {{recipe.comments}}</h5></button> </div>
然后我们使用ng-show来控制是否显示菜谱的详细信息。 隐藏,复制Code
<ul class="list-group" ng-show="recipe.show"> <li ng-repeat="step in recipe.steps" class="list-group-item">
只需单击recipe badge扩展您想要查看的内容。 5)创建模板- add.html 右键单击wwwroot下的“partials”文件夹。添加一个新项目。在客户端模板部分,选择HTML Page。我们将其命名为“add.html”。 在add.html中,使用ng-submit将数据发送到服务器。我们将通过ng-model指令将用户输入到输入字段中的信息绑定到一个范围变量菜谱。当用户按下Save按钮使用表单提交时,我们会调用作用域函数addRecipe它会在控制器中后台将recipe对象提交给服务器。 隐藏,复制Code
<h1>Add a new recipe</h1> <divclass="container-fluid"> <formng-submit="addRecipe()"> <divclass="row"> <divclass="form-group col-xs-4"> <labelfor="name">Name</label> <inputng-model="recipe.name"name="name"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-8"> <labelfor="comments">Comments</label> <inputng-model="recipe.comments"name="comments"type="text"class="form-control"/> </div> </div> <divclass="row"> <buttontype="submit"class="btn btn-primary">Save</button> <ahref="/"class="btn btn-default">Cancel</a> </div> </form> </div>
编辑模板- Edit .html 右键单击wwwroot下的“partials”文件夹。添加一个新项目。在客户端模板部分,选择HTML Page。我们提供一个名称“edit.html”。 现在我们要更新一个食谱。我们将在edit.html部分模板中处理这个问题。edit.html看起来像add.html,因为我们需要为最终用户提供所有必要的字段,以实际更新现有菜谱。我们有recipe.name和recipe.comments的输入。它们通过ng-model指令被绑定到一个范围变量——一个对象配方。此外,在编辑控制器上有一个作用域函数——editRecipe。当用户在编辑中按下Save按钮时。html中,该函数将被调用,而将更新的菜谱信息提交到服务器进行持久存储是该函数的工作。 隐藏,复制Code
<h1>Edit recipe</h1> <divclass="container-fluid"> <formng-submit="editRecipe()"> <divclass="row"> <divclass="form-group col-xs-4"> <labelfor="name">Name</label> <inputng-model="recipe.name"name="name"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-8"> <labelfor="comments">Comments</label> <inputng-model="recipe.comments"name="comments"type="text"class="form-control"/> </div> </div> <divclass="row"> <buttontype="submit"class="btn btn-primary">Save</button> <ahref="/"class="btn btn-default">Cancel</a> </div> </form> </div>
7)删除模板 右键点击wwwroot下的“partials”文件夹。添加一个新项目。在客户端模板部分,选择HTML Page。我们将其命名为“delete.html”。 在删除。html,我们会提供一个段落来进行确认。那么你真的想删除这个食谱吗?我们将绑定到有问题的食谱信息也就是要删除的食谱。我们将提供一个按钮,它调用一个作用域函数- deleteRecipe。它将向服务器提交一个请求,以删除特定的菜谱。 隐藏,复制Code
<div class="alert alert-warning"> <p>Do you really want to delete this recipe?</p> <p> {{recipe.name}} - {{recipe.comments}}</p> </div> <button ng-click="deleteRecipe()" class="btn btn-danger">Yes</button> <a href="/" class="btn btn-default">No</a>
多个URL映射到同一个Web API控制器 那食谱步骤和食谱项呢?一般来说,我们可以创建单独的API控制器来处理菜谱步骤和菜谱项。但是它太重了。我想把所有与菜谱相关的restful服务打包到RecipesController中。但是对于配方步骤操作和配方项操作,它肯定需要不同的url。幸运的是,ASP。NET Core Web API支持不同的路由。路由是Web API将URI匹配到操作的方式。Web API支持一种新的路由类型,称为属性路由。顾名思义,属性路由使用属性来定义路由。属性路由使您可以对web API中的uri进行更多的控制。例如,您可以轻松地创建描述资源层次结构的uri。 web控制器类的route属性是基URI。 隐藏,复制Code
[Route("api/[controller]")] public class RecipesController : Controller { …. }
对于RecipesController,基本URL是/api/recipes。 隐藏,复制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(); }
上面的方法没有route属性,这意味着这个方法被映射到/api/recipes/:id 但是对于get step方法和get item方法,我们需要不同的URL。我想获得步骤URL是/api/recipes/step/:id和项目URL是/api/recipes/item/:id。因此,我们为get step方法添加[Route("step/{id}")],为get item方法添加[Route("item/{id}")]。 隐藏,复制Code
[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(); } [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(); }
让我们看看API路由是否可以工作。单击IIS Express启动我们的web应用程序。首先我们检查URL, api/recipes/step/AEE9602B-03EF-4A5F-A380-2962134ADB7E。 它像预期的那样工作。 然后我们检查api/recipes/item/862B91D5-FB60-4004-8179-0415AB900795 它也起作用了。 我们还需要为post和delete添加路由属性。 隐藏,收缩,复制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) { 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) { 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); }
单个Angular资源服务的多个路由url Angular资源服务也支持多个url。到目前为止,我们只使用默认动作。 隐藏,复制Code
{ get: {method: 'GET'}, save: {method: 'POST'}, query: {method: 'GET', isArray: true}, remove: {method: 'DELETE'}, delete: {method: 'DELETE'} }
以上操作都是在ng resource中构建的,所以我们可以直接使用它。 隐藏,复制Code
recipesService.factory('Recipe', ['$resource', function ($resource) { return $resource('/api/recipes/:id'); }]);
但是我们现在需要定义自己的自定义操作,并使用默认URL为操作提供不同的URL。 隐藏,复制Code
recipesService.factory('Recipe', ['$resource', function ($resource) { return $resource('/api/recipes/:id', {}, { getRecipeStep: { method: 'GET', url: '/api/recipes/step/:id' }, saveRecipeStep: { method: 'POST', url: '/api/recipes/step' }, removeRecipeStep: { method: 'DELETE', url: '/api/recipes/step/:id' }, getRecipeItem: { method: 'GET', url: '/api/recipes/item/:id' }, saveRecipeItem: { method: 'POST', url: '/api/recipes/item' }, removeRecipeItem: { method: 'DELETE', url: '/api/recipes/item/:id' } }); }]);
我们仍然使用recipe的默认操作,并添加新的自定义操作getRecipeStep、saveRecipeStep、removeRecipeStep、getRecipeItem、saveRecipeItem和removeRecipeItem。 所有url都匹配配方步骤和配方项的web API url。 为配方步骤和配方项添加新的角度路径 现在我们需要为app.js中的菜谱步骤创建、更新、删除和菜谱项创建、更新、删除模板和控制器添加新的客户端路由。 隐藏,收缩,复制Code
$routeProvider .when('/', { templateUrl: 'partials/recipes.html', controller: 'recipesController' }) .when('/recipes/add', { templateUrl: 'partials/add.html', controller: 'recipesAddController' }) .when('/recipes/edit/:id', { templateUrl: 'partials/edit.html', controller: 'recipesEditController' }) .when('/recipes/delete/:id', { templateUrl: 'partials/delete.html', controller: 'recipesDeleteController' }) .when('/recipes/addStep/:id', { templateUrl: 'partials/addStep.html', controller: 'recipesAddStepController' }) .when('/recipes/editStep/:id', { templateUrl: 'partials/editStep.html', controller: 'recipesEditStepController' }) .when('/recipes/deleteStep/:id', { templateUrl: 'partials/deleteStep.html', controller: 'recipesDeleteStepController' }) .when('/recipes/addItem/:id', { templateUrl: 'partials/addItem.html', controller: 'recipesAddItemController' }) .when('/recipes/editItem/:id', { templateUrl: 'partials/editItem.html', controller: 'recipesEditItemController' }) .when('/recipes/deleteItem/:id', { templateUrl: 'partials/deleteItem.html', controller: 'recipesDeleteItemController' });
为配方步骤和配方项添加新的Angular控制器 在recipesController.js中注入step和item控制器。 隐藏,复制Code
angular .module('masterChefApp') .controller('recipesController', recipesController) .controller('recipesAddController', recipesAddController) .controller('recipesEditController', recipesEditController) .controller('recipesDeleteController', recipesDeleteController) .controller('recipesAddStepController', recipesAddStepController) .controller('recipesEditStepController', recipesEditStepController) .controller('recipesDeleteStepController', recipesDeleteStepController) .controller('recipesAddItemController', recipesAddItemController) .controller('recipesEditItemController', recipesEditItemController) .controller('recipesDeleteItemController', recipesDeleteItemController);
recipesAddStepController创建或提供了允许某人向应用程序添加菜谱步骤的功能。当我们添加配方步骤时,我们需要父配方Id。我们将通过使用routeParams服务获得要创建的配方步骤。通过从route参数获取菜谱的ID。 隐藏,复制Code
recipesAddStepController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesAddStepController($scope, Recipe, $location, $routeParams) { $scope.recipeStep = new Recipe(); $scope.recipeStep.parentId = $routeParams.id; $scope.addRecipeStep = function () { $scope.recipeStep.$saveRecipeStep(function () { $location.path('/'); }); }; }
recipesEditStepController创建或提供了允许某人将配方步骤更新到应用程序的功能。我们将使用routeParams服务来更新菜谱步骤。通过从route参数获取菜谱步骤的ID。 隐藏,复制Code
recipesEditStepController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesEditStepController($scope, Recipe, $location, $routeParams) { $scope.recipeStep = Recipe.getRecipeStep({ id: $routeParams.id }); $scope.editRecipeStep = function () { $scope.recipeStep.$saveRecipeStep(function () { $location.path('/'); }); }; }
recipesDeleteStepController使用$routeParams获取ID并检索特定的菜谱步骤。然后将此函数的删除步骤提供给应用程序。 隐藏,复制Code
recipesDeleteStepController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesDeleteStepController($scope, Recipe, $location, $routeParams) { $scope.recipeStep = Recipe.getRecipeStep({ id: $routeParams.id }); $scope.deleteRecipeStep = function () { $scope.recipeStep.$removeRecipeStep({ id: $scope.recipeStep.id }, function () { $location.path('/'); }); }; }
recipesAddItemController创建或提供了允许用户向应用程序添加菜谱项的功能。当我们添加菜谱项时,我们需要父菜谱步骤Id。我们将通过使用routeParams服务获得要创建的菜谱项。通过从route参数获取菜谱步骤的ID。 隐藏,复制Code
recipesAddItemController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesAddItemController($scope, Recipe, $location, $routeParams) { $scope.recipeItem = new Recipe(); $scope.recipeItem.parentId = $routeParams.id; $scope.addRecipeItem = function () { $scope.recipeItem.$saveRecipeItem(function () { $location.path('/'); }); }; }
recipesEditItemController创建或提供了允许用户将菜谱项更新到应用程序的功能。我们将使用routeParams服务来更新菜谱项。通过从route参数获取菜谱项的ID。 隐藏,复制Code
recipesEditItemController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesEditItemController($scope, Recipe, $location, $routeParams) { $scope.recipeItem = Recipe.getRecipeItem({ id: $routeParams.id }); $scope.editRecipeItem = function () { $scope.recipeItem.$saveRecipeItem(function () { $location.path('/'); }); }; }
recipesDeleteItemController使用$routeParams获取ID并检索特定的菜谱项。然后提供此函数,将菜谱项删除到应用程序。 隐藏,复制Code
recipesDeleteItemController.$inject = ['$scope', 'Recipe', '$location', '$routeParams']; function recipesDeleteItemController($scope, Recipe, $location, $routeParams) { $scope.recipeItem = Recipe.getRecipeItem({ id: $routeParams.id }); $scope.deleteRecipeItem = function () { $scope.recipeItem.$removeRecipeItem({ id: $scope.recipeItem.id }, function () { $location.path('/'); }); }; }
添加配方步骤和配方项的所有模板 现在我们需要为配方步骤和配方项创建所有模板。创建“addStep。html”、“editStep。html”、“deleteStep.html”、“addItem。html”、“editItem。html”和“deleteItem。在partials文件夹中。 1)配方步骤模板 在addStep。html,使用ng-submit发送数据到服务器。当用户按下Save按钮时,调用一个作用域函数addRecipeStep,该函数在控制器的后台将向服务器提交这个配方步骤对象。 隐藏,复制Code
<h1>Add a new recipe step</h1> <divclass="container-fluid"> <formng-submit="addRecipeStep()"> <divclass="row"> <divclass="form-group col-xs-1"> <labelfor="stepNo">Step No.</label> <inputng-model="recipeStep.stepNo"name="stepNo"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-8"> <labelfor="instructions">Instructions</label> <inputng-model="recipeStep.instructions"name="instructions"type="text"class="form-control"/> </div> </div> <divclass="row"> <buttontype="submit"class="btn btn-primary">Save</button> <ahref="/"class="btn btn-default">Cancel</a> </div> </form> </div>
html更新现有的配方步骤。使用ng-model指令将输入字段绑定到一个范围变量——一个对象recipeStep。此外,在step编辑控制器上,有一个作用域函数- editRecipeStep。 隐藏,复制Code
<h1>Edit Recipe Step</h1> <divclass="container-fluid"> <formng-submit="editRecipeStep()"> <divclass="row"> <divclass="form-group col-xs-1"> <labelfor="stepNo">Step No.</label> <inputng-model="recipeStep.stepNo"name="stepNo"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-8"> <labelfor="instructions">Instructions</label> <inputng-model="recipeStep.instructions"name="instructions"type="text"class="form-control"/> </div> </div> <divclass="row"> <buttontype="submit"class="btn btn-primary">Save</button> <ahref="/"class="btn btn-default">Cancel</a> </div> </form> </div>
在deleteStep。html,我们会提供一个段落来进行确认。我们将提供一个按钮,它调用一个作用域函数- deleteRecipeStep。它将向服务器提交一个请求,以删除特定的配方步骤。 隐藏,复制Code
<div class="alert alert-warning"> <p>Do you really want to delete this recipe step?</p> <p> {{recipeStep.stepNo}} - {{recipeStep.instructions}}</p> </div> <button ng-click="deleteRecipeStep()" class="btn btn-danger">Yes</button> <a href="/" class="btn btn-default">No</a>
2)配方物品模板 在addItem。html,使用ng-submit发送数据到服务器。当用户按下Save按钮时,调用一个作用域函数addRecipeItem,该函数在控制器的后台将向服务器提交这个recipe item对象。 隐藏,收缩,复制Code
<h1>Add a new recipe item</h1> <divclass="container-fluid"> <formng-submit="addRecipeItem()"> <divclass="row"> <divclass="form-group col-xs-4"> <labelfor="name">Name</label> <inputng-model="recipeItem.name"name="name"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-4"> <labelfor="quantity">Quantity</label> <inputng-model="recipeItem.quantity"name="quantity"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-4"> <labelfor="measurementUnit">Measurement Unit</label> <inputng-model="recipeItem.measurementUnit"name="measurementUnit"type="text"class="form-control"/> </div> </div> <divclass="row"> <buttontype="submit"class="btn btn-primary">Save</button> <ahref="/"class="btn btn-default">Cancel</a> </div> </form> </div>
html更新现有的菜谱项。使用ng-model指令将输入字段绑定到一个范围变量——一个对象recipeItem。此外,在项目编辑控制器上,有一个作用域函数- editRecipeItem。 隐藏,收缩,复制Code
<h1>Edit Recipe Item</h1> <divclass="container-fluid"> <formng-submit="editRecipeItem()"> <divclass="row"> <divclass="form-group col-xs-4"> <labelfor="name">Name</label> <inputng-model="recipeItem.name"name="name"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-4"> <labelfor="quantity"></label> <inputng-model="recipeItem.quantity"name="quantity"type="text"class="form-control"/> </div> </div> <divclass="row"> <divclass="form-group col-md-4 col-xs-4"> <labelfor="measurementUnit"></label> <inputng-model="recipeItem.measurementUnit"name="measurementUnit"type="text"class="form-control"/> </div> </div> <divclass="row"> <buttontype="submit"class="btn btn-primary">Save</button> <ahref="/"class="btn btn-default">Cancel</a> </div> </form> </div>
在deleteItem。html,我们会提供一个段落来进行确认。我们将提供一个按钮,它调用作用域函数deleteRecipeItem。它将向服务器提交一个请求,以删除特定的菜谱项。 隐藏,复制Code
<div class="alert alert-warning"> <p>Do you really want to delete this recipe item?</p> <p> {{recipeItem.name}} {{recipeItem.quantity}} {{recipeItem.measurementUnit}}</p> </div> <button ng-click="deleteRecipeItem()" class="btn btn-danger">Yes</button> <a href="/" class="btn btn-default">No</a>
一切都完成了。现在您可以创建、更新或删除菜谱了。你会成为一个真正的大厨。不仅仅是一个只遵循别人食谱的厨师。 IE缓存问题 最后,我想谈谈发生在IE上的一个缓存问题。如果我们把IIS Express改成IE,在我添加了一个新菜谱“roast Duck”之后,你不能马上看到我刚刚添加的新菜谱。它没有正确地插入吗?去数据库查一下,新食谱就在那里。看起来当返回到list时,AngularJS根本没有发送httpget请求到服务器,只是从缓存中获取结果。这就是为什么新的更新不会弹出。我们可以资源通过httpProvider解决这个问题。在AngularJS应用程序配置函数中注入httpProvider。然后将http默认缓存设置为false,并将http get请求头中的If-Modified-Since设置为0。 隐藏,收缩,复制Code
angular.module('masterChefApp').config(['$routeProvider', '$httpProvider', '$locationProvider', function ($routeProvider, $httpProvider, $locationProvider) { //disable http cache $httpProvider.defaults.cache = false; if (!$httpProvider.defaults.headers.get) { $httpProvider.defaults.headers.get = {}; } $httpProvider.defaults.headers.get['If-Modified-Since'] = '0'; ////////////////////////////////////////////////////////////////// $routeProvider .when('/', { templateUrl: 'partials/recipes.html', controller: 'recipesController' }) .when('/recipes/add', { templateUrl: 'partials/add.html', controller: 'recipesAddController' }) .when('/recipes/edit/:id', { templateUrl: 'partials/edit.html', controller: 'recipesEditController' }) .when('/recipes/delete/:id', { templateUrl: 'partials/delete.html', controller: 'recipesDeleteController' }) .when('/recipes/addStep/:id', { templateUrl: 'partials/addStep.html', controller: 'recipesAddStepController' }) .when('/recipes/editStep/:id', { templateUrl: 'partials/editStep.html', controller: 'recipesEditStepController' }) .when('/recipes/deleteStep/:id', { templateUrl: 'partials/deleteStep.html', controller: 'recipesDeleteStepController' }) .when('/recipes/addItem/:id', { templateUrl: 'partials/addItem.html', controller: 'recipesAddItemController' }) .when('/recipes/editItem/:id', { templateUrl: 'partials/editItem.html', controller: 'recipesEditItemController' }) .when('/recipes/deleteItem/:id', { templateUrl: 'partials/deleteItem.html', controller: 'recipesDeleteItemController' }); $locationProvider.html5Mode(true); }]);
然后我们再试一次。它像一个魅力。虽然我没有这个缓存问题在谷歌Chrome,我们仍然需要修复这个问题在IE,因为web应用程序应该工作在所有的浏览器。 结论 在本文中,我介绍了如何使用angular route创建SPA CRUD应用程序。我们还讨论了如何在单个服务器端Web API控制器中映射多个url。以及相应的,如何在单个客户端angular资源服务中映射不同的路由。从Maser Chef part 3开始,我们将在Angular2和EntityFramework Core上开始一个新的冒险。 本文转载于:http://www.diyabc.com/frontweb/news17322.html