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

628 篇文章 16 订阅
71 篇文章 3 订阅

目录

在存储库中使用泛型

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缓存问题

结论


Maser Chef第1部分中,我介绍了如何将ASP.NET Core MVCFluent NHibernateAngular JS集成。在本文中,我将讨论如何使用ASP.NET Core MVCFluent NHibernateAngular 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<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);
    }

现在,我们还需要更改映射类。请注意不同名称的映射。

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的子类。

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()。这意味着什么?当我们更改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<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);
}

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函数进入服务器并获取适当的配方——这次是提供IDget方法。这将提供给前端。用户将能够制造任何东西。

最后,我们将更新的配方记录提交到服务器。

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指令。

<!DOCTYPE html>
<html ng-app="masterChefApp">
<head>
    <base href="/">
    <meta charset="utf-8" />
    <title>Master Chef Recipes</title>
    <script src="lib/angular/angular.min.js"></script>
    <script src="lib/angular-resource/angular-resource.min.js"></script>
    <script src="lib/angular-route/angular-route.min.js"></script>
    <script src="app.js"></script>
    </head>
<body ng-cloak>
    <div>
        <ng-view></ng-view>
    </div>
</body>
</html>

基于此ng-view指令的使用以及我们已经设置的路由,ng-view将能够提供正确的局部视图以及正确的控制器,以便在任何客户端路由上使用$routeProvider为视图提供动力。

我们在app.js文件中指定了四个控制器。这些控制器为我们提供了CRUD操作。路径URL /将要从服务器检索所有配方。 /recipes/add将创建一个新配方。具有可变IDrecipes/edit将更新现有配方,并且具有可变ID/recipes/delete 也将从服务器中删除或移除特定配方。

现在,我们在wwwroot文件夹下创建“partials”文件夹。然后可以一个一个地添加模板。

2)检索模板——Recipes.html

右键单击wwwroot下的“partials”文件夹。添加一个新项目。在客户端模板部分中,选择“HTML页面。我们将其命名为recipes.html ”

recipes.html,它将检索并显示食谱列表。

<div>
    <h2>Master Chief Recipes</h2>
    <ul>
        <li ng-repeat="recipe in recipes">
            <div>
               <h5>{{recipe.name}} - {{recipe.comments}}</h5>
            </div>
            <div>
                <a href="recipes/edit/{{recipe.id}}">edit</a>
            </div>
            <div>
                <a href="recipes/delete/{{recipe.id}}">delete</a>
            </div>
            <ul>
                <li ng-repeat="step in recipe.steps">
                    <p> step {{step.stepNo}} : {{step.instructions}}</p>
                    <ul>
                        <li ng-repeat="item in step.recipeItems">
                            <p> {{item.name}}  {{item.quantity}} {{item.measurementUnit}}</p>
                        </li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
    <p><a href="recipes/add"> Add a new recipe </a></p>
</div>

请注意,这不是完整的html。我们只是在定义一个局部视图,该视图将在AngularJS应用程序中替换。

现在,如果我们运行它,我们应该看到所有配方。

3)Bootstrap 样式

尽管它可以正常工作,但是它是完全纯HTML。因此,我们需要应用一些CSS样式。

Bootstrap是一个非常流行的前端框架,它包括用于排版、表单、按钮、表格、导航、模式、图像轮播等的基于HTMLCSS的设计模板,以及可选的JavaScript插件。应用bootstrap样式可以使我们的Master Web应用程序更漂亮。

我们已经在Bower配置中添加了bootstrap软件包。

{
	"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"
  }
}

因此,引导程序已经安装在wwwroot\lib文件夹中。现在我们将其包含在index.html

<link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" media="screen">

我们将应用以下bootstrap样式。

我们将index.html中的主div.container-fluidfull-width)一起使用,以实现正确的对齐和填充。

我们在cipes.html中将全部.list-group用于ui .list-group-item用于li 。我们还对添加链接应用btn-primary,对编辑链接应用btn-default,对删除链接应用btn-delete。我也想将食谱显示为徽章,所以也要应用.badge样式。

再次运行Master Chef,看看现在是什么样。

