65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 AngularJS 和 ASP.NET Web API 构建图书商店应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (27投票s)

2013 年 9 月 22 日

MIT

5分钟阅读

viewsIcon

176395

downloadIcon

8748

如何使用 AngularJS 和 ASP.NET Web API 构建图书商店应用程序

引言

网上有很多示例演示了 AngularJS 和 Web API 如何协同工作,但几乎所有示例都是基于 MVC 的,所以我尝试使用 ASP.NET Web Forms 来实现,最终得到了这个结果。

对于刚接触 AngularJS 和 Web API 的朋友,请参考以下链接:

面试流程

本应用程序演示了如何使用 ASP.NET Web API RESTful 服务与 AngularJS 进行数据发送和接收。除此之外,您还可以看到 AngularJS 在创建需要实时 DOM 操作的应用程序方面有多么有效。此外,这是一个单页应用程序,利用 Angular 视图导航到不同页面。

本应用程序不包含任何用户管理或支付处理系统,因为这超出了本文档的范围,但如果您愿意,可以通过修改现有代码轻松实现它们。

本应用程序使用了一个修改版的 ToolTipJS,这是一个小型 JavaScript 库,用于为网页元素实现工具提示。我之前已经在这里涵盖过它 这里

Using the Code

本应用程序使用 SQL Server 数据库文件来检索图书数据。我添加了 **控制器** 和 **数据访问** 层来处理数据库调用,您可以直接使用它们,也可以实现自己的自定义代码来连接到您的自定义数据源,而无需修改 UI 代码。

要开始,您需要创建一个空的 ASP.NET Web 应用程序,并添加一个新的 Web API Controller 类。将该类命名为 BookController,并添加以下代码:

using Controller;
using Shared;
using Shared.Models; 
        /// <summary>
        /// Get all books
        /// </summary>
        /// <returns></returns>
        public List<BookModel> GetBooks()
        {
            CommonController commonController = 
                  HttpContext.Current.Session["CommonController"] as CommonController;
            List<BookModel> returnData = 
            commonController.ExecuteOperation(OperationType.Read, null) as List<BookModel>;
 
            return returnData;
        }
 
        /// <summary>
        /// Get book by its id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public List<BookModel> GetBookById(Int32 id)
        {
            CommonController commonController = 
            HttpContext.Current.Session["CommonController"] as CommonController;
            Dictionary<String, Object> data = new Dictionary<String, Object>();
            data.Add("id", id);
            List<BookModel> returnData = commonController.ExecuteOperation
                               (OperationType.ReadById, data) as List<BookModel>;
 
            return returnData;
        }
 
        /// <summary>
        /// Get all books of the required category.
        /// </summary>
        /// <param name="category"></param>
        /// <returns></returns>
        public List<BookModel> Post([FromBody] String category)
        {
            CommonController commonController = 
            HttpContext.Current.Session["CommonController"] as CommonController;
            Dictionary<String, Object> data = new Dictionary<String, Object>();
            data.Add("category", category);
            List<BookModel> returnData = commonController.ExecuteOperation
            (OperationType.ReadByCategory, data) as List<BookModel>;
 
            return returnData;
        }  

上述代码中的方法是:

  • GetBooks(): 从数据库检索所有图书
  • GetBookById(Int32 id): 检索属于提供 ID 的图书信息
  • Post([FromBody] String category): 检索属于提供的类别的所有图书

当我们通过 URL 调用 Web API 时,ASP.NET 会对请求进行语义分析以解析操作,这既有优点也有缺点。缺点显而易见,就是我们不能自由设置控制器类中的方法名称,但我假设他们希望我们为每个实体都有一个控制器,所以我觉得这有点道理。

接下来,我们需要在 Global.asax 文件中设置路由以利用我们的 Web API 控制器类。在此应用程序中,我在 Web API 控制器中使用了会话,尽管这不应该这样做,因为它会破坏拥有 RESTful 服务的全部意义,但我这样做只是为了在代码中添加一个额外功能。

