您的位置:首页 > Web前端 > AngularJS

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

2019-08-13 22:20 1446 查看
原文链接:https://www.geek-share.com/detail/2657888902.html

 

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增改查以及上传图片

 

显示带分页过滤条件的Movie

 

 

/关于分页,通常情况下传的参数包括:当前页、页容量
//关于过滤:传过滤字符串
[AllowAnonymous]
[Route("{page:int=0}/{pageSize=3}/{filter?}")] //给路由中的变量赋上初值
public HttpResponseMessage Get(HttpRequestMessage request, int? page, int? pageSize, string filter = null)
{
int currentPage = page.Value; // 当前页
int currentPageSize = pageSize.Value;//页容量

return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;
List<Movie> movies = null;
int totalMovies = new int();//总数

if (!string.IsNullOrEmpty(filter)) //如果有过滤条件
{
movies = _moviesRepository.GetAll()
.OrderBy(m => m.ID)
.Where(m => m.Title.ToLower()
.Contains(filter.ToLower().Trim()))
.ToList();
}
else
{
movies = _moviesRepository.GetAll().ToList();
}

totalMovies = movies.Count(); //总数量
movies = movies.Skip(currentPage * currentPageSize)
.Take(currentPageSize)
.ToList();

//领域模型转换成视图模型
IEnumerable<MovieViewModel> moviesVM = Mapper.Map<IEnumerable<Movie>, IEnumerable<MovieViewModel>>(movies);

PaginationSet<MovieViewModel> pagedSet = new PaginationSet<MovieViewModel>()
{
Page = currentPage,
TotalCount = totalMovies,
TotalPages = (int)Math.Ceiling((decimal)totalMovies / currentPageSize),
Items = moviesVM
};

//再把视图模型和分页等数据放在响应中返回
response = request.CreateResponse<PaginationSet<MovieViewModel>>(HttpStatusCode.OK, pagedSet);

return response;
});
}

 

PagenationSet<T>是对分页和领域模型集合的一个封装。

 

namespace HomeCinema.Web.Infrastructure.Core
{
public class PaginationSet<T>
{
public int Page { get; set; }

public int Count
{
get
{
return (null != this.Items) ? this.Items.Count() : 0;
}
}

public int TotalPages { get; set; }
public int TotalCount { get; set; }

public IEnumerable<T> Items { get; set; }
}
}

 

界面部分,首页中使用了一个自定义的directive。

 

<side-bar></side-bar>

 

在side-bar所对应的html部分提供了获取所有Movie的链接。

 

<a ng-href="#/movies/">Movies<i class="fa fa-film fa-fw pull-right"></i></a>

 

而在app.js的路由设置中:

 

.when("/movies", {
templateUrl: "scripts/spa/movies/movies.html",
controller: "moviesCtrl"
})

 

先来看scripts/spa/movies/movies.html页面摘要:

 

<!--过滤-->
<input id="inputSearchMovies" type="search" ng-model="filterMovies" placeholder="Filter, search movies..">
<button class="btn btn-primary" ng-click="search();"></button>
<button class="btn btn-primary" ng-click="clearSearch();"></button>

<!--列表-->
<a class="pull-left" ng-href="#/movies/{{movie.ID}}" title="View {{movie.Title}} details">
<img class="media-object" height="120" ng-src="../../Content/images/movies/{{movie.Image}}" alt="" />
</a>
<h4 class="media-heading">{{movie.Title}}</h4>
<strong>{{movie.Director}}</strong>
<strong>{{movie.Writer}}</strong>
<strong>{{movie.Producer}}</strong>
<a class="fancybox-media" ng-href="{{movie.TrailerURI}}">Trailer<i class="fa fa-video-camera fa-fw"></i></a>
<span component-rating="{{movie.Rating}}"></span>
<label class="label label-info">{{movie.Genre}}</label>
<available-movie is-available="{{movie.IsAvailable}}"></available-movie>