Bootstrap包括功能强大的移动优先网格系统,用于构建各种形状和尺寸的布局。它基于12列布局,并具有多层,每个层对应一个媒体查询范围。有三个主要组件——容器、行和列。容器——固定宽度的.container或全宽度的.container-fluid——将站点内容居中,并帮助对齐网格内容。行是水平的列组,可确保您的列正确排列。Column类表示每行可能使用的12列中的列数。因此,如果要三个相等宽度的列,则可以使用.col-xs-4

我们在Master Chef模板中使用bootstrap网格系统。

4)使用Angular JS进行扩展/折叠

我知道有很多方法可以与jQuery进行扩展/折叠以更改DOM。但是请记住,我们使用的是AngularJSMVVM模式。因此,我很想通过更改控制器(视图模型)中的模型来实现扩展/折叠。

recipesController中添加expand()函数。在expand()函数中,设置配方对象的show属性。

recipesController.$inject = ['$scope', 'Recipe'];

    function recipesController($scope, Recipe) {
        $scope.recipes = Recipe.query();
        $scope.expand = function (recipe) {
            recipe.show = !recipe.show;
        }
}

我们在recipesController中添加了ng-click调用expand()函数。

<div class="btn-group">
               <button class="btn badge pull-left" ng-click="expand(recipe)"><h5>{{recipe.name}} - {{recipe.comments}}</h5></button>
</div>

然后,我们使用ng-show来控制是否显示食谱的详细信息。

<ul class="list-group" ng-show="recipe.show">
                <li ng-repeat="step in recipe.steps" class="list-group-item">

只需单击配方标记即可展开您想要的外观。

5)创建模板——add.html

右键单击wwwroot下的partials文件夹。添加一个新项目。在客户端模板部分中,选择“HTML页面。我们将其命名为“add.html”

add.html,使用ng-submit将数据发布到服务器。我们将通过ng-model指令将用户在输入字段中输入的信息绑定到范围变量配方。当用户通过单击保存按钮使用我们的表单提交时,我们将调用一个作用域函数addRecipe,该函数在我们控制器的后台会将该配方对象提交给服务器。

<h1>Add a new recipe</h1>
<div class="container-fluid">
    <form ng-submit="addRecipe()">
        <div class="row">
            <div class="form-group col-xs-4">
                <label for="name">Name</label>
                <input ng-model="recipe.name" name="name" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <div class="form-group col-md-4 col-xs-8">
                <label for="comments">Comments</label>
                <input ng-model="recipe.comments" name="comments" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <button type="submit" class="btn btn-primary">Save</button>
            <a href="/" class="btn btn-default">Cancel</a>
        </div>
    </form>
</div>

6)编辑模板——edit.html

右键单击wwwroot下的partials文件夹。添加一个新项目。在客户端模板部分中,选择“HTML页面。我们命名为edit.html ”

现在我们要更新配方。我们将在edit.html部分模板中进行处理。edit.html看起来像add.html因为我们需要为最终用户提供所有必要的字段以实际更新现有配方。我们有配方.name和配方.comments的输入。它们通过ng-model指令绑定到范围变量(对象配方)。另外,在编辑控制器上,还有一个作用域函数——editRecipe。因此,当用户按下edit.htmlSave按钮时,该函数将被调用,而该函数的工作就是将更新的配方信息提交给服务器以进行持久存储。

<h1>Edit recipe</h1>
<div class="container-fluid">
    <form ng-submit="editRecipe()">
        <div class="row">
            <div class="form-group col-xs-4">
                <label for="name">Name</label>
                <input ng-model="recipe.name" name="name" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <div class="form-group col-md-4 col-xs-8">
                <label for="comments">Comments</label>
                <input ng-model="recipe.comments" name="comments" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <button type="submit" class="btn btn-primary">Save</button>
            <a href="/" class="btn btn-default">Cancel</a>
        </div>
    </form>
</div>

7)删除模板

右键单击wwwroot下的partials文件夹。添加一个新项目。在客户端模板部分中,选择“HTML页面。我们给它起一个名字delete.html ”