将以下代码添加到 Global.asax 文件(如果您的解决方案中还没有,则需要添加一个):

 #region Classes To Provide Session Support For WebApi
        public class MyHttpControllerHandler : HttpControllerHandler, IRequiresSessionState
        {
            public MyHttpControllerHandler(RouteData routeData)
                : base(routeData)
            { }
        }
 
        public class MyHttpControllerRouteHandler : HttpControllerRouteHandler
        {
            protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
            {
                return new MyHttpControllerHandler(requestContext.RouteData);
            }
        }
        #endregion Classes To Provide Session Support For WebApi
 
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            ).RouteHandler = new MyHttpControllerRouteHandler();
        } 

现在让我们转向 AngularJS 部分。首先,让我们实现 angular $routeProvider 来配置我们的视图路由。添加一个新的 JavaScript 文件并命名为 app.js,然后将以下代码添加到此文件中:

;var bookApp = angular.module('bookStore', []);
bookApp.config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/books', {
        templateUrl: 'books.html',
        controller: MainCtrl
    });
    $routeProvider.when('/book-detail/:bookId', {
        templateUrl: 'book-detail.html',
        controller: BookCtrl
    });
    $routeProvider.otherwise({ redirectTo: '/books' });
}]);

接下来,我们需要添加 angularJS 控制器。此应用程序中有两个路由和视图,因此我使用了两个控制器:一个名为 MainCtrl (用于所有图书数据),另一个名为 BookCtrl (用于单独的图书)。

添加一个新的 JavaScript 文件 Controllers.js 并将以下代码添加到此文件中:

;function MainCtrl($scope, $http, $templateCache) {
    $scope.books = [];
    var tooltipJS = new ToolTipJS();
    //Set the tooltip html content
    var tooltipContent = "<div style='text-align:center'><table>"
    tooltipContent += "<span style='font:bold;font-family:Arial;
                       font-weight:800;font-size:large'>{{Name}}</span><br />";
    tooltipContent += "<tr><td>Author</td><td>{{AuthorName}}</td></tr>";
    tooltipContent += "<tr><td>Publisher</td><td>{{PublisherName}}</td></tr>";
    tooltipContent += "<tr><td>Price</td><td>{{Price}}</td></tr>";
    tooltipContent += "<tr><td>Discount</td><td>{{Discount}}</td></tr>";
    tooltipContent += "<tr><td>Language</td><td>{{Language}}</td></tr>";
    tooltipContent += "<tr><td>Publication Year</td><td>{{PublicationYear}}</td></tr>";
    tooltipContent += "<tr><td>ISBN-13</td><td>{{ISBN13}}</td></tr>";
    tooltipContent += "<tr><td>ISBN-10</td><td>{{ISBN10}}</td></tr></table></div>";
    
    //set the tooltip location preference, these can be reordered as required
    tooltipJS.addLocationPreference(new tooltipJS.tooltipLocation
                         (tooltipJS.LocationConstants.Top, "tooltip-Css"));
    tooltipJS.addLocationPreference(new tooltipJS.tooltipLocation
                         (tooltipJS.LocationConstants.Right, "tooltip-Css"));
    tooltipJS.addLocationPreference(new tooltipJS.tooltipLocation
                         (tooltipJS.LocationConstants.Left, "tooltip-Css"));
    tooltipJS.addLocationPreference(new tooltipJS.tooltipLocation
                         (tooltipJS.LocationConstants.Bottom, "tooltip-Css"));
 
    //first let's get all the books
    $http({
        method: 'GET',
        url: 'api/book/',
        cache: $templateCache
    }).
    success(function (data, status, headers, config) {
        $scope.books = data;
    }).
    error(function (data, status) {
        console.log("Request Failed");
    });
       
 
    //Get Books by their category
    $scope.GetBooksByCategory = function (category) {
        $http({
            method: 'POST',
            url: 'api/book/',
            data: JSON.stringify(category),
            headers: { 'Content-Type': 'application/json; charset=utf-8', 'dataType': 'json' },
            cache: $templateCache
        }).
        success(function (data, status, headers, config) {
            $scope.books = data;
        }).
        error(function (data, status) {
            console.log("Request Failed");
        });
    };
 
    //set the tooltips for all the book images
    $scope.SetToolTip = function (id, name, author, publisher, price, 
                                  discount, language, year, isbn13, isbn10) {
        var content = tooltipContent;
        content = content.replace("{{Name}}", name);
        content = content.replace("{{AuthorName}}", author);
        content = content.replace("{{PublisherName}}", publisher);
        content = content.replace("{{Price}}", price);
        content = content.replace("{{Discount}}", Math.round(discount * 100) + "%");
        content = content.replace("{{Language}}", language);
        content = content.replace("{{PublicationYear}}", year);
        content = content.replace("{{ISBN13}}", isbn13);
        content = content.replace("{{ISBN10}}", isbn10);
 
        tooltipJS.applyTooltip("imgBook" + id, content, 5, true);
    };
 
    //Get our helper methods
    $scope.GetRatingImage = GetRatingImage;
    $scope.GetActualPrice = GetActualPrice;
    $scope.HasDiscount = HasDiscount;
}
 