<!--分页-->
<custom-pager page="{{page}}" custom-path="{{customPath}}" pages-count="{{pagesCount}}" total-count="{{totalCount}}" search-func="search(page)"></custom-pager>

 

再来看对应的moviesCtrl控制器:

 

(function (app) {
'use strict';

app.controller('moviesCtrl', moviesCtrl);

moviesCtrl.$inject = ['$scope', 'apiService','notificationService'];

//所有变量都赋初值pageClass, loadingMovies, page, pagesCount, movies, search方法,clearSearch方法
function moviesCtrl($scope, apiService, notificationService) {
$scope.pageClass = 'page-movies';
$scope.loadingMovies = true;
$scope.page = 0;
$scope.pagesCount = 0;

$scope.Movies = [];

$scope.search = search;
$scope.clearSearch = clearSearch;

//当前页索引作为参数传递
function search(page) {
page = page || 0;

$scope.loadingMovies = true;

//这里的object键值将被放在路由中以便action方法接收
var config = {
params: {
page: page,
pageSize: 6,
filter: $scope.filterMovies
}
};

apiService.get('/api/movies/', config,
moviesLoadCompleted,
moviesLoadFailed);
}

function moviesLoadCompleted(result) {
$scope.Movies = result.data.Items;
$scope.page = result.data.Page;
$scope.pagesCount = result.data.TotalPages;
$scope.totalCount = result.data.TotalCount;
$scope.loadingMovies = false;

if ($scope.filterMovies && $scope.filterMovies.length)
{
notificationService.displayInfo(result.data.Items.length + ' movies found');
}

}

function moviesLoadFailed(response) {
notificationService.displayError(response.data);
}

function clearSearch() {
$scope.filterMovies = '';
search();
}

$scope.search();
}

})(angular.module('homeCinema'));

 

然后对于分页,当然需要自定义directive,如下:

 

<custom-pager page="{{page}}" custom-path="{{customPath}}" pages-count="{{pagesCount}}" total-count="{{totalCount}}" search-func="search(page)"></custom-pager>

 

对应的html部分来自spa/layout/pager.html

 

<div>
<div ng-hide="(!pagesCount || pagesCount < 2)" style="display:inline">
<ul class="pagination pagination-sm">
<li><a ng-hide="page == 0" ng-click="search(0)"><<</a></li>
<li><a ng-hide="page == 0" ng-click="search(page-1)"><</a></li>
<li ng-repeat="n in range()" ng-class="{active: n == page}">
<a ng-click="search(n)" ng-if="n != page">{{n+1}}</a>
<span ng-if="n == page">{{n+1}}</span>
</li>
<li><a ng-hide="page == pagesCount - 1" ng-click="search(pagePlus(1))">></a></li>
<li><a ng-hide="page == pagesCount - 1" ng-click="search(pagesCount - 1)">>></a></li>
</ul>
</div>
</div>

 

自定义的directive部分写在了spa/layout/customPager.directive.js里:

 

(function(app) {
'use strict';

app.directive('customPager', customPager);

function customPager() {
return {
scope: {
page: '@',
pagesCount: '@',
totalCount: '@',
searchFunc: '&',
customPath: '@'
},
replace: true,
restrict: 'E',
templateUrl: '/scripts/spa/layout/pager.html',
controller: ['$scope', function ($scope) {
$scope.search = function (i) {
if ($scope.searchFunc) {
$scope.searchFunc({ page: i });
}
};

$scope.range = function () {
if (!$scope.pagesCount) { return []; }
var step = 2;
var doubleStep = step * 2;
var start = Math.max(0, $scope.page - step);
var end = start + 1 + doubleStep;
if (end > $scope.pagesCount) { end = $scope.pagesCount; }

var ret = [];
for (var i = start; i != end; ++i) {
ret.push(i);
}

return ret;
};

$scope.pagePlus = function(count)
{
return +$scope.page + count;
}
}]
}
}

})(angular.module('common.ui'));

 