delete.html,我们将提供一个段落进行确认。那么,您真的要删除此食谱吗?我们将绑定到要删除的配方相关的配方信息。我们将提供一个按钮,它调用范围函数——deleteRecipe。它将向服务器提交删除特定配方的请求。

<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控制器来处理配方步骤和配方项目。但这太重了。我想在RecipesController中包装所有与食谱相关的Restful服务。但是对于配方步骤操作和配方项目操作,肯定需要不同的URL。幸运的是,ASP.NET Core Web API支持不同的路由。路由是Web APIURI与操作匹配的方式。Web API支持一种新型的路由,称为属性路由。顾名思义,属性路由使用属性来定义路由。通过属性路由,您可以更好地控制Web API中的URI。例如,您可以轻松创建描述资源层次结构的URI

Web控制器类的route属性是基本URI

[Route("api/[controller]")]
    public class RecipesController : Controller
{
….
}

对于RecipesController,基本URL/api/recipes

[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

但是看来我们需要不同的URLget step方法和get item方法。我想获取步骤URL/api/recipes/step/:id,获取项目URL/api/recipes/item/:id。因此,我们为get step方法添加[Route("step/{id}")]和为get item方法添加[Route("item/{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();

        }
[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应用程序。首先,我们检查URLapi/recipes/step/AEE9602B-03EF-4A5F-A380-2962134ADB7E

它按预期工作。

然后我们检查api/recipes/item/862B91D5-FB60-4004-8179-0415AB900795

它也在工作。

我们还需要为发布和删除添加Route属性。

//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。到目前为止,我们仅使用默认操作。

{
  get: {method: 'GET'},
  save: {method: 'POST'},
  query: {method: 'GET', isArray: true},
  remove: {method: 'DELETE'},
  delete: {method: 'DELETE'}
}

上面的动作是内置在ng资源中的,因此我们可以直接使用它。

recipesService.factory('Recipe', ['$resource', function ($resource) {
      return $resource('/api/recipes/:id');
  }]);

但是,我们现在需要定义自己的自定义动作,并为该动作指定默认URL的不同URL

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' }
        });
}]);

我们仍然使用默认操作的配方,并添加新的自定义操作getRecipeStepsaveRecipeStepremoveRecipeStepgetRecipeItemsaveRecipeItemremoveRecipeItem

所有URL均与配方步骤和配方项目的Web API URL匹配。

为配方步骤和配方项目添加新的Angular路由

现在我们需要添加新的客户端路径,用于在app.js中创建、更新、删除配方步骤和配方项创建、更新、删除模板和控制器。

$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中注入步骤和项目控制器。

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

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

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并检索特定的配方步骤。然后提供此函数删除配方步骤到应用程序。

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

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服务来更新配方项目。通过路由参数获取配方项的ID

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并检索特定的配方项目。然后提供此函数删除配方项到应用程序。

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('/');
            });
        };
}

添加配方步骤和配方项目的所有模板

现在,我们需要为配方步骤和配方项目创建所有模板。在partials文件夹中创建addStep.htmleditStep.htmldeleteStep.htmladdItem.htmleditItem.htmldeleteItem.html

1)配方步骤模板

addStep.html,使用ng-submit将数据发布到服务器。当用户按下保存按钮时,调用一个范围函数addRecipeStep,该范围函数将在我们控制器的后台将此配方步骤对象提交给服务器。

<h1>Add a new recipe step</h1>
<div class="container-fluid">
    <form ng-submit="addRecipeStep()">
        <div class="row">
            <div class="form-group col-xs-1">
                <label for="stepNo">Step No.</label>
                <input ng-model="recipeStep.stepNo" name="stepNo" type="text" class="form-control" />
            </div>
        </div>

        <div class="row">
            <div class="form-group col-md-4 col-xs-8">
                <label for="instructions">Instructions</label>
                <input ng-model="recipeStep.instructions" name="instructions" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <button type="submit" class="btn btn-primary">Save</button>
            <a href="/" class="btn btn-default">Cancel</a>
        </div>
    </form>
</div>

editStep.html更新现有的配方步骤。使用ng-model指令将输入字段绑定到范围变量(对象recipeStep)。此外,在步骤编辑控制器上,还有一个范围函数——editRecipeStep

