Wednesday, December 28, 2016

Цели и планы на 2017 год

Пришло самое время подвести итоги 2016 года и проверить, сумел ли я достичь тех целей, которые ставил себе в 2015 году.
Итак, первый вопрос – по поводу самого блога. Вероятнее всего, буду писать статьи только на английском. Наверное, придётся пойти на такой шаг, чтобы улучшить практический письменный язык, заодно, надеюсь, этот шаг будет больше привлекать иностранную аудиторию. В целом за этот год я написал 12 статей. Это в три раза меньше, чем количество статей в 2015 году. Создание нормальной статьи для блога, начиная от идеи, написания рабочего примера и оформления этого в отдельную статью (а потом эту смесь текста и кода отдать моей красавице жене на вычитку и исправление ошибок) – это очень громоздкая работа, поэтому я решил больше времени уделить самообучению в сфере web разработки и в английском. 
Что из этого вышло
Пройдено 30 курсов по разным технологиям и языкам, начиная с Angular 2, React, TypeScript, JabaScript и заканчивая деплойментом с Docker и Octopus.
Также каждый день уделял по 3-4 часа на изучене английского языка с помощью Lingualeo и Memrise. Весь пройдённый материал закреплял на работе на speaking clubs с прекрасными преподавателями, которым выражаю огромную благодарность за потраченное время. 
Я также продолжил дальше заниматься менторством. В этом году я обучил уже третью группу студентов таким темам, как паттерны проектирования, EntityFramework Code First и Repository & Unit Of Work patterns. Приятно наблюдать, что переданные знания успешно внедряются в рабочем проекте.
Ну и, пожалуй, самый авантюрный шаг, на который я решился в этом году, – это изучение французского. Я рассматриваю изучение нового языка как один из способов лучше понять английский, а также пробовать заставить себя мыслить на другом языке.
Вот план, поставленные в блоге на 2016 год:

  • Разобраться с OAuth и OWIN Forms аутентификацией.
  • Выучить глубоко JavaScript и TypeScript
  • ASP.NET vNext with Angular 2.0
  • Попробовать пройти сертификацию Developing ASP.NET MVC 4 Web Applications или даже всю цепочку MCSD: Web Applications.
  • C# 7
  • EntityFramework 7
  • DDD (Experimenting with CQRS and event driven design)
  • Mentoring
  • Read 20 books in English