点击Movie列表中某一项的图片来到明细页

 

html部分:

 

<a class="pull-left" ng-href="#/movies/{{movie.ID}}" title="View {{movie.Title}} details">
<img class="media-object" height="120" ng-src="../../Content/images/movies/{{movie.Image}}" alt="" />
</a>

 

在app.js中已经定义了路由规则:

 

.when("/movies/:id", {
templateUrl: "scripts/spa/movies/details.html",
controller: "movieDetailsCtrl",
resolve: { isAuthenticated: isAuthenticated }
})

 

来看API部分:

 

[Route("details/{id:int}")]
public HttpResponseMessage Get(HttpRequestMessage request, int id)
{
return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;
var movie = _moviesRepository.GetSingle(id);

MovieViewModel movieVM = Mapper.Map<Movie, MovieViewModel>(movie);

response = request.CreateResponse<MovieViewModel>(HttpStatusCode.OK, movieVM);

return response;
});
}

 

再来看明细部分的页面摘要:scripts/spa/movies/details.html

 

<h5>{{movie.Title}}</h5>
<div class="panel-body" ng-if="!loadingMovie">
<a class="pull-right" ng-href="#/movies/{{movie.ID}}" title="View {{movie.Title}} details">
<img class="media-object" height="120" ng-src="../../Content/images/movies/{{movie.Image}}" alt="" />
</a>
<h4 class="media-heading">{{movie.Title}}</h4>
Directed by: <label>{{movie.Director}}</label><br />
Written by: <label>{{movie.Writer}}</label><br />
Produced by: <label>{{movie.Producer}}</label><br />
Rating: <span component-rating='{{movie.Rating}}'></span>
<br />
<label class="label label-info">{{movie.Genre}}</label>
<available-movie is-available="{{movie.IsAvailable}}"></available-movie>

<a ng-href="{{movie.TrailerURI}}" >View Trailer <i class="fa fa-video-camera pull-right"></i></a>
<a ng-href="#/movies/edit/{{movie.ID}}" class="btn btn-default">Edit movie </a>
</div>

 

控制器部分为:scripts/spa/movies/movieDetailsCtrl.js

 

(function (app) {
'use strict';

app.controller('movieDetailsCtrl', movieDetailsCtrl);

movieDetailsCtrl.$inject = ['$scope', '$location', '$routeParams', '$modal', 'apiService', 'notificationService'];

function movieDetailsCtrl($scope, $location, $routeParams, $modal, apiService, notificationService) {
$scope.pageClass = 'page-movies';
$scope.movie = {};
$scope.loadingMovie = true;
$scope.loadingRentals = true;
$scope.isReadOnly = true;
$scope.openRentDialog = openRentDialog;
$scope.returnMovie = returnMovie;
$scope.rentalHistory = [];
$scope.getStatusColor = getStatusColor;
$scope.clearSearch = clearSearch;
$scope.isBorrowed = isBorrowed;

function loadMovie() {

$scope.loadingMovie = true;

apiService.get('/api/movies/details/' + $routeParams.id, null,
movieLoadCompleted,
movieLoadFailed);
}

function movieLoadCompleted(result) {
$scope.movie = result.data;
$scope.loadingMovie = false;
}

function movieLoadFailed(response) {
notificationService.displayError(response.data);
}

loadMovie();
}

})(angular.module('homeCinema'));

 

更新

 

 

在Movie的明细页给出了编辑按钮:

 

<a ng-href="#/movies/edit/{{movie.ID}}" class="btn btn-default">Edit movie </a>

 

而在app.js的路由设置中:

 

.when("/movies/edit/:id", {
templateUrl: "scripts/spa/movies/edit.html",
controller: "movieEditCtrl"
})

 

来看编辑明细页摘要:scripts/spa/movies/edit.html

 

