• 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(3)


    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目。

    源码: https://github.com/chsakell/spa-webapi-angularjs
    文章:http://chsakell.com/2015/08/23/building-single-page-applications-using-web-api-and-angularjs-free-e-book/

    这里记录下对此项目的理解。分为如下几篇:

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(1)--领域、Repository、Service

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(2)--依赖倒置、Bundling、视图模型验证、视图模型和领域模型映射、自定义handler

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(3)--主页面布局

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(4)--Movie增改查以及上传图片

    Home/Index.cshtml视图

    创建一个有关ui的module:spa/modules/common.ui.js

    (function () {
        'use strict';
     
        angular.module('common.ui', ['ui.bootstrap', 'chieffancypants.loadingBar']);
     
    })();

    创建一个有关功能的module:spa/modules/common.ui.js

    (function () {
        'use strict';
     
        angular.module('common.core', ['ngRoute', 'ngCookies', 'base64', 'angularFileUpload', 'angularValidator', 'angucomplete-alt']);
     
    })();

    Home/Index.cshtml视图摘要:

    <html ng-app="homeCinema">
        <body ng-controller="rootCtrl">
            <top-bar></top-bar>
            <side-bar></side-bar>
            <div class="page {{ pageClass }}" ng-view></div>
        </body>
    </html>

    homeCinema是一个主module,依赖common.ui和common.core这2个module,定义在了spa/app.js中,具体如下:

    //config传入名称为config的函数
    //run传入名称为run的函数
    //执行顺序:app.config()→app.run()→directive compile functions if found→app.controller→directive's link funciton if found
    angular.module('homeCinema', ['common.core', 'common.ui'])
        .config(config)
        .run(run);
        
        
    //为config函数注入参数    
    config.$inject = ['$routeProvider'];
    
    //路由设置,让controller和页面匹配
    function config($routeProvider) {
        $routeProvider
            .when("/", {
                templateUrl: "scripts/spa/home/index.html",
                controller: "indexCtrl"
            })
            .when("/login", {
                templateUrl: "scripts/spa/account/login.html",
                controller: "loginCtrl"
            })
            .when("/register", {
                templateUrl: "scripts/spa/account/register.html",
                controller: "registerCtrl"
            })
            .when("/customers", {
                templateUrl: "scripts/spa/customers/customers.html",
                controller: "customersCtrl"
            })
            .when("/customers/register", {
                templateUrl: "scripts/spa/customers/register.html",
                controller: "customersRegCtrl",
                //注入到cotroller中的依赖,controller会等到resolve的动作结束后再初始化
                resolve: { isAuthenticated: isAuthenticated }
            })
            .when("/movies", {
                templateUrl: "scripts/spa/movies/movies.html",
                controller: "moviesCtrl"
            })
            .when("/movies/add", {
                templateUrl: "scripts/spa/movies/add.html",
                controller: "movieAddCtrl",
                resolve: { isAuthenticated: isAuthenticated }
            })
            .when("/movies/:id", {
                templateUrl: "scripts/spa/movies/details.html",
                controller: "movieDetailsCtrl",
                resolve: { isAuthenticated: isAuthenticated }
            })
            .when("/movies/edit/:id", {
                templateUrl: "scripts/spa/movies/edit.html",
                controller: "movieEditCtrl"
            })
            .when("/rental", {
                templateUrl: "scripts/spa/rental/rental.html",
                controller: "rentStatsCtrl"
            }).otherwise({ redirectTo: "/" });
    }    
    
    //为resolve的函数注入参数
    isAuthenticated.$inject = ['membershipService', '$rootScope', '$location'];
    
    //resolve执行的函数
    function isAuthenticated(membershipService, $rootScope, $location) {
        if (!membershipService.isUserLoggedIn()) {
            $rootScope.previousState = $location.path();
            $location.path('/login');
        }
    }
    
    //为run函数注入参数
    run.$inject = ['$rootScope', '$location', '$cookieStore', '$http'];
    
    //一些初始化工作
    function run($rootScope, $location, $cookieStore, $http) {
        // handle page refreshes
        //rootScope.repository
        //rootScope.repository.loggedUser
        $rootScope.repository = $cookieStore.get('repository') || {};
        if ($rootScope.repository.loggedUser) {
            $http.defaults.headers.common['Authorization'] = $rootScope.repository.loggedUser.authdata;
        }
    
        $(document).ready(function () {
            $(".fancybox").fancybox({
                openEffect: 'none',
                closeEffect: 'none'
            });
    
            $('.fancybox-media').fancybox({
                openEffect: 'none',
                closeEffect: 'none',
                helpers: {
                    media: {}
                }
            });
    
            $('[data-toggle=offcanvas]').click(function () {
                $('.row-offcanvas').toggleClass('active');
            });
        });
    }

    side-bar

    在界面中的适用方法:<side-bar></side-bar>

    在common.ui这个module中自定义了一个directive。在spa/layout/sideBar.directive.js

    (function(app) {
        'use strict';
     
        app.directive('sideBar', sideBar);
     
        function sideBar() {
            return {
                restrict: 'E',
                replace: true,
                templateUrl: '/scripts/spa/layout/sideBar.html'
            }
        }
     
    })(angular.module('common.ui'));

    spa/layout/sidebar.html摘要:

    <a ng-href="#/">Home</i>
    <a ng-href="#/customers/">Customers</a>
    <a ng-href="#/customers/register">Register customer</a>
    <a ng-href="#/movies/">Movies</a>
    <a ng-href="#/movies/add">Add movie<i class="fa fa-plus-circle fa-fw pull-right"></i></a>
    <a ng-href="#/rental/">Rental history<i class="fa fa-leanpub fa-fw pull-right"></i></a>
    <a ng-href="#/login" ng-if="!userData.isUserLoggedIn">Login</a>
    <a ng-click="logout();" ng-if="userData.isUserLoggedIn">Logout</a>

    其中,userData.isUserLoggedIn肯定是homeCinema这个module的controller,定义在了spa/home/rootCtrl.js中。

    (function (app) {
        'use strict';
    
        app.controller('rootCtrl', rootCtrl);
    
        rootCtrl.$inject = ['$scope','$location', 'membershipService','$rootScope'];
        
    
        function rootCtrl($scope, $location, membershipService, $rootScope) {
    
            //userData对象
            $scope.userData = {};
            
            //$scope.userData.displayUserInfo方法显示用户
            $scope.userData.displayUserInfo = displayUserInfo;
            
            //方法登出
            $scope.logout = logout;
    
            //$scope.userData.isUserLoggedIn,布尔类型
            //$scope.username
            function displayUserInfo() {
                $scope.userData.isUserLoggedIn = membershipService.isUserLoggedIn();
    
                if($scope.userData.isUserLoggedIn)
                {
                    //主页面初始化的时候就定义在$rootScope.repository.loggedUser.username了
                    $scope.username = $rootScope.repository.loggedUser.username;
                }
            }
    
            function logout() {
                membershipService.removeCredentials();
                $location.path('#/');
                $scope.userData.displayUserInfo();
            }
    
            $scope.userData.displayUserInfo();
        }
    
    })(angular.module('homeCinema'));

    以上,注入了membershipService这个服务,另外还有一个apiService,notificationService等服务被放在了common.core模块中,都是以factory的方式创建的服务。

    spa/services/notificationService.js这个服务基于toastr.js管理所有的通知。

    (function (app) {
        'use strict';
     
        //以工厂的方式创建服务
        app.factory('notificationService', notificationService);
     
        function notificationService() {
     
            toastr.options = {
                "debug": false,
                "positionClass": "toast-top-right",
                "onclick": null,
                "fadeIn": 300,
                "fadeOut": 1000,
                "timeOut": 3000,
                "extendedTimeOut": 1000
            };
     
            var service = {
                displaySuccess: displaySuccess,
                displayError: displayError,
                displayWarning: displayWarning,
                displayInfo: displayInfo
            };
     
            return service;
     
            function displaySuccess(message) {
                toastr.success(message);
            }
     
            function displayError(error) {
                if (Array.isArray(error)) {
                    error.forEach(function (err) {
                        toastr.error(err);
                    });
                } else {
                    toastr.error(error);
                }
            }
     
            function displayWarning(message) {
                toastr.warning(message);
            }
     
            function displayInfo(message) {
                toastr.info(message);
            }
     
        }
     
    })(angular.module('common.core'));

    spa/services/apiService.js服务用来管理GET和POST请求。

    (function (app) {
        'use strict';
     
        app.factory('apiService', apiService);
     
        apiService.$inject = ['$http', '$location', 'notificationService','$rootScope'];
     
        function apiService($http, $location, notificationService, $rootScope) {
            var service = {
                get: get,
                post: post
            };
     
            function get(url, config, success, failure) {
                return $http.get(url, config)
                        .then(function (result) {
                            success(result);
                        }, function (error) {
                            if (error.status == '401') {
                                notificationService.displayError('Authentication required.');
                                $rootScope.previousState = $location.path();
                                $location.path('/login');
                            }
                            else if (failure != null) {
                                failure(error);
                            }
                        });
            }
     
            function post(url, data, success, failure) {
                return $http.post(url, data)
                        .then(function (result) {
                            success(result);
                        }, function (error) {
                            if (error.status == '401') {
                                notificationService.displayError('Authentication required.');
                                $rootScope.previousState = $location.path();
                                $location.path('/login');
                            }
                            else if (failure != null) {
                                failure(error);
                            }
                        });
            }
     
            return service;
        }
     
    })(angular.module('common.core'));

    top bar

    自定义的top bar放在了common.ui模块中,spa/layout/topBar.directive.js

    (function(app) {
        'use strict';
     
        app.directive('topBar', topBar);
     
        function topBar() {
            return {
                restrict: 'E',
                replace: true,
                templateUrl: '/scripts/spa/layout/topBar.html'
            }
        }
     
    })(angular.module('common.ui'));

    spa/layout/topBar.html摘要:

    <a class="navbar-brand active" href="#/">Home Cinema</a>
    <a href="#about">About</a>
    <ulng-if="userData.isUserLoggedIn">
        <a href="#/">{{username}}</a>
    </ul>

    以上,username, userData.isUserLoggedIn都是homeCinema这个模块中rootCtrl控制器的变量。

    Latest Movies 

    首先要写一个继承ApiController的类,用来处理登录异常。

    namespace HomeCinema.Web.Infrastructure.Core
    {
        public class ApiControllerBase : ApiController
        {
            protected readonly IEntityBaseRepository<Error> _errorsRepository;
            protected readonly IUnitOfWork _unitOfWork;
    
            public ApiControllerBase(IEntityBaseRepository<Error> errorsRepository, IUnitOfWork unitOfWork)
            {
                _errorsRepository = errorsRepository;
                _unitOfWork = unitOfWork;
            }
    
            public ApiControllerBase(IDataRepositoryFactory dataRepositoryFactory, IEntityBaseRepository<Error> errorsRepository, IUnitOfWork unitOfWork)
            {
                _errorsRepository = errorsRepository;
                _unitOfWork = unitOfWork;
            }
    
            protected HttpResponseMessage CreateHttpResponse(HttpRequestMessage request, Func<HttpResponseMessage> function)
            {
                HttpResponseMessage response = null;
    
                try
                {
                    response = function.Invoke();
                }
                catch (DbUpdateException ex)
                {
                    LogError(ex);
                    response = request.CreateResponse(HttpStatusCode.BadRequest, ex.InnerException.Message);
                }
                catch (Exception ex)
                {
                    LogError(ex);
                    response = request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
                }
    
                return response;
            }
            
            //把错误报错到数据库中去
            private void LogError(Exception ex)
            {
                try
                {
                    Error _error = new Error()
                    {
                        Message = ex.Message,
                        StackTrace = ex.StackTrace,
                        DateCreated = DateTime.Now
                    };
    
                    _errorsRepository.Add(_error);
                    _unitOfWork.Commit();
                }
                catch { }
            }
        }
    }

    以上, ApiControllerBase定义了一个重要的方法CreateHttpResponse,不但可以处理请求响应,还可以进行异常处理,把异常记录到数据库。

    接着定义MoviesController,继承ApiControllerBase基类。

    [Authorize(Roles = "Admin")]
    [RoutePrefix("api/movies")]
    public class MoviesController : ApiControllerBase
    {
        private readonly IEntityBaseRepository<Movie> _moviesRepository;
        
        public MoviesController(IEntityBaseRepository<Movie> moviesRepository,IEntityBaseRepository<Error> _errorsRepository, IUnitOfWork _unitOfWork)
                : base(_errorsRepository, _unitOfWork)
        {
            _moviesRepository = moviesRepository;
        }
        
        ...
    }

    首页展示6个Moview,针对此写一个aciton方法。

    [AllowAnonymous]
    [Route("latest")]
    public HttpResponseMessage Get(HttpRequestMessage request)
    {
        return CreateHttpResponse(request, () =>
        {
            HttpResponseMessage response = null;
            
            //获取6个
            var movies = _moviesRepository.GetAll().OrderByDescending(m => m.ReleaseDate).Take(6).ToList();
    
            //转换成视图模型
            IEnumerable<MovieViewModel> moviesVM = Mapper.Map<IEnumerable<Movie>, IEnumerable<MovieViewModel>>(movies);
    
            //创建响应
            response = request.CreateResponse<IEnumerable<MovieViewModel>>(HttpStatusCode.OK, moviesVM);
    
            return response;
        });
    }

    界面如何展示出来呢?在spa/app.js中的路由已经有了定义。

     $routeProvider
        .when("/", {
            templateUrl: "scripts/spa/home/index.html",
            controller: "indexCtrl"
        })
        ...

    也就是在根地址下,使用indexCtrl这个controller,路由到scripts/spa/home/index.html这里。

    scripts/spa/home/index.html

    <div ng-if="loadingMovies">
        <label class="label label-primary">Loading movies...</label>
    </div>
    
     <div ng-repeat="movie in latestMovies">
         <strong>{{movie.Title}} </strong>
         <a ng-href="../../Content/images/movies/{{movie.Image}}" title="{{movie.Description | limitTo:200}}">
             <img class="media-object" height="120" ng-src="../../Content/images/movies/{{movie.Image}}" alt="" />
         </a>
         <available-movie is-available="{{movie.IsAvailable}}"></available-movie>
         {{movie.Description | limitTo: 70}}...</small>
         <label class="label label-info">{{movie.Genre}}</label>
         <span component-rating="{{movie.Rating}}"></span>
         <a ng-href="{{movie.TrailerURI}}">Trailer<i class="fa fa-video-camera fa-fw"></i></a>
     </div>

    以上,其实定义了2个directive,一个是available-movie,用来显示Movie的状态,可能是Available,也可能是Not Available;另一个是component-rating,用来显示星级,基于 raty.js文件开发。

    先来看控制器:spa/home/indexCtrl.js

    (function (app) {
        'use strict';
    
        app.controller('indexCtrl', indexCtrl);
    
        indexCtrl.$inject = ['$scope','apiService', 'notificationService'];
    
        function indexCtrl($scope, apiService, notificationService) {
    
            $scope.loadingMovies = true;
    
            $scope.latestMovies = [];
            $scope.loadData = loadData;
    
            function loadData() {
                apiService.get('/api/movies/latest', null,
                            moviesLoadCompleted,
                            moviesLoadFailed);
            }
    
            function moviesLoadCompleted(result) {
                $scope.latestMovies = result.data;
                $scope.loadingMovies = false;
            }
    
    
            function moviesLoadFailed(response) {
                notificationService.displayError(response.data);
            }
    
    
            loadData();
        }
    
    })(angular.module('homeCinema'));

    再来看是否显示Available Movie的这个自定义directive,在页面中是这样使用的:

    <available-movie is-available="{{movie.IsAvailable}}"></available-movie>

    实际是在spa/directives/availableMovie.directive.js中定义的。

    (function (app) {
        'use strict';
     
        //注意这里的惯例,这里的availableMovie相当于界面上的available-movie
        app.directive('availableMovie', availableMovie);
     
        function availableMovie() {
            return {
                restrict: 'E',
                templateUrl: "/Scripts/spa/directives/availableMovie.html",
                link: function ($scope, $element, $attrs) {
                
                   //getAvailbleClass供html中调用
                   //attrs表示属性,注意这里的惯例:isAvailable相当于界面上的is-available
                    $scope.getAvailableClass = function () {
                        if ($attrs.isAvailable === 'true')
                            return 'label label-success'
                        else
                            return 'label label-danger'
                    };
                    
                    //getAvailability根据属性的布尔值返回不同的字符串
                    $scope.getAvailability = function () {
                        if ($attrs.isAvailable === 'true')
                            return 'Available!'
                        else
                            return 'Not Available'
                    };
                }
            }
        }
     
    })(angular.module('common.ui'));

    最终,在spa/directives/availableMovie.html中:

    <label ng-class="getAvailableClass()">{{getAvailability()}}</label>

    在Movie的显示中,还定义了一个directive,用来显示星级,在界面中按如下:

    <span component-rating="{{movie.Rating}}"></span>

    在spa/directives/componentRating.directive.js中:

    (function(app) {
        'use strict';
     
        app.directive('componentRating', componentRating);
     
        function componentRating() {
            return {
                restrict: 'A', //A说明directive以属性的方式
                link: function ($scope, $element, $attrs) {
                    $element.raty({
                        score: $attrs.componentRating, //componentRating相当于界面中的component-rating,接收component-rating属性值
                        halfShow: false,
                        readOnly: $scope.isReadOnly,//表明是只读的星级
                        noRatedMsg: "Not rated yet!",
                        starHalf: "../Content/images/raty/star-half.png",
                        starOff: "../Content/images/raty/star-off.png",
                        starOn: "../Content/images/raty/star-on.png",
                        hints: ["Poor", "Average", "Good", "Very Good", "Excellent"],
                        click: function (score, event) {
                            //Set the model value
                            $scope.movie.Rating = score;
                            $scope.$apply();
                        }
                    });
                }
            }
        }
     
    })(angular.module('common.ui'));

    点击首页Movie缩略图,弹出窗口

    关于缩略图的html部分就在scripts/spa/home/index.html中,具体为:

    <a class="fancybox pull-left" rel="gallery1" ng-href="../../Content/images/movies/{{movie.Image}}" title="{{movie.Description | limitTo:200}}">
        <img class="media-object" height="120" ng-src="../../Content/images/movies/{{movie.Image}}" alt="" />
    </a>

    根据类名fancybox,使用jquery语法调用jquery.fancybox.js的语法,具体调用是在app.js中调用的:

    function run($rootScope, $location, $cookieStore, $http) {
        ...
    
        $(document).ready(function () {
            $(".fancybox").fancybox({
                openEffect: 'none',
                closeEffect: 'none'
            });
    
            ...
        });
    }

    待续~

  • 相关阅读:

    php多版本环境变量设置
    vagrantbox 磁盘扩容
    # Csv数据导入导出
    php把数组写入文件
    log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).
    springmvc 多个拦截器执行的顺序
    yum 源修改
    连接(JOIN)运算
    SQL基本概述
  • 原文地址:https://www.cnblogs.com/darrenji/p/4945733.html
Copyright © 2020-2023  润新知