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

待续~

对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(3)的更多相关文章

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

    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目. 源码: https://github.com/chsakell/spa-webapi-angula ...

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

    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目. 源码: https://github.com/chsakell/spa-webapi-angula ...

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

    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目. 源码: https://github.com/chsakell/spa-webapi-angula ...

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

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证 chsakell分享了前端使用AngularJS,后端使用ASP. ...

  5. 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session chsakell分享了前端使用AngularJS,后端使用ASP.NE ...

  6. 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(1)--后端

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(1)--后端 chsakell分享了前端使用AngularJS,后端使用ASP.NET Web API的购物车 ...

  7. 对一个前端AngularJS,后端OData,ASP.NET Web API案例的理解

    依然chsakell,他写了一篇前端AngularJS,后端OData,ASP.NET Web API的Demo,关于OData在ASP.NET Web API中的正删改查没有什么特别之处,但在前端调 ...

  8. 在ASP.NET Web API项目中使用Hangfire实现后台任务处理

    当前项目中有这样一个需求:由前端用户的一个操作,需要触发到不同设备的消息推送.由于推送这个具体功能,我们采用了第三方的服务.而这个服务调用有时候可能会有延时,为此,我们希望将消息推送与用户前端操作实现 ...

  9. 如何创建一个Asp .Net Web Api项目

    1.点击文件=>新建=>项目 2.创建一个Asp .NET Web项目 3.选择Empty,然后选中下面的MVC和Web Api,也可以直接选择Web Api选项,注意将身份验证设置为无身 ...

随机推荐

  1. Eclipse的git插件冲突合并方法

    Eclipse有一个git的插件叫EGit,用于实现本地代码和远程代码对比.合并以及提交.但是在本地代码和远程代码有冲突的时候,EGit的处理方案还是有点复杂.今天就彻底把这些步骤给理清楚,并公开让一 ...

  2. web html调用百度地图

    如果想在自己的网页上面加入百度地图的话,可以用百度地图的api.具体使用方法如下: 第一步:进入百度创建地图的网站http://api.map.baidu.com/lbsapi/creatmap/,搜 ...

  3. 4.自定义数据《jquery实战》

    4.4 元素中的存储自定义数据 data([key],[value]) 在元素上存放数据,返回jQuery对象. key (String) 存储的数据名. key,value (String,Any) ...

  4. Git(四)Git的分支管理

    一. 创建合并分支原理 在我们每次的提交,Git都把它们串成一条时间线,这条时间线就是一个分支.截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支.HEAD指针严格来说不是指 ...

  5. 主席树初步学习笔记(可持久化数组?静态区间第k大?)

    我接触 OI也快1年了,然而只写了3篇博客...(而且还是从DP跳到了主席树),不知道我这个机房吊车尾什么时候才能摸到大佬们的脚后跟orz... 前言:主席树这个东西,可以说是一种非常畸形的数据结构( ...

  6. 【LOJ】#2090. 「ZJOI2016」旅行者

    题解 每次按较长边把矩形分成两半,找一个中间轴,轴上的每个点跑一边最短路更新所有的答案 然后把矩形分成两半,递归下去 代码 #include <bits/stdc++.h> #define ...

  7. JAVA 画图板实现(基本画图功能+界面UI)二、功能实现及重绘实现

    上篇博客中介绍了界面的实现方法,在这篇博客中将对每个按钮的功能的实现进行讲解并介绍重绘 首先肯定要添加事件监听机制了,那么问题来了,事件源对象是谁?需要添加什么方法?事件接口是什么? 1.我们需要点击 ...

  8. JavaScript 对象Array,Map,Set使用

    for(int i = 0 :i < 3 ;i++ ){ //[重点说三遍] 在说明每个对象的用法之前,首先说明 JavaScript 对象的使用一定要注意浏览器的兼容性问题!尤其是IE的版本! ...

  9. Codeforces-1084C

    title: Codeforces-1084C date: 2018-12-13 16:02:04 tags: acm 刷题 categories: Codeforces 概述 好久没写博客了,,,最 ...

  10. 【python学习-5】面向对象的python

    python是一种面向对象的编程语言,虽然与C++一样,支持面向过程的程序设计,python完全可以使用函数.模块等方式来完成工作,但是当使用python编写一个较大的项目时,则应该考虑使用面向对象的 ...