您当前的位置: 首页 >  ar

寒冰屋

暂无认证

  • 4浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

使用Fluent NHibernate和AngularJS的Master Chef(第2部分)ASP.NET Core MVC

寒冰屋 发布时间:2020-03-28 15:00:30 ,浏览量:4

目录

在存储库中使用泛型

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;
}

然后我们使RecipeRecipeStepRecipeItem继承Entity和使用Id更换RecipeRecipeId,使用Id更换RecipeStepRecipeStepId和使用Id更换RecipeItemItemId。同时使用ParentId更换RecipeStepRecipeId和使用ParentId更换RecipeItemRecipeStepId

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()。这意味着什么?当我们更改RecipeStepRecipeItem时,其父对象缓存不知道此更改。我们需要刷新父对象缓存。

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

1)注入ngRoute

打开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并避免将哈希爆炸用于客户端路由。

Angular JS客户端控制器

我们已经配置了默认路由,添加路由,编辑路由和删除路由。然后,我们需要相应的控制器recipesControllerrecipesAddControllerrecipesEditControllerrecipesDeleteController。我们在recipesController.js中定义所有这些控制器。

1)注入“Add”,“Edit”和“Delete”控制器
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方法将配方提交给服务器。在提交配方后的回调中,我们仅要将应用程序重定向到其主页。

3)实现食谱Edit控制器
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来传递idrecipesEditController创建或提供允许某人将配方更新到应用程序的功能。我们将使用&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方法告诉服务器我们要摆脱特定的食谱。

部分视图模板 1)修改Index.html以使用ng-view

修改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}}
关注
打赏
1665926880
查看更多评论
0.1142s