目录
在存储库中使用泛型
Angular客户端路由
1)注入ngRoute
2)配置Angular路由
Angular JS客户端控制器
1)注入“Add”,“Edit”和“Delete”控制器
2)实现食谱Add控制器
3)实现食谱Edit控制器
4)实现食谱Delete控制器
部分视图模板
1)修改Index.html以使用ng-view
2)检索模板——Recipes.html
3)Bootstrap 样式
4)使用Angular JS进行扩展/折叠
5)创建模板——add.html
6)编辑模板——edit.html
7)删除模板
多个URL映射到同一Web API控制器
单个Angular资源服务的多个路由URL
为配方步骤和配方项目添加新的Angular路由
为配方步骤和配方项添加新的Angular控制器
添加配方步骤和配方项目的所有模板
1)配方步骤模板
2)食谱项目模板
IE缓存问题
结论
- 下载源71.3 KB
在Maser Chef第1部分中,我介绍了如何将ASP.NET Core MVC与Fluent NHibernate和Angular JS集成。在本文中,我将讨论如何使用ASP.NET Core MVC,Fluent NHibernate和Angular JS来实现CRUD SPA(单页应用程序)。
在存储库中使用泛型创建、读取、更新和删除(缩写为CRUD)是持久性存储的四个基本功能。
我们首先需要在我们的存储库类中的数据库级别实现CRUD。我想将泛型用于查询,添加、更新、删除方法,以避免冗余编码。为什么要使用泛型?简短的答案是类型安全的编译时检查,速度更快,适用于具有相同基础行为的许多类型。
在以前的数据模型类中,所有成员的名称与数据库字段的名称相同。实际上,数据模型类成员不必与数据库字段相同。例如,Recipe类的ID不必是RecipeId,它可以是任何名称,例如Id。我们需要做的是在映射过程中告诉Fluent NHibernate,如下所示。
Id(x => x.Id, "RecipeId");
这样Fluent NHibernate知道它正在将“Id
” 映射到“RecipeId
”。
因为我们不必使用与数据库字段相同的名称,所以现在我们有机会更改不同的数据模型类以具有一些公共成员。
因此,我们创建了一个Entity
基类。
public class Entity
{
public virtual Guid Id { get; set; }
public virtual Guid? ParentId { get; set; }
public virtual Type ParentType => null;
}
然后我们使Recipe
,RecipeStep
和RecipeItem
继承Entity
和使用Id
更换Recipe
的RecipeId
,使用Id
更换RecipeStep
的RecipeStepId
和使用Id
更换RecipeItem
的ItemId
。同时使用ParentId
更换RecipeStep
的RecipeId
和使用ParentId
更换RecipeItem
的RecipeStepId
。
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 Steps { get; set; }
}
public class RecipeStep : Entity
{
public virtual int StepNo { get; set; }
public virtual string Instructions { get; set; }
public virtual IList 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);
}
现在,我们还需要更改映射类。请注意不同名称的映射。
public class RecipeMap : ClassMap
{
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
{
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
{
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的子类。
public T GetEntity(Guid id) where T : Entity
{
try
{
return _session.Get(id);
}
catch (Exception ex)
{
throw ex;
}
}
public T AddEntity(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(entity.Id) as T;
}
catch (Exception ex)
{
throw ex;
}
return newOne;
}
}
public void UpdateEntity(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(Guid id) where T : Entity
{
using (var transaction = _session.BeginTransaction())
{
var entity = _session.Get(id);
if (entity != null)
{
try
{
_session.Delete(entity);
Commit(transaction, entity);
RefreshParentObject(entity);
}
catch (Exception ex)
{
throw ex;
}
}
}
}
对于添加、更新和删除方法,所有这些都调用RefreshParentObject()
。这意味着什么?当我们更改RecipeStep
或RecipeItem
时,其父对象缓存不知道此更改。我们需要刷新父对象缓存。
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控制器。
[HttpGet("{id}")]
public IActionResult Get(Guid id)
{
var recipe = _repository.GetEntity(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));
}
else
{
var existingOne = _repository.GetEntity(recipe.Id);
existingOne.Name = recipe.Name;
existingOne.Comments = recipe.Comments;
existingOne.ModifyDate = DateTime.Now;
_repository.UpdateEntity(existingOne);
return new ObjectResult(existingOne);
}
}
[HttpPut("{id}")]
public IActionResult Put(Guid id, [FromBody]Recipe recipe)
{
var existingOne = _repository.GetEntity(recipe.Id);
existingOne.Name = recipe.Name;
existingOne.Comments = recipe.Comments;
_repository.UpdateEntity(recipe);
return new ObjectResult(existingOne);
}
[HttpDelete("{id}")]
public IActionResult Delete(Guid id)
{
_repository.DeleteEntity(id);
return new StatusCodeResult(200);
}
Angular客户端路由
现在,我们需要在Master Chef应用程序中设置客户端路由,以便可以根据客户端提供的URL替换动态视图。我们可以从angular-route模块中获取Angular路由功能。
使用ngRoute
模块,您可以在单个页面应用程序中导航到不同的页面,而无需使用页面reloading.$route
来深度链接url到控制器和视图(HTML部分)。它监视$location.url()
并尝试将路径映射到现有路由定义。
有两个依赖$route
,$location
和$routeParams
。
打开app.js,在我们的masterChefApp
模块中注入ngroute
。
(function () {
'use strict';
angular.module('masterChefApp', [
// Angular modules
'ngRoute',
// Custom modules
'recipesService'
// 3rd Party Modules
]);
})();
2)配置Angular路由
为我们的Angular应用程序模块定义配置函数——masterChefApp
。并且,在该配置函数中,使用来自ngRoute
模块的路由提供程序服务来定义客户端路由
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,该ID可与一种配方匹配。最后一条路由/recipes/delete/:id也需要采用动态ID参数。该默认途径只是列出所有食谱。“Add
”路由将用于添加,“Edit
”路由将用于编辑或更新,“Delete
“路由将要删除或移除。CRUD函数由这四个客户端路由表示。对于每个路由,我们都需要定义一个模板URL(表示应为此路由呈现的一些HTML)以及还有一个单独的控制器来处理这条路由。
在最底端,使用$locationProvider
,它的html5Mode
函数,
设置为true以确保我可以使用友好自然的URL并避免将哈希爆炸用于客户端路由。
我们已经配置了默认路由,添加路由,编辑路由和删除路由。然后,我们需要相应的控制器recipesController
,recipesAddController
,recipesEditController
和recipesDeleteController
。我们在recipesController.js中定义所有这些控制器。
angular
.module('masterChefApp')
.controller('recipesController', recipesController)
.controller('recipesAddController', recipesAddController)
.controller('recipesEditController', recipesEditController)
.controller('recipesDeleteController', recipesDeleteController);
2)实现食谱Add控制器
recipesAddController.$inject = ['$scope', 'Recipe', '$location'];
function recipesAddController($scope, Recipe, $location) {
$scope.recipe = new Recipe();
$scope.addRecipe = function () {
$scope.recipe.$save(function () {
$location.path('/');
});
}
}
因此recipesAddController
需要$scope
和配方服务,它也需要$location
服务。recipesAddController
创建或提供允许某人向应用程序添加配方的功能。为此,请使用“配方”服务创建一个新的$scope
变量配方。它还在此处创建一个$scope
函数——addRecipe
,它将通过使用配方服务save方法将配方提交给服务器。在提交配方后的回调中,我们仅要将应用程序重定向到其主页。
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
需要$scope
和配方服务,$location
服务。它也需要$routeParameter
来传递id
。recipesEditController
创建或提供允许某人将配方更新到应用程序的功能。我们将使用&routeParams
服务来更新配方。从route参数获取配方ID。然后,我们将通过调用配方服务get函数进入服务器并获取适当的配方——这次是提供ID的get方法。这将提供给前端。用户将能够制造任何东西。
最后,我们将更新的配方记录提交到服务器。
4)实现食谱Delete控制器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
,在这里我们可以使用Recipe服务的$remove
方法告诉服务器我们要摆脱特定的食谱。
修改index.html以使用部分视图。首先向/添加一个“基本”标签及其href属性。这是必需的,这样$locationProvider
才能正常工作,因为它需要一个基础才能工作。现在转到正文内容。摆脱所有这些,只需使用ng-view指令。
Master Chef Recipes
基于此ng-view指令的使用以及我们已经设置的路由,ng-view将能够提供正确的局部视图以及正确的控制器,以便在任何客户端路由上使用$routeProvider
为视图提供动力。
我们在app.js文件中指定了四个控制器。这些控制器为我们提供了CRUD操作。路径URL /将要从服务器检索所有配方。 /recipes/add将创建一个新配方。具有可变ID的recipes/edit将更新现有配方,并且具有可变ID的/recipes/delete 也将从服务器中删除或移除特定配方。
现在,我们在wwwroot文件夹下创建“partials”文件夹。然后可以一个一个地添加模板。
2)检索模板——Recipes.html右键单击wwwroot下的“partials”文件夹。添加一个新项目。在“客户端模板”部分中,选择“HTML页面”。我们将其命名为“recipes.html ”。
recipes.html,它将检索并显示食谱列表。
Master Chief Recipes
-
{{recipe.name}} - {{recipe.comments}}
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录