<img ng-src="../../Content/images/movies/{{movie.Image}}" class="avatar img-responsive" alt="avatar">
<input type="file" ng-file-select="prepareFiles($files)">

<form class="form-horizontal" role="form" novalidate angular-validator name="editMovieForm" angular-validator-submit="UpdateMovie()">

<input class="form-control" name="title" type="text" ng-model="movie.Title" validate-on="blur" required required-message="'Movie title is required'">

<select ng-model="movie.GenreId" class="form-control black" ng-options="option.ID as option.Name for option in genres" required></select>
<input type="hidden" name="GenreId" ng-value="movie.GenreId" />

<input class="form-control" type="text" ng-model="movie.Director" name="director" validate-on="blur" required required-message="'Movie director is required'">

<input class="form-control" type="text" ng-model="movie.Writer" name="writer" validate-on="blur" required required-message="'Movie writer is required'">

<input class="form-control" type="text" ng-model="movie.Producer" name="producer" validate-on="blur" required required-message="'Movie producer is required'">

<input type="text" class="form-control" name="dateReleased" datepicker-popup="{{format}}" ng-model="movie.ReleaseDate" is-open="datepicker.opened" datepicker-options="dateOptions" ng-required="true" datepicker-append-to-body="true" close-text="Close" validate-on="blur" required required-message="'Date Released is required'" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="openDatePicker($event)"></button>
</span>

<input class="form-control" type="text" ng-model="movie.TrailerURI" name="trailerURI" validate-on="blur" required required-message="'Movie trailer is required'" ng-pattern="/^(https?\:\/\/)?(www\.youtube\.com|youtu\.?be)\/.+$/" invalid-message="'You must enter a valid YouTube URL'">

<textarea class="form-control" ng-model="movie.Description" name="description" rows="3" validate-on="blur" required required-message="'Movie description is required'" />

<span component-rating="{{movie.Rating}}" ng-model="movie.Rating" class="form-control"></span>

<input type="submit" class="btn btn-primary" value="Update" />
<a class="btn btn-default" ng-href="#/movies/{{movie.ID}}">Cancel</a>
</form>

 

再来看编辑明细页控制器:scripts/spa/movies/movieEditCtrl.js

 

(function (app) {
'use strict';

app.controller('movieEditCtrl', movieEditCtrl);

movieEditCtrl.$inject = ['$scope', '$location', '$routeParams', 'apiService', 'notificationService', 'fileUploadService'];

function movieEditCtrl($scope, $location, $routeParams, apiService, notificationService, fileUploadService) {
$scope.pageClass = 'page-movies';
$scope.movie = {};
$scope.genres = [];
$scope.loadingMovie = true;
$scope.isReadOnly = false;
$scope.UpdateMovie = UpdateMovie;
$scope.prepareFiles = prepareFiles;
$scope.openDatePicker = openDatePicker;

$scope.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
$scope.datepicker = {};

var movieImage = null;

//加载movie
function loadMovie() {

$scope.loadingMovie = true;

apiService.get('/api/movies/details/' + $routeParams.id, null,
movieLoadCompleted,
movieLoadFailed);
}

//加载movie完成后加载genre
function movieLoadCompleted(result) {
$scope.movie = result.data;
$scope.loadingMovie = false;

//再加载genre
loadGenres();
}

function movieLoadFailed(response) {
notificationService.displayError(response.data);
}

function genresLoadCompleted(response) {
$scope.genres = response.data;
}

function genresLoadFailed(response) {
notificationService.displayError(response.data);
}

function loadGenres() {
apiService.get('/api/genres/', null,
genresLoadCompleted,
genresLoadFailed);
}

function UpdateMovie() {
//上传图片
if (movieImage) {
fileUploadService.uploadImage(movieImage, $scope.movie.ID, UpdateMovieModel);
}
else
UpdateMovieModel();
}

//实施更新
function UpdateMovieModel() {
apiService.post('/api/movies/update', $scope.movie,
updateMovieSucceded,
updateMovieFailed);
}

function prepareFiles($files) {
movieImage = $files;
}

function updateMovieSucceded(response) {
console.log(response);
notificationService.displaySuccess($scope.movie.Title + ' has been updated');
$scope.movie = response.data;
movieImage = null;
}

function updateMovieFailed(response) {
notificationService.displayError(response);
}

function openDatePicker($event) {
$event.preventDefault();
$event.stopPropagation();

$scope.datepicker.opened = true;
};

loadMovie();
}

})(angular.module('homeCinema'));

 

对于上传图片,放在了一个自定义的服务中,在spa/services/fileUploadService.js中:

 

(function (app) {
'use strict';

app.factory('fileUploadService', fileUploadService);

fileUploadService.$inject = ['$rootScope', '$http', '$timeout', '$upload', 'notificationService'];

function fileUploadService($rootScope, $http, $timeout, $upload, notificationService) {

$rootScope.upload = [];

var service = {
uploadImage: uploadImage
}

function uploadImage($files, movieId, callback) {
//$files: an array of files selected
for (var i = 0; i < $files.length; i++) {
var $file = $files[i];
(function (index) {
$rootScope.upload[index] = $upload.upload({
url: "api/movies/images/upload?movieId=" + movieId, // webapi url
method: "POST",
file: $file
}).progress(function (evt) {
}).success(function (data, status, headers, config) {
// file is uploaded successfully
notificationService.displaySuccess(data.FileName + ' uploaded successfully');
callback();
}).error(function (data, status, headers, config) {
notificationService.displayError(data.Message);
});
})(i);
}
}

return service;
}

})(angular.module('common.core'));

 

在后端API中,对应的更新和上传图片action如下:

 

[HttpPost]
[Route("update")]
public HttpResponseMessage Update(HttpRequestMessage request, MovieViewModel movie)
{
return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;

if (!ModelState.IsValid)
{
response = request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
else
{
var movieDb = _moviesRepository.GetSingle(movie.ID);
if (movieDb == null)
response = request.CreateErrorResponse(HttpStatusCode.NotFound, "Invalid movie.");
else
{
movieDb.UpdateMovie(movie);
movie.Image = movieDb.Image;
_moviesRepository.Edit(movieDb);

_unitOfWork.Commit();
response = request.CreateResponse<MovieViewModel>(HttpStatusCode.OK, movie);
}
}

return response;
});
}

[MimeMultipart]
[Route("images/upload")]
public HttpResponseMessage Post(HttpRequestMessage request, int movieId)
{
return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;

var movieOld = _moviesRepository.GetSingle(movieId);
if (movieOld == null)
response = request.CreateErrorResponse(HttpStatusCode.NotFound, "Invalid movie.");
else
{
var uploadPath = HttpContext.Current.Server.MapPath("~/Content/images/movies");

var multipartFormDataStreamProvider = new UploadMultipartFormProvider(uploadPath);

// Read the MIME multipart asynchronously
Request.Content.ReadAsMultipartAsync(multipartFormDataStreamProvider);

string _localFileName = multipartFormDataStreamProvider
.FileData.Select(multiPartData => multiPartData.LocalFileName).FirstOrDefault();

// Create response
FileUploadResult fileUploadResult = new FileUploadResult
{
LocalFilePath = _localFileName,

FileName = Path.GetFileName(_localFileName),

FileLength = new FileInfo(_localFileName).Length
};

// update database
movieOld.Image = fileUploadResult.FileName;
_moviesRepository.Edit(movieOld);
_unitOfWork.Commit();

response = request.CreateResponse(HttpStatusCode.OK, fileUploadResult);
}

return response;
});
}

 

添加

 

在sidebar的html部分为:

 

<li><a ng-href="#/movies/add">Add movie</a></li>

 

在app.js的路由设置中:

 

.when("/movies/add", {
templateUrl: "scripts/spa/movies/add.html",
controller: "movieAddCtrl",
resolve: { isAuthenticated: isAuthenticated }
})

 

scripts/spa/movies/add.html部分与edit.html部分相似。


scripts/spa/movies/movieAddCtrl.js中:

 

(function (app) {
'use strict';

app.controller('movieAddCtrl', movieAddCtrl);

movieAddCtrl.$inject = ['$scope', '$location', '$routeParams', 'apiService', 'notificationService', 'fileUploadService'];

function movieAddCtrl($scope, $location, $routeParams, apiService, notificationService, fileUploadService) {

$scope.pageClass = 'page-movies';
$scope.movie = { GenreId: 1, Rating: 1, NumberOfStocks: 1 };

$scope.genres = [];
$scope.isReadOnly = false;
$scope.AddMovie = AddMovie;
$scope.prepareFiles = prepareFiles;
$scope.openDatePicker = openDatePicker;
$scope.changeNumberOfStocks = changeNumberOfStocks;

$scope.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
$scope.datepicker = {};

var movieImage = null;

function loadGenres() {
apiService.get('/api/genres/', null,
genresLoadCompleted,
genresLoadFailed);
}

function genresLoadCompleted(response) {
$scope.genres = response.data;
}

function genresLoadFailed(response) {
notificationService.displayError(response.data);
}

function AddMovie() {
AddMovieModel();
}

function AddMovieModel() {
apiService.post('/api/movies/add', $scope.movie,
addMovieSucceded,
addMovieFailed);
}

function prepareFiles($files) {
movieImage = $files;
}

function addMovieSucceded(response) {
notificationService.displaySuccess($scope.movie.Title + ' has been submitted to Home Cinema');
$scope.movie = response.data;

//添加movie成功后再上传图片
if (movieImage) {
fileUploadService.uploadImage(movieImage, $scope.movie.ID, redirectToEdit);
}
else
redirectToEdit();
}

function addMovieFailed(response) {
console.log(response);
notificationService.displayError(response.statusText);
}

function openDatePicker($event) {
$event.preventDefault();
$event.stopPropagation();

$scope.datepicker.opened = true;
};

function redirectToEdit() {
$location.url('movies/edit/' + $scope.movie.ID);
}

function changeNumberOfStocks($vent)
{
var btn = $('#btnSetStocks'),
oldValue = $('#inputStocks').val().trim(),
newVal = 0;

if (btn.attr('data-dir') == 'up') {
newVal = parseInt(oldValue) + 1;
} else {
if (oldValue > 1) {
newVal = parseInt(oldValue) - 1;
} else {
newVal = 1;
}
}
$('#inputStocks').val(newVal);
$scope.movie.NumberOfStocks = newVal;
console.log($scope.movie);
}

loadGenres();
}

})(angular.module('homeCinema'));

 

后端对应的添加API部分:

 

[HttpPost]
[Route("add")]
public HttpResponseMessage Add(HttpRequestMessage request, MovieViewModel movie)
{
return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;

if (!ModelState.IsValid)
{
response = request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
else
{
Movie newMovie = new Movie();
newMovie.UpdateMovie(movie);

for (int i = 0; i < movie.NumberOfStocks; i++)
{
Stock stock = new Stock()
{
IsAvailable = true,
Movie = newMovie,
UniqueKey = Guid.NewGuid()
};
newMovie.Stocks.Add(stock);
}

_moviesRepository.Add(newMovie);

_unitOfWork.Commit();

// Update view model
movie = Mapper.Map<Movie, MovieViewModel>(newMovie);
response = request.CreateResponse<MovieViewModel>(HttpStatusCode.Created, movie);
}

return response;
});
}

 

本系列结束~

 

转载于:https://www.cnblogs.com/darrenji/p/4945763.html

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