ASP.NET 5 và AngularJS Phần 3, Chèn Client Routing

Bạn có thể tải code về trong bài viết blog này từ GitHub: https://github.com/StephenWalther/MovieAngularJSApp Trong bài viết trên blog này, tôi giải thích làm thế nào để phân chia ứng dụng trang đơn (SPA) thành nhiều trang ảo. Tôi sẽ sử dụng AngularJS routing để tạo ra danh sách, thêm, sửa, xóa.

Định nghĩa Client-Side Routes

Bước đầu tiên là xác định các client-side routes. Bạn xác định các routes như trong các tập tin JavaScript - app.js - trong khi bạn tạo ra ứng dụng AngularJS của bạn.

(function () {
    'use strict';

    config.$inject = ['$routeProvider', '$locationProvider'];

    angular.module('moviesApp', [
        'ngRoute', 'moviesServices'
    ]).config(config);

    function config($routeProvider, $locationProvider) {
        $routeProvider
            .when('/', {
              templateUrl: '/Views/list.html',
              controller: 'MoviesListController'
            })
            .when('/movies/add', {
                templateUrl: '/Views/add.html',
                controller: 'MoviesAddController'
            })
            .when('/movies/edit/:id', {
                templateUrl: '/Views/edit.html',
                controller: 'MoviesEditController'
            })
            .when('/movies/delete/:id', {
                templateUrl: '/Views/delete.html',
                controller: 'MoviesDeleteController'
            });

        $locationProvider.html5Mode(true);
    }

})();

Trong đoạn mã trên, tôi đã thêm 4 routes. Đối với mỗi route tôi định nghĩa một templateUrl định vị trí của HTML cho các routes. Tôi cũng đã liên kết từng routes với một bộ điều khiển AngularJS. Bạn cũng nên lưu ý rằng tôi đã thêm ngRoute là một phụ thuộc cho moviesApp. Tôi sẽ không nhận được bất kỳ chức năng routes mà không thêm một dependency vào các mô-đun ngRoute:

angular.module('moviesApp', [
        'ngRoute', 'moviesServices'
    ]).config(config);

Cuối cùng, nhận thấy rằng tôi đã kích hoạt html5Mode trong dòng cuối cùng của mã. Việc kích hoạt html5Mode cho phép bạn sử dụng các URL tự nhiên mà trông như: https://movies.com/movies/add/

Thay vì URL hashbang mà trông như: http://movies.com/#!/movies/add/

Sử dụng html5mode tương thích với bất kỳ trình duyệt hỗ trợ HTML5 History API (IE10 + và mọi trình duyệt khác).

Các yêu cầu viết lại trên máy chủ

Nếu bạn điều hướng đến /movies/add và nhấn nút Reload, sau đó bạn sẽ nhận được một response 404 từ máy chủ. Bạn sẽ nhận được một lỗi 404 vì /movies/add là một route phía khách hàng và không phải là một route phía máy chủ. Để khắc phục vấn đề này, bạn cần cấu hình IIS để chuyển hướng tất cả các yêu cầu trở về home. Thêm tập tin web.config sau vào thư mục wwwroot của bạn:

<!-- from http://stackoverflow.com/questions/25916851/wrapping-staticfilemiddleware-to-redirect-404-errors -->
<configuration>
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true" />
  <rewrite>
    <rules>
      <!--Redirect selected traffic to index -->
      <rule name="Index Rule" stopProcessing="true">
        <match url=".*" />
        <conditions logicalGrouping="MatchAll">
          <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
          <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/api/" negate="true" />
        </conditions>
        <action type="Rewrite" url="/index.html" />
      </rule>
    </rules>
  </rewrite>
</system.webServer>
</configuration>

Chú ý rằng tôi cũng tạo điều kiện cho runAllManagedModulesForAllRequests (RAMMFAR). Tôi cần phải kích hoạt RAMMFAR để xử lý yêu cầu PUT và DELETE.

Tạo Controllers Khách hàng

Chúng ta cần một bộ điều khiển khách hàng riêng biệt để tương ứng với routes. Đây là cách client controllers được định nghĩa trong file moviesController.js:

(function () {
    'use strict';

    angular
        .module('moviesApp')
        .controller('MoviesListController', MoviesListController)
        .controller('MoviesAddController', MoviesAddController)
        .controller('MoviesEditController', MoviesEditController)
        .controller('MoviesDeleteController', MoviesDeleteController);

    /* Movies List Controller  */
    MoviesListController.$inject = ['$scope', 'Movie'];

    function MoviesListController($scope, Movie) {
        $scope.movies = Movie.query();
    }

    /* Movies Create Controller */
    MoviesAddController.$inject = ['$scope', '$location', 'Movie'];

    function MoviesAddController($scope, $location, Movie) {
        $scope.movie = new Movie();
        $scope.add = function () {
            $scope.movie.$save(function () {
                $location.path('/');
            });
        };
    }

    /* Movies Edit Controller */
    MoviesEditController.$inject = ['$scope', '$routeParams', '$location', 'Movie'];

    function MoviesEditController($scope, $routeParams, $location, Movie) {
        $scope.movie = Movie.get({ id: $routeParams.id });
        $scope.edit = function () {
            $scope.movie.$save(function () {
                $location.path('/');
            });
        };
    }

    /* Movies Delete Controller  */
    MoviesDeleteController.$inject = ['$scope', '$routeParams', '$location', 'Movie'];

    function MoviesDeleteController($scope, $routeParams, $location, Movie) {
        $scope.movie = Movie.get({ id: $routeParams.id });
        $scope.remove = function () {
            $scope.movie.$remove({id:$scope.movie.Id}, function () {
                $location.path('/');
            });
        };
    }

})();

Chú ý rằng tôi không tạo ra một bộ điều khiển duy nhất với nhiều hành động (như tôi sẽ làm trong trường hợp phía máy chủ MVC). Thay vào đó, tôi tạo ra một bộ điều khiển riêng biệt tương ứng với danh sách, thêm, sửa, xóa.

Mỗi bộ điều khiển tương tác với các API Web ASP.NET với sự giúp đỡ của một tài nguyên Movie. Ví dụ, các MoviesListController lấy danh sách các phim từ máy chủ bằng cách gọi Movie.query(). Và, MoviesAddController posts một bộ phim mới vào máy chủ bằng cách gọi movie.$save(). Các nguồn tài nguyên Movie được định nghĩa trong một module có tên moviesServices.js:

(function () {
    'use strict';

    angular
        .module('moviesServices', ['ngResource'])
        .factory('Movie', Movie);

    Movie.$inject = ['$resource'];

    function Movie($resource) {
        return $resource('/api/movies/:id');
    }
})();

Tạo Giao diện chính

Các trang index.html trong Movies app hiện đang làm việc như một Master Page/Layout Page. Nó chứa đựng những nội dung đó sẽ được chia sẻ với tất cả các trang. Dưới đây là nội dung cập nhật cho trang index.html:

<!DOCTYPE html>
<html ng-app="moviesApp">
<head>
    <base href="/">
    <meta charset="utf-8" />
    <title>Movies</title>

    <!-- jQuery -->
    <script src="//code.jquery.com/jquery-1.11.2.min.js"></script>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap-theme.min.css">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>

    <!-- AngularJS-->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-resource.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-route.js"></script>
    <script src="app.js"></script>
</head>
<body ng-cloak>
    <div class="container-fluid">
        <ng-view></ng-view>
    </div>
</body>
</html>

Có ba điều chính mà bạn cần lưu ý về trang index.html. Đầu tiên, nhận thấy rằng đã thêm Twitter Bootstrap để ứng dụng của tôi lấy styles Bootstrap và script từ maxcdn bootstrap CDN. Thứ hai, nhận thấy rằng đã kéo module AngularJS ngRoute từ CDN của Google với tag:

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-route.js"></script>

Cuối cùng, thông báo rằng tôi đang sử dụng AngularJS directive (yếu tố tùy chỉnh) để đánh dấu nơi mà tôi muốn các trang nội dung được thêm vào trang index.html. Ví dụ, khi tôi điều hướng đến /movies/add sau đó trang add.html sẽ được nạp vào các vị trí được đánh dấu bởi các directive.

Tạo trang ảo

Movies app có 4 trang ảo để làm các thao tác CRUD:

  • list.html - Danh sách các bộ phim trong một grid.
  • add.html - Cho phép bạn thêm các bộ phim mới.
  • edit.html - Cho phép bạn chỉnh sửa phim hiện có.
  • delete.html - Cho phép bạn xóa phim hiện có.