Теперь, пожалуй, пройдусь по этим целям для самопроверки.
Разобраться с OAuth и OWIN Forms аутентификацией – с этой целью я успешно справился. Во-первых, у меня была возможность применить это все на практике, пускай и на пилотных проектах. Во-вторых, пройденный курс на pluralsight помог закрепить этот материал.
Выучить глубоко JavaScript и TypeScriptне знаю, насколько глубоко получилось выучить JavaScript, но как минимум писать сейчас на нем весь клиентский код для клиента не составляет никакого труда. Думаю, могу поставить эту цель как выполненную и продолжать улучшать знания в этой среде.  
ASP.NET vNext with Angular 2.0 – цель была ознакомиться с данными технологиями и фреймворком. С момента написания прошлой статьи ASP.NET vNext сменил свое название на ASP.NET Core, но это не помешало мне попробовать его на пилотном проекте и на своих проектах для блога. Angular 2 я по привычке использовал на своем классическом примере со студентами, но не уверен, что у меня будет время адаптировать этот проект для блога. В этом вопросе, наверное, я даже перевыполнил план, так как я успел много времени потратить на React, и все тот же проект со студентами переписать на него. Поэтому ставлю цель как выполненную.
Попробовать пройти сертификацию Developing ASP.NET MVC 4 Web Applications или даже всю цепочку MCSD: Web Applications. с этой целью я не справился. И, наверное, единственная для этого причина состоит в том, что я не захотел тратить 80$ за сертификат, от которого не вижу никакой пользы. Время на изучение ASP.NET MVC я все равно потратил, и думаю, что полученных знаний хватило бы для прохождения сертификации. Но цель все же не выполнена.
C# 7 – разобраться с C# 7 была самая легкая цель, которую я себе поставил. Я обожаю C# как язык программирования, поэтому эта цель была, наверное, самой приятной. Заодно сумел попробовать в работе VS 2017 и IDE от JetBrains под названием Rider. Цель как выполнена.
EntityFramework 7 – фреймворк, который с момента написания поменял свое название на EntityFramework Core. Успел попробовать его на практике, также завести багу разработчикам, доклад о которой они успешно проигнорировали и которая существует до сих пор. Цель выполнена, но вот фреймворк еще сырой и использовать его в production не рекомендую.
DDD (Experimenting with CQRS and event driven design) неоднозначная цель. Был на ивентах, посвященных CQRD,  правда, единственное, что я делал на них, – это пытался показать, что CRUD модель иногда очень даже неплохо заменяет CQRS. На практике я так и не попробовал использовать Event Sourcing и не уверен, что у меня будет когда-то эта возможность. Заказчики очень боятся внедрять CQRS в свои системы, а я пока не вижу в нем особого преимущества. Надеюсь, мое виденье этого процесса изменится в будущем. Пока поставлю цель как частично завершенную.
Mentoring – в этом году у меня была возможность побывать ментором для двух групп студентов и прочитать по три лекции для каждой группы. План по менторству можно считать выполненным.
Read 20 books in English – последняя цель для меня была просто удовольствием. Из них технических книг было всего шесть и четырнадцать  в основном приключения и фантастика. Например, все коллекция книг о Властелине Колец и о Гарри Поттере. Так что цель можно считать завершенной.
Цели на 2017 год
Следующий год хочу посвятить на развитие навыков, необходимых для успешного созданий и проектирования программ, т.е. навыков, которые нужны для того, чтобы приблизиться к званию software architect.
  • Microsoft application architecture guide
  • AOP in .NET: Practical Aspect-Oriented Programming
  • DDD concepts
  • Enterprise integration patterns - designing, building, and deploying messaging solutions
  • UML Concepts
  • Basic layered architecture principles
  • The Synthesis of Solutions
  • Read 20 books in English
  • Выучить французский до уровня A2
Думаю, этих планов мне должно хватить на целый 2017 год. Бывает сложно мотивировать себя что-либо делать, начать это делать или не бросить запланированное на полпути. Присоединяйтесь к блогу, будем делать это вместе в виде соревнования. Желаю всем счастливого Нового года. Пусть он будет намного лучше, чем был уходящий, и чтобы он все ваши добрые пожелания и светлые мечты сбылись в этом году. Отдельное спасибо хочу сказать своей любимой жене и дочери, которые меня постоянно мотивируют изменяться в лучшую сторону. Я знаю, солнышко, что ты будешь читать эту статью перед публикацией. Спасибо тебе большое за то, что ты у меня есть и за то, что помогаешь мне во всех моих начинаниях. Я бы не хотел и не смог этого ничего сделать без тебя. 

Sunday, June 5, 2016

Обзор книги Мартина Фаулера "Patterns of Enterprise Application Architecture"

Наконец-то я смог себя пересилить и написать статью, над которой давно думал. (Лень, подготовка к новой для себя области и постоянное обучение не позволяли этого сделать.) Поделюсь с Вами мыслями по поводу книги Мартина Фаулера “Patterns of Enterprise Application Architecture”. На русском эта книга называется “Архитектура корпоративных программных приложений”, если у Вас возникнет желание ее прочитать. 
Как и во всех своих предыдущих рецензиях, выделю сначала плюсы и минусы книги, а затем перейду к более детальной субъективной критике.
Плюсы:
  • Обилие примеров и описание паттернов.
  • Перекрёстные ссылки с указанием страницы с описанием паттерна.
  • Язык примеров C# и Java что делает их многим доступным для понимания.

Минусы:
  • Некоторые паттерны описаны скудно в плане реализации.
  • Примеры сильно устарели.
  • Примеры, которые написаны на C#, полностью повторяют Java стиль