<h1>Edit Recipe Step</h1>
<div class="container-fluid">
    <form ng-submit="editRecipeStep()">
        <div class="row">
            <div class="form-group col-xs-1">
                <label for="stepNo">Step No.</label>
                <input ng-model="recipeStep.stepNo" name="stepNo" type="text" class="form-control" />
            </div>
        </div>

        <div class="row">
            <div class="form-group col-md-4 col-xs-8">
                <label for="instructions">Instructions</label>
                <input ng-model="recipeStep.instructions" name="instructions" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <button type="submit" class="btn btn-primary">Save</button>
            <a href="/" class="btn btn-default">Cancel</a>
        </div>
    </form>
</div>

deleteStep.html,我们将提供一个段落进行确认。我们将提供一个按钮,它调用范围函数——deleteRecipeStep。它将向服务器提交删除特定配方步骤的请求。

<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将数据发布到服务器。当用户按下保存按钮时,调用一个范围函数addRecipeItem,该范围函数将在我们控制器的后台将此配方项目对象提交给服务器。

<h1>Add a new recipe item</h1>
<div class="container-fluid">
    <form ng-submit="addRecipeItem()">
        <div class="row">
            <div class="form-group col-xs-4">
                <label for="name">Name</label>
                <input ng-model="recipeItem.name" name="name" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <div class="form-group col-md-4 col-xs-4">
                <label for="quantity">Quantity</label>
                <input ng-model="recipeItem.quantity" name="quantity" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <div class="form-group col-md-4 col-xs-4">
                <label for="measurementUnit">Measurement Unit</label>
                <input ng-model="recipeItem.measurementUnit" name="measurementUnit" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <button type="submit" class="btn btn-primary">Save</button>
            <a href="/" class="btn btn-default">Cancel</a>
        </div>
    </form>
</div>

editItem.html更新现有的配方项目。使用ng-model指令将输入字段绑定到范围变量(对象recipeStep)。此外,在项目编辑控制器上,还有一个范围函数——editRecipeItem

<h1>Edit Recipe Item</h1>
<div class="container-fluid">
    <form ng-submit="editRecipeItem()">
        <div class="row">
            <div class="form-group col-xs-4">
                <label for="name">Name</label>
                <input ng-model="recipeItem.name" name="name" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <div class="form-group col-md-4 col-xs-4">
                <label for="quantity"></label>
                <input ng-model="recipeItem.quantity" name="quantity" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <div class="form-group col-md-4 col-xs-4">
                <label for="measurementUnit"></label>
                <input ng-model="recipeItem.measurementUnit" name="measurementUnit" type="text" class="form-control" />
            </div>
        </div>
        <div class="row">
            <button type="submit" class="btn btn-primary">Save</button>
            <a href="/" class="btn btn-default">Cancel</a>
        </div>
    </form>
</div>

deleteItem.html,我们将提供一个段落进行确认。我们将提供一个调用范围函数——deleteRecipeItem的按钮。它将向服务器提交删除特定配方项目的请求。

<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使用,则在添加新配方烤鸭后,您将看不到我立即添加的新配方。插入不正确吗?去检查数据库,新配方在那里。看起来,当返回列表时,AngularJS根本没有向服务器发送http get请求,而只是从缓存中获取结果。这就是为什么不弹出新更新的原因。我们可以通过httpProvider解决此问题。在AngularJS应用程序配置函数中注入httpProvider。然后在http get请求标头中将http默认缓存设置为false,并将If-Modified-Since设置为0

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

    }]);

然后,我们再试一次。它像一种魅力。尽管我在Google Chrome浏览器中没有此缓存问题,但我们仍然需要在IE上解决此问题,因为Web应用程序应可在所有浏览器上使用。

结论

在本文中,我介绍了如何使用Angular路由制作SPA CRUD应用程序。我们还讨论了如何在单个服务器端Web API控制器中映射多个URL。相应地,如何在单个客户端Angular资源服务中映射不同的路由。从  Maser Chef第3部分 开始,我们将在Angular2EntityFramework Core上进行新的冒险。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值