Dưới đây là những gì trang list.html show:

<div>

    <h1>List Movies</h1>
    <table class="table table-bordered table-striped">
        <thead>
            <tr>
                <th></th>
                <th>Title</th>
                <th>Director</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="movie in movies">
                <td>
                    <a href="/movies/edit/{{movie.Id}}" class="btn btn-default btn-xs">edit</a>
                    <a href="/movies/delete/{{movie.Id}}" class="btn btn-danger btn-xs">delete</a>
                </td>
                <td>{{movie.Title}}</td>
                <td>{{movie.Director}}</td>
            </tr>
        </tbody>
    </table>

    <p>
        <a href="/movies/add" class="btn btn-primary">Add New Movie</a>
    </p>
</div>

Các trang list.html sử dụng các ng-repeat directive để hiển thị danh sách các phim trong một bảng HTML. Tôi để kiểu bảng với Twitter Bootstrap.

alt

Trang add.html chứa một dạng HTML được tạo ra với sự giúp đỡ của Boostrap:

<h1>Add Movie</h1>
<div class="row">
    <div class="col-md-5">

        <form ng-submit="add()">
            <div class="form-group">
                <label for="Title">Movie Title</label>
                <input name="Title" type="text" class="form-control" placeholder="Enter title" ng-model="movie.Title">
            </div>
            <div class="form-group">
                <label for="Director">Director</label>
                <input name="Director" type="text" class="form-control" placeholder="Enter director" ng-model="movie.Director">
            </div>
            <a href="/" class="btn btn-default">Cancel</a>
            <button type="submit" class="btn btn-primary">Save</button>
        </form>
    </div>
</div>

alt

Chú ý rằng <input> elements bao gồm thuộc tính ng-model . Những thuộc tính này tạo ra hai chiều liên kết dữ liệu giữa các form fields và model. Khi bạn nhấp vào nút Submit, thuộc tính ng-submit trên thẻ <form> gọi add () định nghĩa tại các điều khiển. Các trang edit.html trông rất giống với trang add.html:

<h1>Edit Movie</h1>

<div class="row">
    <div class="col-md-5">

        <form ng-submit="edit()">
            <div class="form-group">
                <label for="Title">Movie Title</label>
                <input name="Title" type="text" class="form-control" placeholder="Enter title" ng-model="movie.Title">
            </div>
            <div class="form-group">
                <label for="Director">Director</label>
                <input name="Director" type="text" class="form-control" placeholder="Enter director" ng-model="movie.Director">
            </div>
            <a href="/" class="btn btn-default">Cancel</a>
            <button type="submit" class="btn btn-primary">Save</button>
        </form>

    </div>
</div>

Các thuộc tính ng-model áp dụng cho các <input> element bởi form được điền trước với giá trị property của bộ phim đang được chỉnh sửa. Vì vậy, chúng tôi nhận được danh hiệu "Star Wars" và đạo diễn "Lucas" tự động.

alt

Khi bạn submit form edit, bộ điều khiển với edit() được gọi. Cuối cùng, trang delete.html được sử dụng để xác nhận rằng bạn thực sự muốn xóa một bộ phim từ các cơ sở dữ liệu.

alt

Dưới đây là nội dung của các trang delete.html:

 <div class="alert alert-warning">
    Are you sure that you want to permanently delete the movie
    "{{movie.Title}}"?
</div>

<a href="/" class="btn btn-default">Cancel</a>
<button class="btn btn-danger" ng-click="remove()">OK</button>

Khi bạn nhấp vào nút Delete, phương thức remove () được gọi.

Tóm lược

Trong bài viết trên blog này, tôi tập trung client-side routing bằng cách sử dụng mô-đun AngularJS ngRoute. Bạn đã học cách sử dụng client-side routing với một ứng dụng ASP.NET 5. Hiện tại, bạn không thể thực sự tạo ra phim mới hoặc chỉnh sửa bộ phim có sẵn. Trong bài blog tiếp theo, tôi sẽ giải thích làm thế nào bạn có thể sử dụng Entity Framework 7 để lưu thay đổi cho một cơ sở dữ liệu.

Nguồn: http://stephenwalther.com/archive/2015/01/16/asp-net-5-and-angularjs-part-3-adding-client-routing