А теперь обо всем этом более подробно. Хотелось бы сказать, что книга стоит того, чтобы ее прочитать, хотя бы для того, чтобы структурировать свои знания. Я считаю эту книгу классикой, которую можно поставить на уровне “Банды четырех” (GOF) с их книгой “Design Patterns”, потому что эта книга дает базу и понятия, которые присущие в проектировании enterprise приложений. Как минимум, термины с этой книги вы можете использовать для общения с опытными разработчиками, и они будут вас понимать.
Фаулер, например, неплохо раскрывает такие паттерны, как Repository, Mapper, Active Record, Unit of Work и другие. К сожалению, в мире разработки архитектурных решений не так уж много книг, которые достойно описывают данную область без лишней “воды”. Когда читаешь эту книгу, как минимум радует, что когда встречается название паттерна, всегда есть ссылка на источник, где о нем можно почитать. Мне это очень помогало при сравнении и анализе глав книги. Ну и так как мой язык разработки – C#, а Java очень подобный к нему, эту книгу мне было очень легко воспринимать. Если вы не знакомы с этими языками, примеры понять будет посложнее.
А теперь пройдемся по негативным моментам книги. Я уважаю Мартина Фаулера как разработчика и архитектора, а также как автора книг, но хотелось бы обратить внимание на нюансы, которые мне не понравились в книге. И первый такой момент заключается в том, что в книге встречаются часто разные названия одного и того же паттерна. Например, коллизия с названием паттерна Value Object и Data Transfer Object.
Совпадение названий
На протяжении довольно долгого времени данное типовое решение носило название объекта-значения. К сожалению, несколько лет назад в сообществе J2EE [3] термин объект-значение (value object) стал применяться для обозначения типового решения объект переноса данных (Data Transfer Object, 419), что вызвало настоящую бурю среди разработчиков типовых решений. Подобная путаница с названиями — далеко не редкость в нашей сфере деятельности. Не так давно авторы книги [3] решили отказаться от использования термина "объект-значение" и заменили его термином объект переноса (transfer object). Я продолжаю использовать термин "объект-значение" в том смысле, в котором он применяется в контексте данной книги. По крайней мере, так я не вступаю в противоречие со своими предыдущими работами!
Сама идея того, что автор всюду использует Value Object, вроде, сама по себе и ничего, но когда ты смотришь примерь пример и понимаешь, что это классический в .NET DTO, в голове возникает много вопросов.
Взглянем на примеры книги. К сожалению, очень много из них устарели много лет назад. Например, глава Presentation Patterns пестрит изобилием кода для JSP, а код примеров на C# лучше не читать, чтобы не поплохело. Другой пример – это глава о паттернах Object-Relational Structural Patten, в которых все примеры приводились на ручном парсинге и выборке данных. Может быть, это и полезно для базы, но когда есть такие ORM, как EntityFramework или NHibernate, думаешь, зачем сколько кода постоянно дублировать и переписывать. Я не совсем понимал, почему так сложно приводить в примеры готовую реализацию. Или, может, в Java нет готовой реализации некоторых паттернов. Целая глава, которая посвящена Data Source Architectural Patterns, полностью реализована в .Net Framework. Тот же Table Data Gateway – это типичная реализация DataSet в ADO .NET, паттерн Row Data Gateway – типичная реализация Row в том же ADO .NET, то же самое – касательно Active Record и Data Mapper. Некоторые паттерны эволюционировали, и примеры с книги не показывают той элегантности, с которой эти паттерны можно использовать. На момент выхода книги, когда я прочитал несколько отзывов о ней, она уже была с неактуальной информацией.  
Так як книга 2002 року выпуска, не понимаю, почему в ней не раскрыты вопросы тестирования и Dependency Inversion, паттерны безопасности, почему нет банального Service Locator. Большинство примеров в книге написаны так, что они не поддаются тестированию. Как говорят программисты, “на коленке написан код. То есть, о части материала с книги можно много дискутировать с коллегами на работе или просто с друзьями по интересам.
Мой самый нелюбимый момент в книге  примеры на двух языках. Проблема не в самих примерах, они в большинстве неплохие, но некоторые очень маленькие и тривиальные, что недостаточно для раскрытия сути. Плохо, что стиль Java примеров, переходит на стиль написания C#. То есть, все примеры на C# написаны в стиле именования Java. Но это мое субъективное мнение.
Итоги
Несмотря на свой возраст, эта книга является незаменимой основой, и  любому разработчику выше среднего уровня было бы полезно ее прочитать. Реальное понимание идеи книги приходит, если вы начинаете понимать, как эти идеи развивались с течением времени. Мартин Фаулер пишет в основном по сути, и в этой книге немного “воды”, которую часто любят лить архитекторы, когда начинают мыслить на страницах книги о высоком, не передавая конкретной сути. Я отнес бы эту книгу в категорию “should read”, то есть книгу, которую следовало бы прочесть. Моя оценка данной книги – 4 балла из 5 возможных.