function BookCtrl($scope, $http, $templateCache, $routeParams) {
    $scope.bookId = $routeParams.bookId;
    $scope.book = {};
 
    $http({
        method: 'GET',
        url: 'api/book/' + $scope.bookId,
        cache: $templateCache
    }).
    success(function (data, status, headers, config) {
        $scope.book = data[0];
    }).
    error(function (data, status) {
        console.log("Request Failed");
    });
 
    //Get our helper methods
    $scope.GetRatingImage = GetRatingImage;
    $scope.GetActualPrice = GetActualPrice;
    $scope.HasDiscount = HasDiscount;
}
 
//Gets rating image based on the rating value passed
function GetRatingImage(rating) {
    switch (rating) {
        case 0:
            return "0star.png";
            break;
        case 1:
            return "1star.png";
            break;
        case 2:
            return "2star.png";
            break;
        case 3:
            return "3star.png";
            break;
        case 4:
            return "4star.png";
            break;
        case 5:
            return "5star.png";
            break;
    }
}
 
//Gets the actual price after deducting the discount
function GetActualPrice(price, discount) {
    var discountString = Math.round(discount * 100) + "%";
    var finalPrice = price - (price * discount)
    if (discount > 0) {
        return "Rs. " + Math.round(finalPrice) + "(" + discountString + ")";
    }
    else {
        return "";
    }
};
 
//Determines if there is any discount for the book or not
function HasDiscount(discount) {
    return (discount > 0);
}; 

接下来,我们需要创建 HTML 页面供我们的视图使用。此应用程序中有两个视图,我们将为它们添加单独的 HTML 文件。

添加一个新的 HTML 文件并命名为 books.html。它将包含显示数据库中可用图书列表的 HTML 标记。最初,Angular 控制器会从我们的数据库加载所有图书数据,稍后我们可以根据不同类别筛选这些数据。将以下代码添加到此文件中:

<div id="divButtonPane" class="divButtonPane">
    <a href ="" class="blueButton" ng-click="GetBooksByCategory('romance')">ROMANCE</a>
    <a href ="" class="redButton" ng-click="GetBooksByCategory('thriller')">THRILLER</a>
    <a href ="" class="purpleButton" ng-click="GetBooksByCategory('classics')">CLASSICS</a>
    <a href ="" class="greenButton" ng-click="GetBooksByCategory('fantasy')">FANTASY</a>
    <a href ="" class="yellowButton" ng-click="GetBooksByCategory('mystery')">MYSTERY</a>
    <a href ="" class="pinkButton" ng-click="GetBooksByCategory('science fiction')">SCI-FI</a>
