zoukankan      html  css  js  c++  java
  • 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session

    chsakell分享了前端使用AngularJS,后端使用ASP.NET Web API的购物车案例,非常精彩,这里这里记录下对此项目的理解。


    文章:
    http://chsakell.com/2015/01/31/angularjs-feat-web-api/
    http://chsakell.com/2015/03/07/angularjs-feat-web-api-enable-session-state/

    源码:
    https://github.com/chsakell/webapiangularjssecurity

    本系列共三篇,本篇是第二篇。


    购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(1)--后端
    购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session
    购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证

    HomeController用来展示主页面,并接受前端传来的Order的编号。

    public calss HomeCOntroller : Controller
    {
        public ActionReuslt Index()
        {
            retun View();
        }
        
        public ActionResult ViewOrder(int id)
        {
            using(var context = new SotreContext())
            {
                //这时候Order的导航属性Gadgets还没有加载出来呢
                var order = context.Orders.Find(id);
                
                //根据Order编号获取中间表
                var gadgetOrders = context.GadgetOrders.Where(go => go.OrderID == id);
                
                foreach(GadgetOrder gadgetOrder in gadgetOrders)
                {
                    //加载中间表某个记录中对应的导航属性
                    context.Entry(gadgetOrder).Reference(g => g.Gadget).Load();
                    order.Gadgets.Add(gadgetOrder.Gadget);
                }
                return View(order);
            }
        }
    }

    Home/Index.cshtml视图。

    <html ng-app="gadgetsStore">
        ...
        <body ng-controller='gadgetStoreCtrl'>
            <div ng-hide="checkoutComplete()">
                <div ng-show="showFilter()">
                    <form>
                        <input type="text" ng-model="searchItem">
                    </form>
                </div>
                <cart-details></cart-details>
            </div>
            <div ng-show="data.error" ng-cloak>
                {{data.error.status}}
            </div>
            <ng-view />
            
            <script src="../../Scripts/angular.js" type="text/javascript"></script>
            <script src="../../Scripts/angular-route.js" type="text/javascript"></script>
            
            <script src="../../app/mainApp.js"></script>
            <script src="../../app/controllers/gadgetsStore.js" type="text/javascript"></script>
            <script src="../../app/filters/storeFilters.js" type="text/javascript"></script>
            <script src="../../app/controllers/gadgetsControllers.js" type="text/javascript"></script>
            <script src="../../app/components/cartCmp.js" type="text/javascript"></script>
            <script src="../../app/controllers/checkoutController.js" type="text/javascript"></
        </body>
    </html>

    以上,ng-hide="checkoutComplete()"决定着是否显示所在div,ng-show="data.error" 决定是否显示报错,<ng-view />根据路由显示不同视图,ng-cloak用来避免在切换视图时页面的闪烁,<cart-details></cart-details>是自定义的directive,和angularjs有关的js文件放在顶部,applicaiton相关js文件放在其下面,在mainApp.js文件中坐落着一个顶级module名称是gadgetStore,而顶级controller被放在了gadgetsStoreCtrl.js这个js文件中了。


    最终的界面如下:

    main.js 声明顶级module,以及配置路由。

    angular.module("gadgetsStore", ["storeFilters", "storeCart", "ngRoute"])
        .config(function($routeProvider){
            $routeProvider.when("/gadgets",{templateUrl: "app/views/gadgets.html"});
            $routeProvider.when("/checkout",{templateUrl: "app/views/checkout.html"});
            $routeProvider.when("/submitorder",{templateUrl: "app/views/submitOrder.html"});
            $routeProvider.when("/complete",{templateUrl: "app/views/orderSubmitted.html"});
            $routeProvider.otherwise({templateUrl: "app/views/gadgets.html"});
        });

    storeFilters, storeCart是我们自定义的,这里注入进来。

    有了gadgetsStore这个module,现在就为这个module添加controller等。

    angular.module('gadgetsStore')
        .constant('gadgetsUrl', 'http://localhost:8888/api/gadgets')
        .constant('ordersUrl', 'http://localhost:8888/api/orders')
        .constant('categoreisUrl', 'http://localhost:8888/api/categories')
        .controller('gadgetStore', function($scope, $http, $location, gadgetsUrl, categoresUrl, ordersUrl, cart){//因为gadgetsStore依赖引用了storeCart,所以这里可以引用cart
        
            //这里的data被用在主视图上,所以data的数据会被其它部分视图共享
            // $scope.data.gadgets
            // scope.data.erro
            // $scope.data.categories
            // $scope.data.OrderLocation
            // $scope.data.OrderID
            // $scope.data.orderError
            $scope.data = {};
            
            $http.get(gadgetsUrl)
                .success(function(data){
                    $scope.data.gadgets = data;
                })
                .error(function(error){
                    $scope.data.error = error;
                });
                
            $http.get(categoresUrl)
                .success(function(data){
                    $scope.data.categories = data;
                })
                .error(function(error){
                    $scope.data.error = error;
                });
                
            $scope.sendOrder = function(shippingDetails){
                var order = angular.copy(shippingDetails);
                order.gadgets = cart.getProducts();
                $http.post(ordersUrl, order)
                    .success(function(data, status, headers, config){
                        $scope.data.OrderLocation = headers('Location');
                        $scope.data.OrderID = data.OrderID;
                        cart.getProducts().length = 0;
                    })
                    .error(function(error){
                        $scope.data.orderError = error;
                    }).finally(function(){
                        $location.path("/complete");
                    });
            }
            
            $scope.showFilter = function(){
                return $location.path() == '';
            }
            
            $scope.checkoutComplete = function(){
                return $location.path() == '/complete';
            }
        });

    以上,为gadgetsStore这个module定义了常量以及controller。把一些规定的uri定义成某个moudule的常量是很好的习惯。通过$location.path方法可以获取或设置当前窗口的uri。

    好了,顶级的module和顶级的controller有了,Gadget部分如何显示呢?

    根据路由$routeProvider.when("/gadgets",{templateUrl: "app/views/gadgets.html"}), Gadget的视图被放在了app/views/gadgets.html中了,来看gadgets.html这个视图。

    <div ng-controller="gadgetsCtrl" ng-hide="data.error">
    
        <!--左侧导航部分-->
        <div>
            <!--这里的selectCategory方法实际是把controller内部的一个变量selectedCategory设为null-->
            <a ng-click="selectCategory()">Home</a>
            <a ng-repeat="item in data.categoires | orderBy: 'CategoryID'" ng-click="selectCategory(item.CategoryID)" ng-class="getCategoryClass(item.CategoryID)">{{item.Name}}</a>
        </div>
        
        <!--右侧Gadgets部分-->
        <div>
            <div ng-repeat="item in data.gadgets | filter: categoryFilterFn | filter: searchItem | range:selectedPage:pageSize">
                {{item.Name}}
                {{item.Price | currency}}
                <img ng-src="../../images/{{item.Images}}" />
                {{item.Description}}
                
                <a ng-click="addProductToCart(item)">Add To Cart</a>
            </div>
            
            <!--分页部分-->
            <div>
                <a ng-repeat="page in data.gadgets | filter:categoryFilterFn | filter:searchItem | pageCount:pageSize" ng-click="selectPage($index + 1)" ng-class="getPageClass($index + 1)">
                    {{$index + 1}}
                </a>
            </div>
        </div>
    </div>


    以上,把视图的来源交给了gadgetsCtrl这个controller, 这个controller也被定义在了gadgetsStore这个module中。

    gadgetsCtr.js

    angular.module("gadgetsStore")
        .constant("gadgetsActiveClass", 'btn-primary')
        .constant('gadgetsPageCount', 3)
        .controller("gadgetsCtrl", function($scope, $filter, gadgetsActiveClass, gadgetsPageCount, cart){
        
            //存储Category的主键CategoryID
            var selectedCategory = null;
            
            //这里是传给range和pageCount过滤器的
            $scope.selectedPage = 1;
            $scope.pageSise = gadgetsPageCount;
            
            //实际就是未selectedPage这个变量赋新值
            $scope.selectPage = function(newPage){
                $scope.selectedPage = newPage;
            }
            
            //这里把Category的编号CategoryID传了进来
            $scope.selecteCategory = function(newCategory)
            {
                $selectedCategory = newCategory;
                $scope.selectedPage = 1; 
            }
            
            //这里的product实际就是Gadget
            //过滤出Gadget的CategoryID和这里的selectedCateogory一致的那些Gadgets
            $scope.categoryFilterFn = fucntion(product){
                return selectedCategory == null || product.CategoryID == selectedCategory;
            }
            
            //category实际是Category的主键CategoryID
            $scope.getCategoryClass = function(category){
                return selectedCategory == category ? gadgetsActiveClass : "";
            }
            
            $scope.getPageClass = function(page){
                return $scope.selectedPage = page ? gadgetsActiveClass : "";
            }
            
            $scope.addProductToCart = function(product){
                cart.addProduct(product.GadgetID, product.Name, product.Price, product.CategoryID);
            }
        });

    在显示Gadget列表的时候,<div ng-repeat="item in data.gadgets | filter: categoryFilterFn | filter: searchItem | range:selectedPage:pageSize">,这里用到了一个自定的过滤器range,这个过滤器被定义在了storeFilters.js中。

    var storeFilters = angular.module('storeFilters',[]);
    
    storeFitlers.filter("range", function($filter){
        return function(data, page, size){
            if(angular.isArray(data) && angular.isNumber(page) && angular.isNumber(size)){
                var start_index = (page - 1)*size;
                if(data.legnth < start_index){
                    return [];
                } else {
                    return $filter("limitTo")(data.splice(start_index), size);
                }
            } else{
                return data;
            }
        }
    });
    
    sortFilters.filter("pageCount", function(){
        return function(data, size){
            if(angular.isArray(data))
            {
                var result = [];
                for(var i = 0; i < Math.ceil(data.length/size); i++){
                    result.push(i);
                }
            } else {
                return data;
            }
        }
    });

    再来看$routeProvider.when("/checkout",{templateUrl: "app/views/checkout.html"});这个路由,checkout.html这个部分视图如下:

    <div ng-controller = "cartDetailsController">
        <div ng-show="cartData.length==0">
            no item in the shopping cart
        </div>
        <div ng-hide="cartData.length == 0">
            {{item.count}}
            {{item.Name}}
            {{item.Price | currency}}
            {{(item.Price * item.count) | currency}}
            <button ng-click="remove(item.GadgetID)"></button>
            {{total() | currency}}
            <a href="#">Continue shopping</a>
            <a href="#/submitorder">Place order now</a>
        </div> 
    </div>

    对应的界面如下:

    cartDetailsController这个controller也被放在了顶级module里。如下:

    angular.module("gadgetsStore")
        .controller("cartDetailsController", function($scope, cart){
            $scope.cartData = cart.getProducts();
            
            $scope.total = function(){
                var total  = 0;
                for(var i = 0; i < $scope.cartData.length;i++)
                {
                    total += ($scope.cartData[i].Price * $scope.cartData[i].count);
                }
                return total;
            }
            
            $scope.remove = function(id){
                cart.removeProduct(id);
            }
        });

    我们注意到,我们已经在多个地方注入cart这个服务 ,这个自定义的服务可以以factory的方式来创建,如果要用这个cart服务,它所在的module就要被其它module所引用。下面来创建cart服务:

    var storeCart = angular.module('storeCart',[]);
    
    storeCart.factory('cart', function(){
        var cartData = [];
        
        return {
            addProduct: function(id, name, price, category){
            
                //用来标记是否已经向购物车里加了产品
                var addedToExistingItem = false;
                for(var i=0; i < cartData.length;i++)
                {
                    if(cartData[i].GadgetID == id){
                        cartData[i].count++;
                        addedToExistingItem = true;
                        break;
                    }
                }
                if(!addedToExistingItem)
                {
                    cartData.push({
                        count:1, GadgetID: id, Price: price, Name: name, CategoryID:category
                    });
                }
            },
            removeProduct: function(id){
                for(var i = 0; i < cartData.legnth; i++){
                    if(cartData[i].GadgetID == id){
                        cartData.splice(i, 1);
                        break;
                    }
                }
            },
            getProducts:function(){
                return cartData;
            }
        
        };
    });

    关于购物车部分,我们还记得,在主视图用了<cart-details></cart-details>这个自定义的directive,实际也是在storeCart这个module中定义的。

    sortCart.directive("cartDetails", function(cart){
        return {
            restrict: "E",
            templateUrl: "app/components.cartDetails.html",
            controller: function($scope){
                var cartData = cart.getProducts();
                
                $scope.total = function(){
                    var total =0;
                    for(var i = 0; i < cartData.legnth; i++){
                        total += (cartData[i].Price * cartData[i].count);
                    }
                    return total;
                }
                
                $scope.itemCount = function(){
                    var total = 0;
                    for(var i = 0; i < cartData.length; i++){
                        total += cartData[i].count;
                    }
                    return total;
                }
            }
        };
    });

    以上,对应的视图为:

    Your cart: {{itemCount()}} items
    {total() | currency}
    <a href="#/checkout">Checkout</a>

    在显示购物车明细的时候,给出了提交订单的链接:

    <a href="#/submitorder">Place order now</a>

    根据路由$routeProvider.when("/submitorder",{templateUrl: "app/views/submitOrder.html"}),是会加载app/views/submitOrder.html部分视图,界面如下:

    对应的html为:

    <form name="shippingForm" novalidate>
        <input name="companyName" ng-model="data.shipping.CompanyName" required />
        <span ng-show="shippingForm.companyName.$error.required"></span>
        
        <input name="name" ng-model="data.shipping.OwnerName" required />
        <span ng-show="shippingorm.name.$error.required"></span>
        
        ...
        <button ng-disabled="shippingForm.$invalid" ng-click="sendOrder(data.shipping)">Complete Order</button>
    </form>

    sendOrder被定义在了顶级module中:

    $scope.sendOrder = function (shippingDetails) {
                var order = angular.copy(shippingDetails);
                order.gadgets = cart.getProducts();
                $http.post(ordersUrl, order)
                .success(function (data, status, headers, config) {
                    $scope.data.OrderLocation = headers('Location');
                    $scope.data.OrderID = data.OrderID;
                    cart.getProducts().length = 0;
                })
                .error(function (error) {
                    $scope.data.orderError = error;
                }).finally(function () {
                    $location.path("/complete");
                });
            }

    /complete会路由到$routeProvider.when("/complete",{templateUrl: "app/views/orderSubmitted.html"}), app/views/orderSubmitted.html部分视图如下:

    其html部分为:

    <div ng-show="data.orderError">
        {{data.orderError.status}}the order could not be placed, <a href="#/submitorder">click here to try again</a>
    </div>
    <div ng-hide="data.orderError">
        {{data.OrderID}}
        <a href="#">Back to gadgets</a>
        <a href="{{data.OrderLocation}}">View Order</a>
    </div>

    ■ 实现购物车的Session

    现在为止,还存在的问题是:当刷新页面的时候,购物车内的产品就会消失,即还么有Session机制。

    与ASP.NET Web API路由相关的HttpControllerRouteHandler, HttpControllerHandler, IRequireSessionState。

    首先一个继承内置的HttpControllerHandler,并实现内置的IRequiresSessionState接口。

    public class SessionEnabledControllerHandler : HttpControllerHandler, IRequiresSessionState
    {
        public SessionEnabledControllerHandler(RouteData routeData)
            : base(routeData)
        { }
    }

    然后实现一个内置HttpControllerRouteHandler的继承类。

    public class SessionEnabledHttpControllerRouteHandler : HttpControllerRouteHandler
    {
        protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            return new SessionEnabledControllerHandler(requestContext.RouteData);
        }
    }

    注释掉WebApiConfig.cs中的代码:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            // Moved to RouteConfig.cs to enable Session
            /*
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
             */
        }
    }

    在RouteConfig中配置如下:

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            #region Web API Routes
    
            // Web API Session Enabled Route Configurations
            routes.MapHttpRoute(
                name: "SessionsRoute",
                routeTemplate: "api/sessions/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            ).RouteHandler = new SessionEnabledHttpControllerRouteHandler(); ;
    
            // Web API Stateless Route Configurations
            routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            #endregion
    
            #region MVC Routes
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
            #endregion
        }
    }

    以上,需要引用System.Web.Http。

    现在,如果希望ItemsController中使用Session,那就这样请求:

    http://localhost:61691/api/sessions/items

    如果不想用Session,那就这样请求:

    http://localhost:61691/api/items

    现在,在前端,向购物车添加产品相关代码为:

    addProduct: function (id, name, price, category) {
        var addedToExistingItem = false;
        for (var i = 0; i < cartData.length; i++) {
            if (cartData[i].GadgetID == id) {
                cartData[i].count++;
                addedToExistingItem = true;
                break;
            }
        }
        if (!addedToExistingItem) {
            cartData.push({
                count: 1, GadgetID: id, Price: price, Name: name, CategoryID: category
            });
        }
    }

    类似地,创建一个模型:

    public class CartItem
    {
        public int Count { get; set; }
        public int GadgetID { get; set; }
        public decimal Price { get; set; }
        public string Name { get; set; }
        public int CategoryID { get; set; }
    }

    对应的控制器为:

    public class TempOrdersController : ApiController
    {
        //get api/TempOrders
        public List<CartItem> GetTempOrders()
        {
            List<CartItem> cartItems = null;
            
            if(System.Web.HttpContext.Current.Session["Cart"] != null){
                cartItems = (List<CartItem>)System.Web.HttpContext.Current.Session["Cart"];
            }
            
            return cartItems;
        }
        
        //post api/TempOrders
        [HttpPost]
        public HttpResponseMessage SaveOrder(List<CarItem> cartItems)
        {
            if (!ModelState.IsValid)
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest);
            }
    
            System.Web.HttpContext.Current.Session["Cart"] = cartItems;
    
            return new HttpResponseMessage(HttpStatusCode.OK);
        }
    }

    再回到前端,首先在gadgetsStore这个顶级module中增加有关缓存API的uri常量。

    angular.module('gadgetsStore')
        .constant('gadgetsUrl', 'http://localhost:61691/api/gadgets')
        .constant('ordersUrl', 'http://localhost:61691/api/orders')
        .constant('categoriesUrl', 'http://localhost:61691/api/categories')
        .constant('tempOrdersUrl', 'http://localhost:61691/api/sessions/temporders')
        .controller('gadgetStoreCtrl', function ($scope, $http, $location, gadgetsUrl, categoriesUrl, ordersUrl, tempOrdersUrl, cart) {
    // Code omitted

    重新定义cart这个服务:

    storeCart.factory('cart', function(){
        var cartData = [];
        
        return {
            addProduct: function(id, name, price, category){
                var addedToExistingItem = false;
                for(var i = 0; i < cartData.length; i++){
                    if(cartData[i].GadgetID == id){
                        cartData[i].count++;
                        addedToExistingItem = true;
                        break;
                    }
                }
                if(!addedToExistingItem){
                    cartData.push({
                        count:1, GadgetID: id, Price: price, Name: name, Category: category
                    });
                }
            },
            removeProduct: fucntion(id){
                for(var i = 0; i < cartData.length; i++){
                    if(cartData[i].GadgetID == id){
                        cartData.splice(i, 1);
                        break;
                    }
                }
            },
            getProducts: fucntion(){
                return cartData;
            },
            pushItem: function(item){
                cartData.push({
                    count: item.Count, GadgetID:item.GadgetID, Price: Item.Price, Name: item.Name, CategoryID: item.CategoryID
                })
            }
        };
    });


    为了在页面每次刷新的时候保证Session的状态,在主module中添加如下方法:

    //用来把每次更新保存到后端的Session中
    $scope.saveOrder = function () {
        var currentProducts = cart.getProducts();
    
        $http.post(tempOrdersUrl, currentProducts)
            .success(function (data, status, headers, config) {
            }).error(function (error) {
            }).finally(function () {
            });
    }
    
    //用来每次刷新向后端Session要数据
    $scope.checkSessionGadgets = function(){
        $http.get(tempOrdersUrl)
            .success(function(data){
                if(data){
                    for(var i = 0; i < data.length; i++){
                        var item = data[i];
                        cart.pushItem(item);
                    }
                }
            })
            .error(function(error){
                console.log('error checking session: ' + error) ;
            });
    }

    然后checkSessionGadgets这个方法就要被运用到主视图上去,当页面每次加载的时候调用它。

    <body ng-controller='gadgetStoreCtrl' class="container" ng-init="checkSessionGadgets()">

    每次向购车添加的时候需要重新更新后端的Session状态。

    $scope.addProductToCart = function (product) {
        cart.addProduct(product.GadgetID, product.Name, product.Price, product.CategoryID);
        $scope.saveOrder();
    }

    每次从购物车一处的时候需要重新更新后端的Session状态。

    $scope.remove = function (id) {
        cart.removeProduct(id);
        $scope.saveOrder();
    }

    在用户提交订单的时候,需要一出购物车内的产品,再更新后端的Session状态。

    $scope.sendOrder = function (shippingDetails) {
        var order = angular.copy(shippingDetails);
        order.gadgets = cart.getProducts();
        $http.post(ordersUrl, order)
        .success(function (data, status, headers, config) {
            $scope.data.OrderLocation = headers('Location');
            $scope.data.OrderID = data.OrderID;
            cart.getProducts().length = 0;
            $scope.saveOrder();
        })
        .error(function (error) {
            $scope.data.orderError = error;
        }).finally(function () {
            $location.path("/complete");
        });
    }

    待续~~

  • 相关阅读:
    [转载] $CF652B$ 题解
    [转载] $Luogu$ $P4951$ 题解
    luoguP3723 HNOI2017 礼物
    动态dp学习笔记
    noip级别模板小复习
    noip2017简要题解。
    noip考前抱佛脚 数论小总结
    HDU 6071 Lazy Running
    POI2012 ODL-Distance
    [NOI2002]荒岛野人
  • 原文地址:https://www.cnblogs.com/darrenji/p/4959694.html
Copyright © 2011-2022 走看看