Sunday, March 20, 2016

Client and server AngularJS validation

Сегодня мы с вами поговорим о том, как сделать валидацию для наших приложений, используя AngularJS. Об использовании валидации на стороне клиента вы можете посмотреть здесь в документации. Мы с вами рассмотрим, как можно использовать одновременно клиентскую и серверную валидацию. Начнем, пожалуй, с первой части и посмотрим, как реализовать валидацию на стороне клиента.
AngularJS Client Side Validation
Для этого у меня было создано приложение, в котором я реализовал добавление студентов и отображение их. Для того чтобы посмотреть, как это работает, взглянем на модель данных, которую мы будем проверять.
public class Student
{
    [Key]
    public int Id { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
    [StringLength(50, MinimumLength = 1)]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [Display(Name = "First Name")]
    [StringLength(50, MinimumLength = 2, ErrorMessage = "First name must be between 2 and 50 characters.")]
    public string FirstName { get; set; }

    public string FullName
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }
}
Это все взаимодействует с нашим контроллером через WebAPI контроллер.
public class StudentsController : ApiController
{
    private StudentContext db = new StudentContext();

    // GET: api/Students
    public IQueryable<Student> GetStudents()
    {
        return db.Students;
    }

    // GET: api/Students/5
    [ResponseType(typeof(Student))]
    public IHttpActionResult GetStudent(int id)
    {
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return NotFound();
        }

        return Ok(student);
    }

    // PUT: api/Students/5
    [ResponseType(typeof(void))]
    public IHttpActionResult PutStudent(int id, Student student)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (id != student.Id)
        {
            return BadRequest();
        }

        db.Entry(student).State = EntityState.Modified;

        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!StudentExists(id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return StatusCode(HttpStatusCode.NoContent);
    }

    // POST: api/Students
    [ResponseType(typeof(Student))]
    public IHttpActionResult PostStudent(Student student)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.Students.Add(student);
        db.SaveChanges();

        return CreatedAtRoute("DefaultApi", new { id = student.Id }, student);
    }

    // DELETE: api/Students/5
    [ResponseType(typeof(Student))]
    public IHttpActionResult DeleteStudent(int id)
    {
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return NotFound();
        }

        db.Students.Remove(student);
        db.SaveChanges();

        return Ok(student);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

    private bool StudentExists(int id)
    {
        return db.Students.Count(e => e.Id == id) > 0;
    }
}
Загружаем мы студентов для редактирования с помощью контроллера editStudentCtr.
(function () {
    'use strict';

    angular
        .module('app')
        .controller('editStudentCtr', editStudentCtr);

    editStudentCtr.$inject = ['$scope', '$http', '$routeParams', '$location'];

    function editStudentCtr($scope, $http, $routeParams, $location) {
        console.log("editStudentCtr");
        var id = $routeParams.studentId;

        var url = 'api/Students/' + id;
        $http.get(url)
            .success(function (data) {
                $scope.student = data;
            })
            .error(function (error) {
                console.log(error.message);
            });

        $scope.save = function () {
            $http.put('api/Students/' + id, $scope.student).success(function () {
                $location.path("");
            }).error(function (error) {
                console.log(error.Message);
            });
        };

        $scope.cancel = function () {
            $location.path("");
        };
    }
})();
Теперь рассмотрим, как это работало раньше до валидации.
<div id="createNewStudent">
    <div class="row">
        <form class="form-horizontal" role="form" name="studentForm">
            <input type="hidden" id="studentId" ng-model="student.Id">
            <div class="form-group">
                <label class="control-label col-md-2" for="firstName">First Name:</label>
                <div class="col-md-10">
                    <input id="firstName" class="form-control" ng-model="student.FirstName" />
                </div>
            </div>

            <div class="form-group">
                <label class="control-label col-md-2" for="lastName">Last Name:</label>
                <div class="col-md-10">
                    <input id="lastName" class="form-control" ng-model="student.LastName" />
                </div>
            </div>

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <button class="btn btn-primary" id="saveStudent" ng-click="save()">Save</button>
                    <button class="btn btn-primary" id="cancelStudent" ng-click="cancel()">Cancel</button>
                </div>
            </div>
        </form>
    </div>
