0

ASP.NET 5 và AngularJS Phần 5, Form Validation

Đây là phần thứ 5 trong phần blog của loạt bài xây dựng ASP.NET 5 (ASP.NET vNext) ứng với AngularJS. Trong loạt bài đăng trên blog, tôi sẽ cho các bạn thấy làm thế nào để có thể tạo ra một ứng dụng Movie đơn giản sử dụng ASP.NET 5, MVC 6, và AngularJS.

Bạn có thể tải về mã đã thảo luận trong bài viết blog này từ GitHub: 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 để thực hiện validation cả phía client và phía server.

Validation Client-Side

Hãy bắt đầu với việc xác nhận phía máy khách. Chúng tôi sẽ sử dụng các tính năng được xây dựng trong AngularJS để validate thêm và chỉnh sửa các bộ phim trên forms.

alt

Đây là trang add.html:

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

        <form name="formAdd" novalidate ng-submit="add()">

             <ng-include src="'/partials/_edit.html'"></ng-include>

            <a href="/" class="btn btn-default">Cancel</a>
            <button type="submit" ng-disabled="formAdd.$invalid" class="btn btn-primary">Save</button>
        </form>

    </div>
</div>

Và, đây là trang edit.html:

<h1>Edit Movie</h1>

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

        <form name="formEdit" novalidate ng-submit="edit()">

            <ng-include src="'/partials/_edit.html'"></ng-include>

            <a href="/" class="btn btn-default">Cancel</a>
            <button type="submit" ng-disabled="formAdd.$invalid" class="btn btn-primary">Save</button>
        </form>

    </div>
</div>

Add và edit forms chứa đựng FORM và các BUTTON cho các forms. Các yếu tố FORM bao gồm một thuộc tính novalidate vô hiệu hóa HTML5 validation. Điều này là cần thiết bởi vì việc HTML5 validation xung đột với các AngularJS validation directives. Chú ý rằng các phần tử BUTTON bao gồm một ng-disabled directive. directive này được sử dụng để ngăn chặn người dùng submitting form khi có bất kỳ lỗi client-side validation. Bạn sẽ nhận thấy rằng cả hai forms sử dụng ng-include directive bao gồm một tên partial _edit.html. _edit.html chứa các form field thực tế.

<div class="bg-danger validationErrors" ng-show="validationErrors">
    <ul>
        <li ng-repeat="error in validationErrors">{{error}}</li>
    </ul>
</div>

<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"
           ng-required="true"
           ng-minlength="3" />
</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"
           ng-required="true" />
</div>
<div class="form-group">
    <label for="TicketPrice">Ticket Price</label>
    <input name="TicketPrice"
           type="text"
           class="form-control"
           placeholder="Enter ticket price"
           ng-model="movie.TicketPrice">
</div>
<div class="form-group">
    <label for="Release">Release Date</label>
    <p class="input-group" ng-controller="DatePickerController">
        <input type="text"
               class="form-control"
               datepicker-popup
               ng-model="movie.ReleaseDate"
               is-open="opened" />
        <span class="input-group-btn">
            <button type="button"
                    class="btn btn-default"
                    ng-click="open($event)">
                <i class="glyphicon glyphicon-calendar"></i>
            </button>
        </span>
    </p>
</div>

_edit.html form chứa các fields cho Title, Director, Ticket Price và Release Date. AngularJS directives được sử dụng để thực hiện việc validation sau đây: • Title - Yêu cầu Bắt buộc, phải có ít nhất 3 ký tự. • Director - Bắt buộc. • TicketPrice - Bắt buộc.

Nếu validation sai, sau đó các lớp ng-invalid CSS được áp dụng cho các field và field này xuất hiện với một nền màu đỏ.

input.ng-invalid.ng-touched {
    background-color: #FA787E;
}

input.ng-valid.ng-touched {
    background-color: #78FA89;
}

