Каждый раз с выходом нового фреймворка, хочется попробовать его в деле и написать на нем какое-то приложение. В прошлый раз отлично зашел формат квеста. По этому предлагаю посмотреть что поменялось за почти полтора года и написать еще один квест— и фреймворки посмотрим, и поиграть можно.
Результат:
— сорсы на гитхабе для тех, кому интересно посмотреть на исходники
— линк на квест для тех, кому интересно что получилось или потратить свое время на еще один логический квест.
![](https://habrastorage.org/r/w1560/files/b7f/325/497/b7f32549705e424db4e7f2f9d67cccb5.png)
Под катом описан полный процесс от создания проекта до его развертывания.
Для работы с ASP.NET Core и Angular2 подойдет почти любая IDE или текстовый редактор. Конечно для полноценной отладки ASP.NET нужна Visual Studio, но с выходом новой Core версии для разработки она не обязательна, можно работать с кодом в Sublime\Atom\… и запускать приложение (предварительно установив SDK) из командной строки, с помощью:
Исходный код, инсталлятор, документацию для интерфейса командой строки .NET и SDK можно найти на:
dotnet.github.io
При чем все это теперь доступно не только лишь на Windows но и на Max, Linux, а так же Docker.
Наверное, многие испытают когнитивный диссонанс, увидев
Для создания нового ASP.NET core приложения есть несколько путей:
1) С помощью Visual Studio
2) Из консоли — dotnet new
3) Используя сторонние генераторы.
Не будем углубляться во все проблемы создания и конфигурирования проекта, тем более что нам нужно не пустое приложение, а уже с подключенным Angular2 и, желательно настроенными билд скриптами.
Для этого идеально подходит ASP.NET Core JavaScript Services github.com/aspnet/JavaScriptServices, который позволяет создавать приложения для ASP.NET Core с JavaScript фреймворком на выбор: Angular 2, React, и Knockout.
Для этого нам потребуется: ASP.NET Core, Node.js и генератор yeoman с шаблоном aspnet-spa. Последний можно установить с помощью:
Теперь создание нового проекта сводится к
![](https://habrastorage.org/files/59f/924/2d4/59f9242d4f0c4a34bd2aa4f55ddb0422)
Выбираем Angular2 шаблон, название проекта и генератор сам создаст всю структуру, зависимости и т.д. (это может занять несколько минут).
Сначала может показаться, что этот генератор использует слишком много библиотек, особенно, если вы не любитель последних или же мало работали с фронт-ендом. Но лично мое мнение — что это один из наиболее сбалансированных шаблонов. Особенно хочется выделить следующие свойства:
Конечно же разрабатывать приложения на Angular2 можно и на простом JavaScript, TypeScript дает нам возможности типизации, автодополнения, более привычный (для большинства) синтаксис для ООП. А также команда Angular2 активно использует TypeScript и похоже на то, что TypeScript будет (или уже есть) не официальным языком по умолчанию для Angular2.
Похоже, что webpack быстро становится фаворитом в битве инструментов для фронт-енда. Особенно важно заметить его возможности при постоянном изменении кода. Фактически разработчику можно забыть о том, чтобы постоянно пересобирать проект и обновлять страницу с помощью Горячей Подмены Модулей (Hot module replacement — HMR). После того как попробовали писать фронт-енд на одном мониторе, в то время как на втором он на глазах меняется уже не хочется возвращаться назад, на инструменты без возможности горячей подмены.
Серверный пре-рендер, ленивая загрузка, удобная работа с Development и Production билдами.
Больше можно узнать в блоге одного из разработчиков JavaScriptServices:
blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core
или же увидеть реальный пример в одном из последних публичных стенд-апов команды ASP.NET:
Как уже говорилось раньше, чтобы собрать и запустить проект, можно воспользоваться консольной командой:
Как результат, сайт будет собран и запущен локально:
![](https://habrastorage.org/files/872/d38/b97/872d38b97f8345319ab5c96a64add017)
Или же, сделать тоже самое из Visual Studio.
Если после билда у вас будет уведомление, что не все модули npm установлены,
![](https://habrastorage.org/files/656/4f5/b5e/6564f5b5ee574d7eb18ea78c2ef7b695)
скорее всего это проблема с тем, что один из модулей не поддерживается в Windows, но по скольку зависимость не обязательная, приложение должно работать нормально.
В результате у вас должно запустить рабочее приложение с тремя простыми страничками.
![](https://habrastorage.org/files/e60/de7/dc5/e60de7dc51e34f8695939bb04b9aa974)
Рассмотрим главные отличия, в сравнении с предыдущими версиями фреймворков.
Похоже, структура проекта JavaScriptServices webpack-ориентирована. То есть это npm + webpack, соответственно отсутствуют gulp и bower. На сколько я понимаю bower просто решили не добавлять в стартовый проект, так как нет много зависимостей от JavaScript библиотек, со всем справляется npm. А все функции gulp (или grunt) теперь выполняет webpack.
Что касается файловой структуры, то по части серверных файлов все так же, как и раньше, а вот на front-end есть некоторые отличия. Например, в папке со статическими файлами wwwroot есть только папка dist, куда, фактически, компилируется весь JavaScript.
В сравнении с первой версией Angular бросается в глаза то, что нет никаких представлений и контроллеров — все в компонентах и для каждой html части компоненты есть TypeScript часть.
Webpack конечно очень радует в плане горячей замены модулей, разработка front-end части из-за этого значительно приятнее.
Стоит так же заметить, что ошибки WebPack отлично показываются уже на уровне ASP.NET, очень удобная интеграция. Правда после ошибочного состояния (например, подтягивание не существующей компоненты) горячая замена у меня не заработала, возможно в таких случаях все же надо обновлять страницу вручную.
Так же очень удобно то, что дебажить в браузере можно TypeScript и все это уже встроено по умолчанию
![](https://habrastorage.org/files/aa8/6c8/85b/aa86c885bfa241fd97cf0605727a8f8e)
В первую очередь я переделал страницы с примерами на те, что нужны мне. Это оказалось очень просто, несмотря на то, что мои знания Angular2 и TypeScript близки к нулю. Вообще синтаксис нового Angular кажется очень читабельным, как минимум с первого взгляда. А возможность описывать интерфейсы, классы и типы в TypeScript делают его очень понятным.
Пример простой компоненты:
![](https://habrastorage.org/files/0d7/107/46f/0d710746fe3d4107afe81cc8d08063c7)
Думаю большинство разработчиков, которые видят TypeScript в первый раз сразу поймут что здесь происходит.
Если вы знакомы с предыдущими версиями Entity Framework то с Entity Framework Core проблем возникнуть не должно. В крайнем случае — можно просмотреть документацию на docs.efproject.net/en/latest/intro.html
Сначала ставим nuget пакеты для самого EF, а также для инструментов для работы с базой (версия последнего пока не стабильна):
github.com/gbdrm/HabraQuest/commit/959f4cb6c253dad0cc5e6eb34756ee5cc9c920f9
Дальше, добавляем команды инструментов в project.json, для этого открываем его, находим настройки tools и добавляем
Возможно в вашем шаблоне инструменты уже будут добавлены. Тогда ничего менять не надо.
Добавляем классы модели нашего приложения и DbContext и регистрируем их в Startup.cs.
Ну и конечно ConnectionString в appsettings.json и так же добавляем код для конфигурации в Startup.cs.
github.com/gbdrm/HabraQuest/commit/7411eb4262bdef9f5fb810f6cf7d5a239221c0b5
Следующий шаг, типичная история с миграциями — добавляем начальную миграцию и просим базу обновиться.
Поскольку база еще не существует, после второй команды она должна быть создана. Это можно проверить, посмотрев, какие базы данных существуют если присоединиться к SQL-серверу с помощью Visual или SQL Management студии.
github.com/gbdrm/HabraQuest/commit/09b42dc0a8e45da1f461b38c2b41d79c4fc0b88a
Давайте добавим простой метод в Home контроллер и попробуем минимально использовать соединение с базой. Добавим метод создания нового игрока, который будем возвращать его токен.
github.com/gbdrm/HabraQuest/commit/e67a322184517aa929d1347f6fd638c6290bf9d3
Если теперь мы запустим приложение, можно протестировать этот метод, добавив /home/registernewplayer в конец адреса. В результате мы должны получить токен нашего игрока.
![](https://habrastorage.org/files/c4b/d93/335/c4bd9333569143d19b5bb5a3115f3477)
По скольку мы уже создали примитивный back-end, то можем попробовать его использовать на клиенте. Попробуем сделать следующее:
когда новый пользователь заходит на страницу — проверить, есть ли у него cookie с токеном и, если нету, попросить его из сервера. Чтобы не создавать собственный велосипед, попробуем использовать существующую библиотеку для работы с cookie. Например, ng2-cookies.
Для этого нам надо установить ее и подключить в компоненте home.
github.com/gbdrm/HabraQuest/commit/8c7aadd9607a0c4d0a1039707b08b0c4b720112c
Сложно описать мои следующие несколько часов упорной борьбы со стереотипами JavaScript и первого Angular. Оказалось, что во второй версии фреймворка все абсолютно иначе. Могу лишь сказать, что закончилось это тем, что я решил пока не использовать сервисы (решил сначала лучше разобраться с rxjs observable, который активно используется в Angular 2), обновил пакеты до следующего релиз кандидата (четвертого), добавил новые формы (оказалось, что они были сильно переделаны и этот процесс еще не закончен) и решил максимально упросить код (большинство логики в одну компоненту). Для начала нужно сделать что-то рабочее, а потом будем думать о хороших практиках. Так же добавил несколько заглушек на серверную часть.
github.com/gbdrm/HabraQuest/commit/60c8239a89cae2357d0521973e091781027186ba
Из интересных особенностей — то ли студия то ли решарпер умеют подтягивать правильные импорты для TypeScript, так же в большинстве случаев работает штатная навигация по методах. Из-за такого рода особенностей писать код для клиентской части становиться значительно приятней, нету больше чувства, что вы пишете какой-то скрипт, который может упасть где угодно. Вместо этого основные элементы синтаксиса проверяются автоматически и есть уверенность, что все не поломается где-то посредине. А даже если и поломается, то выполнится еще одна проверка на уровне webpack и в результате на странице будет показа ошибка, даже без перезапуска.
![](https://habrastorage.org/files/35f/4a3/60b/35f4a360bfd74ad0b0561bffbd00bcee)
Правда, иногда, это начинает напрягать, когда после каждого сохранения файла — страница начинает перерисовываться. Но думаю это вопрос привычки.
Весь следующий код не представляет особой важности. В основному это реализация заглушенных моментов. Если вам все же интересно просмотреть — исходники доступны на github.com/gbdrm/HabraQuest
По скольку бек-енд написан на .Net то деплоить наше приложение будем на Azure. Кстати за последние год-полтора тут тоже изменений хоть отбавляй, с первого раза не разберешься что и куда. Для нашего приложения нам достаточно будет создать самый базовый сайт с базой (Web App + SQL).
С деплойментом, как всегда, не очень гладко. Во-первых, он не проходит без ошибок. Сам сайт работает, но в итоге Visual Studio показывает ошибки в каких-то внешних модулях TypeScript. Пока так и не разобрался что это. Во-вторых, бывает, что при открытии сайта вылетает ошибка «TaskCanceledException: A task was canceled». С этим тоже пока не до конца ясно, но похоже проблема где-то на уровне хостинга. И кроме этого, немного поигрался с миграциями.
Хотя, в общем, я этим шагом остался доволен. Очень детальный и понятный лог, все можно сконфигурировать (база, миграции). Первый раз, правда, нужно подождать некоторое время (несколько минут) пока все билдится, копируется. Да и вообще, похоже, что процесс довольно сложный, на сколько я понял сначала все копируется локально, а потом уже перебрасывается на Azure, но это уже детали.
Все получилось так, как и планировалось. Новыми фреймворками лично я доволен, с радостью бы на них перешел. Конечно есть еще не очень стабильные моменты, Angular 2 еще и совсем не выпущен официально. Но выглядит все уже довольно интересно и, главное, работает.
Очень радует развитие инструментов, подходов в разработке которые позволяют делать меньше рутинных задач, типа обновления станицы, контролирования структуры базы данных.
Так же, очень много блогов, вопросов, документации в сети, так что с решением типичных задач уже должно быть все просто.
Все результаты можно найти на:
github.com/gbdrm/HabraQuest
habraquest.azurewebsites.net
Получилось довольно сумбурно, так как много разных тем. Какие из них вам наиболее интересны для описания в будущем?
Результат:
— сорсы на гитхабе для тех, кому интересно посмотреть на исходники
— линк на квест для тех, кому интересно что получилось или потратить свое время на еще один логический квест.
![](https://habrastorage.org/files/b7f/325/497/b7f32549705e424db4e7f2f9d67cccb5.png)
Под катом описан полный процесс от создания проекта до его развертывания.
Предварительные требования
Для работы с ASP.NET Core и Angular2 подойдет почти любая IDE или текстовый редактор. Конечно для полноценной отладки ASP.NET нужна Visual Studio, но с выходом новой Core версии для разработки она не обязательна, можно работать с кодом в Sublime\Atom\… и запускать приложение (предварительно установив SDK) из командной строки, с помощью:
dotnet run
Исходный код, инсталлятор, документацию для интерфейса командой строки .NET и SDK можно найти на:
dotnet.github.io
При чем все это теперь доступно не только лишь на Windows но и на Max, Linux, а так же Docker.
Наверное, многие испытают когнитивный диссонанс, увидев
sudo apt-get install dotnet-dev-1.0.0-preview2-003121
Создаем проект
Для создания нового ASP.NET core приложения есть несколько путей:
1) С помощью Visual Studio
2) Из консоли — dotnet new
3) Используя сторонние генераторы.
Не будем углубляться во все проблемы создания и конфигурирования проекта, тем более что нам нужно не пустое приложение, а уже с подключенным Angular2 и, желательно настроенными билд скриптами.
Для этого идеально подходит ASP.NET Core JavaScript Services github.com/aspnet/JavaScriptServices, который позволяет создавать приложения для ASP.NET Core с JavaScript фреймворком на выбор: Angular 2, React, и Knockout.
Для этого нам потребуется: ASP.NET Core, Node.js и генератор yeoman с шаблоном aspnet-spa. Последний можно установить с помощью:
npm install -g yo generator-aspnetcore-spa
Теперь создание нового проекта сводится к
cd <папка_для_проекта>
yo aspnetcore-spa
Выбираем Angular2 шаблон, название проекта и генератор сам создаст всю структуру, зависимости и т.д. (это может занять несколько минут).
Сначала может показаться, что этот генератор использует слишком много библиотек, особенно, если вы не любитель последних или же мало работали с фронт-ендом. Но лично мое мнение — что это один из наиболее сбалансированных шаблонов. Особенно хочется выделить следующие свойства:
TypeScript
Конечно же разрабатывать приложения на Angular2 можно и на простом JavaScript, TypeScript дает нам возможности типизации, автодополнения, более привычный (для большинства) синтаксис для ООП. А также команда Angular2 активно использует TypeScript и похоже на то, что TypeScript будет (или уже есть) не официальным языком по умолчанию для Angular2.
Webpack
Похоже, что webpack быстро становится фаворитом в битве инструментов для фронт-енда. Особенно важно заметить его возможности при постоянном изменении кода. Фактически разработчику можно забыть о том, чтобы постоянно пересобирать проект и обновлять страницу с помощью Горячей Подмены Модулей (Hot module replacement — HMR). После того как попробовали писать фронт-енд на одном мониторе, в то время как на втором он на глазах меняется уже не хочется возвращаться назад, на инструменты без возможности горячей подмены.
Другие бонусы
Серверный пре-рендер, ленивая загрузка, удобная работа с Development и Production билдами.
Больше можно узнать в блоге одного из разработчиков JavaScriptServices:
blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core
или же увидеть реальный пример в одном из последних публичных стенд-апов команды ASP.NET:
Собираем проект
Как уже говорилось раньше, чтобы собрать и запустить проект, можно воспользоваться консольной командой:
dotnet run
Как результат, сайт будет собран и запущен локально:
Или же, сделать тоже самое из Visual Studio.
Если после билда у вас будет уведомление, что не все модули npm установлены,
скорее всего это проблема с тем, что один из модулей не поддерживается в Windows, но по скольку зависимость не обязательная, приложение должно работать нормально.
В результате у вас должно запустить рабочее приложение с тремя простыми страничками.
Приступаем к работе
Рассмотрим главные отличия, в сравнении с предыдущими версиями фреймворков.
Структура проекта
Похоже, структура проекта JavaScriptServices webpack-ориентирована. То есть это npm + webpack, соответственно отсутствуют gulp и bower. На сколько я понимаю bower просто решили не добавлять в стартовый проект, так как нет много зависимостей от JavaScript библиотек, со всем справляется npm. А все функции gulp (или grunt) теперь выполняет webpack.
Что касается файловой структуры, то по части серверных файлов все так же, как и раньше, а вот на front-end есть некоторые отличия. Например, в папке со статическими файлами wwwroot есть только папка dist, куда, фактически, компилируется весь JavaScript.
В сравнении с первой версией Angular бросается в глаза то, что нет никаких представлений и контроллеров — все в компонентах и для каждой html части компоненты есть TypeScript часть.
Особенности в процессе
Webpack конечно очень радует в плане горячей замены модулей, разработка front-end части из-за этого значительно приятнее.
Стоит так же заметить, что ошибки WebPack отлично показываются уже на уровне ASP.NET, очень удобная интеграция. Правда после ошибочного состояния (например, подтягивание не существующей компоненты) горячая замена у меня не заработала, возможно в таких случаях все же надо обновлять страницу вручную.
Так же очень удобно то, что дебажить в браузере можно TypeScript и все это уже встроено по умолчанию
В первую очередь я переделал страницы с примерами на те, что нужны мне. Это оказалось очень просто, несмотря на то, что мои знания Angular2 и TypeScript близки к нулю. Вообще синтаксис нового Angular кажется очень читабельным, как минимум с первого взгляда. А возможность описывать интерфейсы, классы и типы в TypeScript делают его очень понятным.
Пример простой компоненты:
Думаю большинство разработчиков, которые видят TypeScript в первый раз сразу поймут что здесь происходит.
Entity Framework и база данных
Если вы знакомы с предыдущими версиями Entity Framework то с Entity Framework Core проблем возникнуть не должно. В крайнем случае — можно просмотреть документацию на docs.efproject.net/en/latest/intro.html
Сначала ставим nuget пакеты для самого EF, а также для инструментов для работы с базой (версия последнего пока не стабильна):
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools –Pre
github.com/gbdrm/HabraQuest/commit/959f4cb6c253dad0cc5e6eb34756ee5cc9c920f9
Дальше, добавляем команды инструментов в project.json, для этого открываем его, находим настройки tools и добавляем
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
Возможно в вашем шаблоне инструменты уже будут добавлены. Тогда ничего менять не надо.
Добавляем классы модели нашего приложения и DbContext и регистрируем их в Startup.cs.
Ну и конечно ConnectionString в appsettings.json и так же добавляем код для конфигурации в Startup.cs.
github.com/gbdrm/HabraQuest/commit/7411eb4262bdef9f5fb810f6cf7d5a239221c0b5
Следующий шаг, типичная история с миграциями — добавляем начальную миграцию и просим базу обновиться.
Add-Migration Initial
Update-Database
Поскольку база еще не существует, после второй команды она должна быть создана. Это можно проверить, посмотрев, какие базы данных существуют если присоединиться к SQL-серверу с помощью Visual или SQL Management студии.
github.com/gbdrm/HabraQuest/commit/09b42dc0a8e45da1f461b38c2b41d79c4fc0b88a
Давайте добавим простой метод в Home контроллер и попробуем минимально использовать соединение с базой. Добавим метод создания нового игрока, который будем возвращать его токен.
github.com/gbdrm/HabraQuest/commit/e67a322184517aa929d1347f6fd638c6290bf9d3
Если теперь мы запустим приложение, можно протестировать этот метод, добавив /home/registernewplayer в конец адреса. В результате мы должны получить токен нашего игрока.
Первые шаги в Angular 2
По скольку мы уже создали примитивный back-end, то можем попробовать его использовать на клиенте. Попробуем сделать следующее:
когда новый пользователь заходит на страницу — проверить, есть ли у него cookie с токеном и, если нету, попросить его из сервера. Чтобы не создавать собственный велосипед, попробуем использовать существующую библиотеку для работы с cookie. Например, ng2-cookies.
Для этого нам надо установить ее и подключить в компоненте home.
github.com/gbdrm/HabraQuest/commit/8c7aadd9607a0c4d0a1039707b08b0c4b720112c
Сложно описать мои следующие несколько часов упорной борьбы со стереотипами JavaScript и первого Angular. Оказалось, что во второй версии фреймворка все абсолютно иначе. Могу лишь сказать, что закончилось это тем, что я решил пока не использовать сервисы (решил сначала лучше разобраться с rxjs observable, который активно используется в Angular 2), обновил пакеты до следующего релиз кандидата (четвертого), добавил новые формы (оказалось, что они были сильно переделаны и этот процесс еще не закончен) и решил максимально упросить код (большинство логики в одну компоненту). Для начала нужно сделать что-то рабочее, а потом будем думать о хороших практиках. Так же добавил несколько заглушек на серверную часть.
github.com/gbdrm/HabraQuest/commit/60c8239a89cae2357d0521973e091781027186ba
Из интересных особенностей — то ли студия то ли решарпер умеют подтягивать правильные импорты для TypeScript, так же в большинстве случаев работает штатная навигация по методах. Из-за такого рода особенностей писать код для клиентской части становиться значительно приятней, нету больше чувства, что вы пишете какой-то скрипт, который может упасть где угодно. Вместо этого основные элементы синтаксиса проверяются автоматически и есть уверенность, что все не поломается где-то посредине. А даже если и поломается, то выполнится еще одна проверка на уровне webpack и в результате на странице будет показа ошибка, даже без перезапуска.
Правда, иногда, это начинает напрягать, когда после каждого сохранения файла — страница начинает перерисовываться. Но думаю это вопрос привычки.
Весь следующий код не представляет особой важности. В основному это реализация заглушенных моментов. Если вам все же интересно просмотреть — исходники доступны на github.com/gbdrm/HabraQuest
Развертывание
По скольку бек-енд написан на .Net то деплоить наше приложение будем на Azure. Кстати за последние год-полтора тут тоже изменений хоть отбавляй, с первого раза не разберешься что и куда. Для нашего приложения нам достаточно будет создать самый базовый сайт с базой (Web App + SQL).
С деплойментом, как всегда, не очень гладко. Во-первых, он не проходит без ошибок. Сам сайт работает, но в итоге Visual Studio показывает ошибки в каких-то внешних модулях TypeScript. Пока так и не разобрался что это. Во-вторых, бывает, что при открытии сайта вылетает ошибка «TaskCanceledException: A task was canceled». С этим тоже пока не до конца ясно, но похоже проблема где-то на уровне хостинга. И кроме этого, немного поигрался с миграциями.
Хотя, в общем, я этим шагом остался доволен. Очень детальный и понятный лог, все можно сконфигурировать (база, миграции). Первый раз, правда, нужно подождать некоторое время (несколько минут) пока все билдится, копируется. Да и вообще, похоже, что процесс довольно сложный, на сколько я понял сначала все копируется локально, а потом уже перебрасывается на Azure, но это уже детали.
Результат, выводы
Все получилось так, как и планировалось. Новыми фреймворками лично я доволен, с радостью бы на них перешел. Конечно есть еще не очень стабильные моменты, Angular 2 еще и совсем не выпущен официально. Но выглядит все уже довольно интересно и, главное, работает.
Очень радует развитие инструментов, подходов в разработке которые позволяют делать меньше рутинных задач, типа обновления станицы, контролирования структуры базы данных.
Так же, очень много блогов, вопросов, документации в сети, так что с решением типичных задач уже должно быть все просто.
Все результаты можно найти на:
github.com/gbdrm/HabraQuest
habraquest.azurewebsites.net
Получилось довольно сумбурно, так как много разных тем. Какие из них вам наиболее интересны для описания в будущем?
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какие из описанных тем наиболее интересны?
63.29% Angular 2250
73.16% ASP.NET Core289
38.48% EF Core152
45.06% TypeScript178
9.11% yeoman, JavaScriptServices36
29.87% WebPack, инструменты сборки118
40.51% Кросс-платформенность .NET160
10.63% Azure42
8.1% Квест32
Проголосовали 395 пользователей. Воздержался 51 пользователь.