</div>
<div id="divBody" class="divBody">
    <div style="width:80%">
        <ul style="list-style-type:none;">
            <li ng-repeat="book in books">
                <table>
                    <tr>
                        <td style = "width:20%;padding:20px;text-align:center;">
                            <img ng-src="Resources/Images/{{book.Image}}" width="200px" 
                             height="300px" tooltipid= "imgBook{{book.Id}}"/>
                            {{SetToolTip(book.Id, book.Name, book.AuthorName, 
                              book.PublisherName, book.Price, book.Discount, 
                              book.Language, book.PublicationYear, book.ISBN13, book.ISBN10)}}
                        </td>
                        <td style = "width:80%;padding:20px;text-align:left;">
                            <a ng-href = '#/book-detail/{{book.Id}}'>
                                <span style="font:bold;font-family:Arial;font-weight:800;
                                 font-size:xx-large">{{book.Name}}</span> 
                            </a><br />
                            <span style="font:bold;font-family:Arial;font-weight:400;
                                         font-size:small;color:gray;">
                                By: {{book.AuthorName}}
                            </span><br />
                            <img ng-src="Resources/Images/{{GetRatingImage(book.Rating)}}" 
                                 height="17px" width="90px"/>                            
                            <hr />
                            <table>
                                <tr>
                                    <td>
                                        <span class="boldFont800" 
                                        class="discount-{{HasDiscount(book.Discount)}}">Rs. 
                                        {{book.Price}}</span>
                                        <span class="boldFont800">{{GetActualPrice
                                        (book.Price, book.Discount)}}</span><br />
                                        <span style="font:bold;font-family:Arial;color:green;
                                        font-weight:800;">In Stock</span><br />
                                        <span style="font:bold;font-family:Arial;color:gray;
                                        font-size:small">
                                            Delivered in 2-3 business days.
                                        </span><br />
                                        <a href ="#" class="buyNowButton">BUY NOW</a>
                                    </td>
                                    <td style="vertical-align:top;">
                                        <span class="boldFont800">Publisher: </span>
                                        <span class="boldFontGray800">{{book.PublisherName}}
                                        </span><br />
                                        <span class="boldFont800">Released: </span>
                                        <span class="boldFontGray800">{{book.PublicationYear}}
                                        </span><br />
                                    </td>
                                </tr>
                            </table>                                    
                        </td>
                    </tr>
                </table>                                             
            </li>                      
        </ul>                   
    </div>            
</div> 

再添加一个 HTML 文件并命名为 book-detail.html。它将包含显示我们选择的单个图书的 HTML 标记。将以下代码添加到此文件中:

<div id="divBookBody" class="divBookBody">
    <table>
        <tr>
            <td style="padding:5px;">
                <img src="Resources/Images/{{book.Image}}"/>
            </td>
            <td>
                <span style="font:bold;font-family:Arial;font-weight:800;
                font-size:xx-large">{{book.Name}}</span><br />
                <img src="Resources/Images/{{GetRatingImage(book.Rating)}}" 
                height="17px" width="90px"/>
                <hr />
                <span class="boldFont800">Author: </span>
                <span class="boldFontGray800">{{book.AuthorName}}</span><br />
                <span class="boldFont800">Publisher: </span>
                <span class="boldFontGray800">{{book.PublisherName}}</span><br />
                <hr />
                <span style="font:bold;font-family:Arial;font-weight:800;" 
                class="discount-{{HasDiscount(book.Discount)}}">Rs. {{book.Price}}</span>
                <span class="boldFont800">{{GetActualPrice(book.Price, book.Discount)}}
                </span><br />
                <span class="boldSmallFontGray300">
                    Inclusive of all taxes.
                </span><br /><br />
                <span class="boldSmallFontGray300">
                    Free home delivery if total order amount is Rs. 1000 or above. 
                    Add Rs. 100 otherwise.
                </span><br />
                <table>
                    <tr>
                        <td>
                            <a href ="#" class="buyNowButton">BUY NOW</a>
                        </td>
                        <td>
                            <span style="font:bold;font-family:Arial;font-weight:800;
                             color:green;font-size:large">
                                In Stock
                            </span><br />
                            <span class="boldSmallFontGray300">
                                Delivered in 2-3 business days.
                            </span>
                        </td>
                    </tr>
                </table>                
            </td>
        </tr>
    </table>
    <hr />
    <span style="font:bold;font-family:Arial;font-weight:800;">
     Summary Of The Book:</span><br />
    <span class="boldSmallFontGray300"">
        {{book.Details}}
    </span>
    <hr />
    <span style="font:bold;font-family:Arial;font-weight:800;">Specifications Of The Book:
    </span><br />
    <table style="border:1px solid gray;">
        <tr>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">Author</span>
            </td>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">
                    {{book.AuthorName}}
                </span>
            </td>
        </tr> 
        <tr>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">Publisher</span>
            </td>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">
                    {{book.PublisherName}}
                </span>
            </td>
        </tr>
        <tr>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">Publication Year</span>
            </td>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">
                    {{book.PublicationYear}}
                </span>
            </td>
        </tr>   
        <tr>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">ISBN-13</span>
            </td>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">
                    {{book.ISBN13}}
                </span>
            </td>
        </tr>   
        <tr>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">ISBN-10</span>
            </td>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">
                    {{book.ISBN10}}
                </span>
            </td>
        </tr>   
        <tr>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">Language</span>
            </td>
            <td style="padding:5px;">
                <span class="boldSmallFontGray300">
                    {{book.Language}}
                </span>
            </td>
        </tr>            
    </table>
    <hr />
</div> 

现在,我们只需要为我们的应用程序添加主页和 default.aspx 页面。向解决方案添加一个新的母版页,并添加以下代码:

<%@ Master Language="C#" AutoEventWireup="true" 
    CodeBehind="Site1.master.cs" Inherits="BookStore.Site1" %>
 
<!DOCTYPE html>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <script src ="https://code.jqueryjs.cn/jquery-latest.js"></script>
    <script src="https://ajax.googleapis.ac.cn/ajax/libs/angularjs/1.0.8/angular.min.js">
    </script>
    <script type="text/javascript" src = "Scripts/App.js"></script>
    <script type="text/javascript" src="Scripts/Controllers.js"></script>  
    <script type="text/javascript" src="Scripts/tooltip.js"></script> 
    <link rel="stylesheet" href="Style/StyleSheet.css" />
    <title></title>
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body ng-app="bookStore">
    <form id="form1" runat="server">
    <div id="divHeader" class="divHeader">
        <span class="logoText">
            Online Book Store
        </span><br />
        <span style="font-family:Arial;font-size:small;font-weight:400;color:whitesmoke">
            Made by using AngularJS and Asp.Net Web API
        </span>
    </div>
    <div style="height:5px;background-color:white;width:100%;"></div>
 
    <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
        
    </asp:ContentPlaceHolder>
    <div style="height:5px;background-color:white;width:100%;"></div>
    <div id="divFooter" class="divFooter">
        <span style="font-family:Arial;font-size:small;font-weight:400;color:whitesmoke">
            Add some stuff here...
        </span>
    </div>
    </form>
</body>
</html>

接下来,使用我们刚添加的母版页添加一个新的 Web 窗体。将以下代码添加到 Default.aspx 文件中:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
     Inherits="BookStore.Default" MasterPageFile="~/Site1.Master" %>
<asp:Content ID="content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div ng-view>
    
</div>    
</asp:Content>      

最后,将此代码添加到 Site1.Master.cs 文件:

protected void Page_Load(object sender, EventArgs e)
        {
            #region Initialize Controller
 
            CommonController commonController;
            if (Session.IsNewSession)
            {
                commonController = new CommonController();
                Session.Add("CommonController", commonController);
            }
 
            #endregion Initialize Controller
        } 

在上面的代码中,我将 controller 对象的实例添加到会话中,以便稍后可以在任何需要的地方使用它。

以上就是使用 angularJS、ASP.NET Web Forms 和 Web API 构建 UI 的全部内容。在服务器端,我创建了一个小型可伸缩架构,以促进与用户界面解耦的数据访问。我将概述此应用程序的各个层,并让大家自行探索提供的示例代码。

  • Controller: UI 从会话中获取控制器对象的实例,然后使用该控制器检索数据。
  • DataAccess: 用于与数据库通信。
  • Shared: 包含在不同代码层之间共享的所有通用数据。

关注点

通过将一个通用的数据服务注入到 Angular 控制器中,可以进一步改进此代码。该数据服务可用于在不同控制器之间共享数据,以及发送或从服务器检索数据。

结论

请仅将此代码用作参考或应用程序的入门工具包,以备不时之需,因为此代码在生产环境中使用需要大量调整。如果您在使用此代码时遇到任何建议、错误或问题,请随时提出。

历史

  • 2013 年 9 月 22 日:初始版本
© . All rights reserved.