Tôi chạy vào một minor wrinkle khi tôi tạo ra một phần _edit.html. Khi một phần đã được yêu cầu từ máy chủ, các tập tin web.config mà tôi tạo ra trong phần 3 của loạt bài blog này chuyển hướng các yêu cầu đến trang index.html và tôi đã nhận một hồi quy vô hạn. Để làm phẳng wrinkle này, tôi đã phải loại trừ / partials / thư mục từ path rewrite.

<!-- 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" />
          <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/partials/" negate="true" />
        </conditions>
        <action type="Rewrite" url="/index.html" />
      </rule>
    </rules>
  </rewrite>
</system.webServer>
</configuration>

Cập nhật Movie Model

Client-side validation chỉ là một nửa của câu chuyện validation . Nửa còn lại của validation được xử lý bởi các ASP.NET 5 framework. Dưới đây là những gì Movie model trông như:

using System;
using System.ComponentModel.DataAnnotations;

namespace MovieAngularJSApp.Models
{
    public class Movie
    {
        public int Id { get; set; }

        [Required(ErrorMessage="Movie Title is Required")]
        [MinLength(3, ErrorMessage="Movie Title must be at least 3 characters")]
        public string Title { get; set; }

        [Required(ErrorMessage = "Movie Director is Required.")]
        public string Director { get; set; }

        [Range(0, 100, ErrorMessage ="Ticket price must be between 0 and 100 dollars.")]
        public decimal TicketPrice { get; set; }

        [Required(ErrorMessage="Movie Release Date is required")]
        public DateTime ReleaseDate { get; set; }
    }
}

Chú ý rằng các thuộc tính validation được áp dụng cho các thuộc tính của Movies model. Ví dụ, [Required] và [MinLength] được áp dụng cho các property Title.

Cập nhật cơ sở dữ liệu phim

Bởi vì tôi đã làm thay đổi các model Movie, tôi cần phải cập nhật cơ sở dữ liệu để phù hợp với mô hình mới. Tôi mở một Command Prompt, điều hướng đến thư mục dự án và chạy hai lệnh sau đây:

k ef migration add movieProps
k ef migration apply

Thật không may, lệnh thứ hai ném một ngoại lệ bởi vì tôi đã cố gắng để thêm một cột DateTime vào bảng cơ sở dữ liệu hiện có. Bởi vì bảng đã chứa dữ liệu, tôi có ngoại lệ.

alt

Việc sửa chữa dễ dàng nhất cho vấn đề này là để thả các cơ sở dữ liệu. Tôi tìm thấy MoviesDatabase.mdf và MoviesDatabase_log.ldf trong c:\users\stephen folder và xóa hai tập tin. Khi tôi chạy k ef migration apply một lần nữa, tất cả mọi thứ đã làm việc tốt.

alt

Cập nhật Server Controller

Bước tiếp theo là để cập nhật Web API controller trên máy chủ. MoviesController sửa đổi như sau:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc;
using MovieAngularJSApp.Models;

namespace MovieAngularJSApp.API.Controllers
{
    [Route("api/[controller]")]
    public class MoviesController : Controller
    {
        private readonly MoviesAppContext _dbContext;

