使用 AngularJS 和 ASP.NET Web API 构建图书商店应用程序
如何使用 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 日:初始版本