</div>
Если запустить это пример, то можно увидеть, что когда мы не вводим никаких данных, то есть поля для ввода у нас пустые, кнопка сохранения все еще продолжает работать.
Это очень плохо. У нас сработает только проверка на сервере, если она там присутствует. А что же делать, если ее забыли там прописать? По сути, мы с вами разрешаем сами сохранять невалидную модель. Поскольку это нам ни к чему, пора приступить к добавлению валидации на клиентскую сторону. Для того чтобы это все заработало, нам нужно установить свойство name для наших input контролов, для того чтобы с ними нормально мог работать AngularJS, и добавить директиву required, и самое главное – нужно установить для нашей формы директиву novalidate. Novalidate директива необходима, чтобы не разрешать делать дефолтную валидацию, которая есть в браузере (будет красная рамка вокруг контролов, которую проставляет вам конкретный браузер). 
<div id="createNewStudent">
    <div class="row">
        <form class="form-horizontal" role="form" name="studentForm" novalidate>
            <input type="hidden" id="studentId" ng-model="student.Id">
            <div class="form-group">
                <label class="control-label col-md-2" for="firstName">First Name:</label>
                <div class="col-md-10">
                    <input id="firstName" name="firstName" class="form-control" ng-model="student.FirstName" required/>
                </div>
            </div>

            <div class="form-group">
                <label class="control-label col-md-2" for="lastName">Last Name:</label>
                <div class="col-md-10">
                    <input id="lastName" name="lastName" class="form-control" ng-model="student.LastName" required/>
                </div>
            </div>

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <button class="btn btn-primary" id="saveStudent" ng-click="save()">Save</button>
                    <button class="btn btn-primary" id="cancelStudent" ng-click="cancel()">Cancel</button>
                </div>
            </div>
        </form>
    </div>
</div>
Ниже подчеркнуты изменения, которые произошли в нашем примере.
К сожалению, после того как мы внесем эти изменения, кнопка Save будет все равно доступна для сохранения. Для того чтобы она перестала быть доступна, нам нужно указать для нее через директиву ng-disable невозможность ее нажать, если на форме присутствуют невалидные данные.
<button class="btn btn-primary" id="saveStudent" ng-click="save()" ng-disabled="studentForm.$invalid">Save</button>
Теперь, как минимум, мы будем видеть, есть ли у нас ошибки в модели. Мы не можем ничего сохранить в базу.
Но этого недостаточно. Нет подсветки полей, в которых нужны данные, нет предупреждений о том, что данные не введены. Поэтому начнем, пожалуй, с добавления текста о том, что наши поля обязательные для заполнения. Для этого под нашим полем ввода для FirstName добавим следующий блок span:
<span class="help-block" ng-show="studentForm.firstName.$dirty && studentForm.firstName.$error.required">
    The FirstName field is required.