        public MoviesController(MoviesAppContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public IEnumerable<Movie> Get()
        {
            return _dbContext.Movies;
        }

        [HttpGet("{id:int}")]
        public IActionResult Get(int id)
        {
            var movie = _dbContext.Movies.FirstOrDefault(m => m.Id == id);
            if (movie == null) {
                return new HttpNotFoundResult();
            } else {
                return new ObjectResult(movie);
            }
        }

        [HttpPost]
        public IActionResult Post([FromBody]Movie movie)
        {
            if (ModelState.IsValid)
            {
                if (movie.Id == 0)
                {
                    _dbContext.Movies.Add(movie);
                    _dbContext.SaveChanges();
                    return new ObjectResult(movie);
                }
                else
                {
                    var original = _dbContext.Movies.FirstOrDefault(m => m.Id == movie.Id);
                    original.Title = movie.Title;
                    original.Director = movie.Director;
                    original.TicketPrice = movie.TicketPrice;
                    original.ReleaseDate = movie.ReleaseDate;
                    _dbContext.SaveChanges();
                    return new ObjectResult(original);
                }
            }
            return new BadRequestObjectResult(ModelState);

        }

        [HttpDelete("{id:int}")]
        public IActionResult Delete(int id)
        {
            var movie = _dbContext.Movies.FirstOrDefault(m => m.Id == id);
            _dbContext.Movies.Remove(movie);
            _dbContext.SaveChanges();
            return new HttpStatusCodeResult(200);
        }

    }
}

Chú ý rằng method Post() (gọi cả hai forms Thêm và chỉnh sửa) kiểm tra ModelState. Nếu ModelState là không hợp lệ thì dòng mã sau đây được thực hiện:

return new BadRequestObjectResult(ModelState);

Kết quả hành động BadRequestObjectResult trả về tất cả các thông báo lỗi xác nhận trong ModelState cho khách hàng.Các BadRequestObjectResult cũng trả về một mã trạng thái 400 Bad Request. Dưới đây là những gì response trông như thế nào trong Chrome Developer Tools:

alt

Được cảnh báo rằng các BadRequestObjectResult không được bao gồm trong bản Beta 2 của ASP.NET 5 framework. Bạn cần phải chuyển sang Beta 3. Tôi mô tả làm thế nào để sử dụng Nightly Builds của ASP.NET 5 trong bài viết blog sau đây:

http://stephenwalther.com/archive/2015/01/15/upgrading-visual-studio-2015-preview-to-asp-net-5mvc-6-rc

cập nhật Client Controllers

Các bước cuối là để cập nhật các bộ điều khiển phía máy khách để hiển thị bất kỳ lỗi xác nhận trở về từ máy chủ.

alt

Các moviesControllers.js nộp dưới đây chứa được cập nhật functions MoviesAddController và MoviesEditController.

(function () {
    'use strict';

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

    /* 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(
                // success
                function () {
                    $location.path('/');
                },
                // error
                function (error) {
                    _showValidationErrors($scope, error);
                }
            );
        };

    }

    /* 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(
                // success
                function () {
                    $location.path('/');
                },
                // error
                function (error) {
                    _showValidationErrors($scope, error);
                }
            );
        };
    }

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

    /* Movies Delete Controller  */
    DatePickerController.$inject = ['$scope'];

    function DatePickerController($scope) {
        $scope.open = function ($event) {
            $event.preventDefault();
            $event.stopPropagation();

            $scope.opened = true;
        };
    }

    /* Utility Functions */

    function _showValidationErrors($scope, error) {
        $scope.validationErrors = [];
        if (error.data && angular.isObject(error.data)) {
            for (var key in error.data) {
                $scope.validationErrors.push(error.data[key][0]);
            }
        } else {
            $scope.validationErrors.push('Could not add movie.');
        };
    }

})();

Chú ý rằng cả hai functions MoviesAddController và MoviesEditController hiện nay bao gồm một error callback.Khi một 400 Bad Request được trả về từ máy chủ, rror callback được gọi và _showValidationErrors được gọi. function _showValidationErrors () thêm các thông báo lỗi từ ModelState vào mảng $ scope.validationErrors. Các nội dung của mảng validationErrors được hiển thị ở _edit.html:

<div class="bg-danger validationErrors" ng-show="validationErrors">
    <ul>
        <li ng-repeat="error in validationErrors">{{error}}</li>
    </ul>
</div>

Tóm lược

Trong bài viết trên blog này, tôi tập trung vào việc giải thích làm thế nào bạn có thể thực hiện validation khi sử dụng ASP.NET 5 và AngularJS. Tôi giải thích làm thế nào bạn có thể sử dụng AngularJS directives- như ng-required directive - để thực hiện client-side validation. Tôi cũng giải thích làm thế nào bạn có thể tận dụng lợi thế của ModelState và BadRequestObjectResult khi validating dữ liệu gửi đến Web API ASP.NET. Trong bài viết trên blog sau, tôi thảo luận về ASP.NET 5 security.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí