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

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;
    });
}

本系列结束~

时间: 2024-10-22 04:39:12

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

对一个前端使用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/ 这里记录下对此项目的理解.分为如下几篇: ● 对一个前端使用AngularJ

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

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/ 这里记录下对此项目的理解.分为如下几篇: ● 对一个前端使用AngularJ

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

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/ 这里记录下对此项目的理解.分为如下几篇: ● 对一个前端使用AngularJ

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

原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证 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-

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

原文:购物车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-en

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

原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(1)--后端 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-

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

依然chsakell,他写了一篇前端AngularJS,后端OData,ASP.NET Web API的Demo,关于OData在ASP.NET Web API中的正删改查没有什么特别之处,但在前端调用API时,把各种调用使用$resouce封装在一个服务中的写法颇有借鉴意义. 文章:http://chsakell.com/2015/04/04/asp-net-web-api-feat-odata/源码:https://github.com/chsakell/odatawebapi 首先是领域模

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

当前项目中有这样一个需求:由前端用户的一个操作,需要触发到不同设备的消息推送.由于推送这个具体功能,我们采用了第三方的服务.而这个服务调用有时候可能会有延时,为此,我们希望将消息推送与用户前端操作实现异步执行,就是希望在后台自动执行,不阻塞前端用户的操作,而且最好能实现失败重试等功能. 经过一些研究比较,我们发现使用Hangfire这个组件可以较好地实现这个需求.为了给大家做一个演示,我这里简化了代码,做一个范例程序. 我在这里不准备详细介绍Hangfire的基本用法,有兴趣的同学们可以参考官方

前端使用AngularJS的$resource,后端ASP.NET Web API,实现增删改查

AngularJS中的$resource服务相比$http服务更适合与RESTful服务进行交互.本篇后端使用ASP.NET Web API, 前端使用$resource,实现增删改查. 领域和上下文 首先领域先行. public class StudentVm { [Key] public int Id { get; set; } public string Name { get; set; } public string Age { get; set; } } 上下文. public cla