</span>
Здесь мы с помощью директивы ng-show, если у нас срабатывает условие studentForm.firstName.$dirty && studentForm.firstName.$error.required показываем текст The FirstName field is required. Ниже приведен список того, как это все работает.
  • $pristine: It will be TRUE, if the user has not interacted with the form yet
  • $dirty: It will be TRUE, if the user has already interacted with the form.
  • $valid: It will be TRUE, if all containing form and controls are valid
  • $invalid: It will be TRUE, if at least one containing form and control is invalid.
  • $error: Is an object hash, containing references to all invalid controls or forms, where:
    • keys are validation tokens (error names)
    • values are arrays of controls or forms that are invalid with given error.
Если кратко: $pristine – если мы не трогали форму или контрол, $dirty – если взаимодействовали с контролом, $valid – если контрол или форма валидны, $invalid – соответственно, если форма или контрол невалидные, и $error – если содержатся какие-то ошибки. Ниже приведены встроенные токены проверки, которые могут помочь в проверке формы.
  • email
  • max
  • maxlength
  • min
  • minlength
  • number
  • pattern
  • required
  • url
AngularJS также предоставляет соответственные директивы для работы с CSS стилями.
  • ng-pristine
  • ng-dirty
  • ng-valid
  • ng-invalid
Для примера ниже приведен также список атрибутов, которые можно применить на input поле.
  • ng-required
  • ng-minlength
  • ng-maxlength
  • ng-min
  • ng-max
  • ng-pattern
Часть из них мы сегодня и будем использовать.
Думаю, после расписания такого количества теории понятно, как оно работает. Осталось добавить такой же блок span для поля LastName.
<span class="help-block" ng-show="studentForm.lastName.$dirty && studentForm.lastName.$error.required">
    The LastName field is required.
</span>
Полный измененный код приведен ниже:
<div class="form-group">
    <label class="control-label col-md-2" for="firstName">First Name:</label>
    <div class="col-md-10">
        <input id="firstName" name="firstName" class="form-control" ng-model="student.FirstName" required/>
        <span class="help-block" ng-show="studentForm.firstName.$dirty && studentForm.firstName.$error.required">
            The FirstName field is required.
        </span>
    </div>
</div>

<div class="form-group">
    <label class="control-label col-md-2" for="lastName">Last Name:</label>
    <div class="col-md-10">
        <input id="lastName" name="lastName" class="form-control" ng-model="student.LastName" required/>
        <span class="help-block" ng-show="studentForm.lastName.$dirty && studentForm.lastName.$error.required">
            The LastName field is required.
        </span>
    </div>
</div>
Если запустить проект, то можно увидеть, что нужные данные у нас показываются, если мы ничего не вводим и не трогаем поля.
Как-то все равно оно выглядит не очень. Для меня привычно, что рамочка вокруг поля с ошибкой должна быть подсвечена. Это можно сделать, воспользовавшись комбинацией bootstrap + angularjs validation. Для этого с помощью ng-class будем проставлять для атрибута has-error. Ниже приведена реализация всего этого.
<div class="col-md-10" ng-class="{'has-error': studentForm.firstName.$dirty && studentForm.firstName.$invalid}">
    <input id="firstName" name="firstName" class="form-control" ng-model="student.FirstName" required/>
    <span class="help-block" ng-show="studentForm.firstName.$dirty && studentForm.firstName.$error.required">
        The FirstName field is required.
    </span>
</div>
Аналогичное действие проводим для следующего div блока, в котором прописано поле input для изменения LastName. Посмотрим, как будет выглядеть измененная форма для добавления и редактирования студентов.
Как видите, это уже больше похоже на правду и подобно тому, что вы видели при валидации, например, на ASP.NET MVC. Предлагаю закончить до конца наш пример и добавить проверку на максимальное и минимальное количество символов для наших полей, чтобы валидация соответствовала действительности. Для этого нам нужно будет воспользоваться директивами ng-minlength и ng-max-length. Ниже приведен обновленный пример с добавлением этих директив и отображение нужного текста, если данные по количеству символов невалидные.
<div class="form-group">
    <label class="control-label col-md-2" for="firstName">First Name:</label>
    <div class="col-md-10" ng-class="{'has-error': studentForm.firstName.$dirty && studentForm.firstName.$invalid}">
        <input id="firstName" name="firstName" class="form-control" ng-model="student.FirstName" required ng-minlength="1" ng-maxlength="50"/>
        <span class="help-block" ng-show="studentForm.firstName.$dirty && studentForm.firstName.$error.required">
            The FirstName field is required.
        </span>
        <span class="help-block" ng-show="studentForm.firstName.$error.minlength">
            The LastName field must be more than 1 symbols
        </span>
        <span class="help-block" ng-show="studentForm.firstName.$error.maxlength">
            The LastName field must be less than 50 symbols
        </span>
    </div>
</div>

<div class="form-group">
    <label class="control-label col-md-2" for="lastName">Last Name:</label>
    <div class="col-md-10" ng-class="{'has-error': studentForm.lastName.$dirty && studentForm.lastName.$invalid}">
        <input id="lastName" name="lastName" class="form-control" ng-model="student.LastName" required ng-minlength="2" ng-maxlength="50" />
        <span class="help-block" ng-show="studentForm.lastName.$dirty && studentForm.lastName.$error.required">
            The LastName field is required.
        </span>
        <span class="help-block" ng-show="studentForm.lastName.$error.minlength">
            The LastName field must be more than 2 symbols
        </span>
        <span class="help-block" ng-show="studentForm.lastName.$error.maxlength">
            The LastName field must be less than 50 symbols
        </span>
    </div>
</div>
Как видите, кода стало намного больше, так как добавились две дополнительные проверки в каждый div блок. Что из этого получилось, можно посмотреть ниже.
Как видите, у нас появилась проверка уже и на длину текста с помощью AngularJS валидации. Вся проверка у нас была только на стороне клиента. Но как вы понимаете, на сервере могут добавлять разные уровни проверки, или мы что-то забыли проверить, так что настало самое время реализовать еще серверную проверку. Тем более что у нас полностью не покрыта реализация LastName.
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
[StringLength(50, MinimumLength = 1)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
Мы ее можем сделать на клиенте через ng-pattern, который позволяет реализовать регулярки, но я предпочту это сделать на стороне сервера. Тем более, думаю, это может вам когда-то пригодиться.
AngularJS with ASP.NET Server side validation
Давайте посмотрим, как происходит добавление нового студента с помощью AngularJS.
$scope.save = function () {
    $http.post('api/Students', $scope.student).success(function () {
        $location.path("");
    }).error(function (error) {
        console.log(error.Message);
    });
};
Результат мы просто залогировали. И если у нас сервер вернет ошибку, нам нужно подсветить нужный участок кода, если у нас ошибка с нашей моделью данных. Посмотрите, например, на серверный метод post, чтобы понять, как это работает.
// POST: api/Students
[ResponseType(typeof(Student))]
public IHttpActionResult PostStudent(Student student)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Students.Add(student);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = student.Id }, student);
}
То есть, если модель невалидная, мы просто проглатываем ошибку. Сейчас мы рассмотрим, что нужно делать и как с этим бороться.
На помощь нам приходят директивы с AngularJS. Для этого добавляю в свою структуру новую папку directives и создаю файл server-validate.js. Ниже приведена реализация этого файла.
"use strict";

angular.module('app').directive('serverError', function () {
    return {
        restrict: 'A',
        require: '?ngModel',
        link: function (scope, element, attrs, ctrl) {
            return element.on('change keyup', function () {
                return scope.$apply(function () {
                    return ctrl.$setValidity('server', true);
                });
            });
        }
    };
});
Здесь мы добавили, что для каждого элемента мы меняем состояние ‘state’ и уведомляем об этом форму.  Теперь мы в наши поля для редактирования добавим нашу директиву. На рисунке ниже показано, как нужно это сделать.
Теперь я добавлю еще один класс, который будет перегонять то, что мне возвращает сервер, в ту нотацию, которую понимает клиент. Для этого я создам новую папку в helpers и добавлю новый файл, который назову error_helpers.js.
"use strict";

var errorHelper = new ErrorHelper();

function ErrorHelper() {
    function lowerizeFirstLetter(string) {
        return string.charAt(0).toLowerCase() + string.slice(1);
    };

    this.validateForms = function(form, scopeErrors, modelState) {
        angular.forEach(modelState, function(error, field) {
            var existField = lowerizeFirstLetter(field.split('.')[1]);
            if (!form.hasOwnProperty(existField)) {
                console.log(existField + ' not found for validation');
            } else {
                form[existField].$setValidity('server', false);
                scopeErrors[existField] = error.join(', ');
            }
        });
    };
}
С помощью класса ErrorHelper я добавил метод validateForms, который принимает форму, массив с ошибками и modelState. Затем просто бегает по ошибкам и для тех полей, которым с сервера вернулось значение об ошибке, проставляем поле ‘server’ в false. Если поле не найдено на форме, то я это просто логирую. На самом деле, вам лучше бросать ошибку в этом случае. Я делаю это с помощью toast.
Здесь есть еще более плохая вещь, которую постарайтесь не делать ни в коем случае. Вот она.
var errorHelper = new ErrorHelper();
Я слегка ленив, поэтому создал глобальную переменную, чтобы иметь доступ со всех своих контроллеров. Хотя можно было все это де сделать через injection в AngularJS. Поэтому лучше делать, как правильно.
Затем нужно поменять логику сохранения в контроллере. Для этого изменим метод save(), как показано ниже.
$scope.errors = {}; //clean up previous server errors

$scope.save = function () {
    $http.post('api/Students', $scope.student).success(function () {
        $location.path("");
    }).error(function (error) {
        $scope.errors = {};
        errorHelper.validateForms($scope.studentForm, $scope.errors, error.modelState);
        console.log(error.Message);
    });
};
Мы добавили объект errors, в котором будем хранить список ошибок, которые нам вернет наш сервер. Если я запущу пример и введу в поле LastName текст “dddd”, который не подходит для регулярного выражения, которое проверяется на сервере, я получу соответствующую ошибку:
Но к сожалению, на форме ничего не изменится, и о том, что у меня случилась ошибка я не узнаю.
Итак, поле подсветилось, а что за ошибка, я так и не узнал. Для того чтобы показать соответствующий текст, нам нужно добавить еще один span. Ниже приведен пример его реализации для поля LastName.
<span class="help-block" ng-show="studentForm.lastName.$error.server">{{errors.lastName}}</span>
Полный код приведен ниже.
<div class="form-group">
    <label class="control-label col-md-2" for="lastName">Last Name:</label>
    <div class="col-md-10" ng-class="{'has-error': studentForm.lastName.$dirty && studentForm.lastName.$invalid}">
        <input id="lastName" name="lastName" class="form-control" ng-model="student.LastName" required server-error ng-minlength="2" ng-maxlength="50" />
        <span class="help-block" ng-show="studentForm.lastName.$dirty && studentForm.lastName.$error.required">
            The LastName field is required.
        </span>
        <span class="help-block" ng-show="studentForm.lastName.$error.minlength">
            The LastName field must be more than 2 symbols
        </span>
        <span class="help-block" ng-show="studentForm.lastName.$error.maxlength">
            The LastName field must be less than 50 symbols
        </span>
        <span class="help-block" ng-show="studentForm.lastName.$error.server">{{errors.lastName}}</span>
    </div>
</div>
Запускаем опять наш пример и проверяем, что серверная валидация работает.
Итоги
Сегодня мы рассмотрели, как реализовать валидацию с помощью AngularJS как на стороне сервера, так и на стороне клиента. Ниже вы найдете исходники, в которых сами все сможете запустить и посмотреть. Ничего сверхсложного в этом всем нет. Нужно только знать, как работают AngularJS директивы и то, как нужно делать валидацию. Надеюсь, что эта статья поможет вам сэкономить немного времени, если вам предстанет задача реализовать свою валидацию в AngularJS.

Исходники: AngularJS validation sample