Пишем с нуля квест на ASP.NET 5 (vNext) и Angular.js
С выходом новой версии ASP.NET хочется попробовать, какая же она на практике. А для того, чтобы не писать еще один чатик\соц. сеть\блог…, для пилотного проекта выберем логический квест — и фреймворк посмотрим, и поиграть можно.Результат: — сорсы на гитхабе для тех, кому интересно поиграться с новым ASP.NET— линк на квест для тех, кому интересно что получилось или потратить свое время на еще один логический квест.Предварительные требованияДля работы с новой версией ASP.NET нужна Visual Studio 2015 — на данный момент доступна версия Preview, скачать можно здесь: www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs.aspxНикаких проблем с инсталляцией параллельно с другими версиями студии быть не должно.Фактически, никакого другого софта кроме студии для базовой разработки на .NET стеке не нужно.Создаем проект Для создания нового ASP.NET приложения используем, как всегда, — File→New→Project (кстати меню в новой студии опять сделали с нормальным шрифтом, а не ВСЕ КАПСОМ и лично мне оно сейчас кажется непривычным)Выбираем тип проекта ASP.NET Web Application. Структура шаблонов проектов и картинки опять запутаны — то что на нашем типе проекта нет значка vNext совсем не значит, что наш проект не буде на новой версии. Возможно в финальной версии 2015 это будет пофикшено.Если вы хотите добавить ваш проект в систему контроля версий — можно поставить галочку внизу (Add to source control).В следующем окне можно выбрать стартовый шаблон для веб приложений новой версии где уже будут несколько стандартных страниц и авторизация, а так же уже подключен MVC, создан базовый каркас проекта.
При создании проекта, если была поставлена отметка о добавлении в систему контроля версий — можно указать какая система версия нас интересует. Поддерживается TFS и Git.
При первом пуше (если используется Git можно сразу же указать внешний репозиторий)
Что нового в шаблонном проекте Наверное для .NET программиста что впервые создал ASP.NET 5 проект нового будет довольно много. Даже если взглянуть на структуру стартового проекта: Пройдемся по новшествах.
Никаких xml-конфигов. Теперь все конфигурации в json. Даже для тех, кто не знаком с синтаксисом это изменение будет абсолютно комфортным — конфигурации стали короче и понятнее. Файл проекта остался в xml, теперь расширение файлов проекта — *.kporj если это проект что выполняется на новом рантайме — K runtime. global.json — конфигурационный файл для всего солюшена. Изначально в нем одна строчка — «sources»: [ «src», «test» ]Которая указывает где находятся сорсы. Так же можно указать специфический путь к nuget пакетам. debugSettings.json — настройки Run/Debug для отдельно взятого проекта. wwwroot — корневая папка сайта, предназначена для статических файлов (html, css, img, js). Dependencies — если честно, понял не до конца, похоже, что NPM и Bower зависимости выделены отдельно в эту папку. References — теперь в референсах не библиотеки, а пакеты, довольно удобно для навигации и поиска. bower.json — пакеты Bower (front-end пакеты). config.json — что-то типа прошлого Web.config, только теперь короткий и понятный. gruntfile.js — конфигурация для Grunt (инструмент для сборки javascript). package.json — пакеты NPM. project.json — один из главных файлов проекта, включает nuget пакеты и настройки проекта. Первое впечатление — набросали все что можно в кучу. Возможно где-то так оно и есть. С другой стороны просто и быстро можно добавить любой пакет, увидеть все зависимости. Хотя пока не до конца понятно, например, что кроме Grunt стоит включать в пакеты NPM. И, опять же, возможно в финальной версии все будет как-то аккуратней, пока довольно интересно видеть тулы для веб разработки с других платформ внутри Visual Studio.Начинаем кодить. Архитектура, фронт-енд. Структуру проекта логично сделать следующей: все статические файлы (одностраничный сайт на AngularJs) помещаем в папку wwwroot, а для бек-енда создадим новый контроллер, который будет выступать как API для сайта. Кстати в новой версии ASP.NET контроллеры MVC и WebAPI больше не различаются.Я по привычке создаю в wwwroot страничку index.html и папку app. В app-e у меня находятся все вьюшки, контроллеры, сервисы angular. А в index.html — ссылки на все js файлы и layout.По скольку наша главная страница — это index.html, то маршрут (route) по умолчанию в Startup.cs нужно удалить (чтобы, заходя на сайт, мы заходили на index.html, а не на Home/Index).Добавим стандартную bootstrap-страничку и набор скриптов на index.html
, а также стартовое представление — home.html и «рабочую» страницу start.htmlДетальней можно посмотреть в коммите.
Бек-енд. Модель Для начала нам нужно где-то хранить наши данные. В нашем проекте уже создан контекст для работы с базой — ApplicationDbContext. Давайте добавим в него модель нашего приложения.В первую очередь нам нужны сами задания — назовем их QuestTask. Он будет хранить информацию о задании — номер, заголовок, содержание, ответ. namespace HabraQuest.Models { public class QuestTask { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public int Number { get; set; } public string Answer { get; set; } } } Ну и для того, чтобы информация о пройденных уровнях пользователя — добавим таблицу Progress где будут хранится токен пользователя и номер его последнего пройденного задания. Токены будем выдавать при первом обращении к странице и хранить в cookies.
namespace HabraQuest.Models { public class Progress { public int Id { get; set; } public string Token { get; set; } public int TaskNumber { get; set; } } } В общем наша модель готова — добавим эти свойства в контекст.
public class ApplicationDbContext: IdentityDbContext
Погуглил, посмотрел на код, понял, что теперь для миграций надо еще реализовывать IMigrationMetadata. Набросал реализацию. Опять ошибка.Посмотрел код еще раз…
После 5-ти таких итераций понял, что в одном месте у меня таблица называется dbo.Progress в другом просто Progress. Написал одинаковое название и все заработало. Приятно, что фактически миграции можно писать самому, хотя, конечно же, такие вещи лучше делать стандартными инструментами.Результат игры с миграциями можно посмотреть на гитхабе.Итак у нас есть фронт-енд и модель. Осталось создать API.
Бек-енд. Controller-ы ! код не несет никакой практической ценности, не отрефакторен, написан на коленке.! в основном хотелось попробовать новшества C# 6, типа инициализации свойств, оператор?…Назовем наш контроллер по работе с заданиями квеста — QuestController и реализуем метод на get-запрос по проверке ответа и post запрос для функциональности «начать с начала». [AllowAnonymous] [Route («api/[controller]»)] public class QuestController: Controller { // GET api/MainQuest [HttpGet] public MainQuestViewModel Get (string token, string taskNumberString, string answer) { // реализация… }
// POST api/MainQuest [HttpPost] public void Post (string token, bool startAgaing) { if (startAgaing) { using (var db = new ApplicationDbContext ()) { var progress = db.Progress.Single (_ => _.Token == token); progress.TaskNumber = 1; db.SaveChanges (); } } } } и контроллер для статистики (get — сколько людей просмотрело\прошло текущее задание, post — добавить свое имя в таблицу финишировавших):
[AllowAnonymous] [Route («api/[controller]»)] public class StatisticsController: Controller { // GET api/Statistics [HttpGet] public StatisticsResult Get (string token) { using (ApplicationDbContext db = new ApplicationDbContext ()) { var current = db.Progress.FirstOrDefault (_ => _.Token == token); int taskNumber = current?.TaskNumber? 1; // пример новой фичи C#
return new StatisticsResult { Watched = db.Progress.Count (_ => _.TaskNumber >= taskNumber), Done = db.Progress.Count (_ => _.TaskNumber > taskNumber) };
} }
// POST api/Statistics [HttpPost] public Finisher[] Post (string token, string name) { //… } }
public class Finisher { public int Id { get; set; } public string Name { get; set; } public DateTime Time { get; set; } }
public class StatisticsResult { public string Ok { get; } = «OK»; // пример новой фичи C# public int Watched { get; set; } public int Done { get; set; } } Для того, чтобы данные с сервера приходили в удобном для javascript camelCase надо добавить следующий код в Startup.ConfigureServices:
services.AddMvc ().Configure
Вот так выглядит окно команд:
Пока не уверен почему, но с первого раза создать миграцию не получилось, возможно из-за моих изменений в конфигурации. Для того, чтобы она заработала пришлось унаследовать контекст от DbContext и создать объект конфигурации внутри OnConfiguring контекста.
protected override void OnConfiguring (DbContextOptions options) { var efConfiguration = new Microsoft.Framework.ConfigurationModel.Configuration () .AddJsonFile («config.json») .AddEnvironmentVariables (); options.UseSqlServer (efConfiguration.Get («Data: DefaultConnection: ConnectionString»)); } Но таким способом не подхватились предыдущие миграции, пришло удалить часть сгенерированного кода.Кстати, задеплоить в azure получилось раза с 10. Хотя до сих пор не ясно в чем были проблемы.
Хостим в Azure… 3 дня
Казалось бы, все просто. Создать сайт в Azure, скачать профиль для публикации, 2 клика в студии и все. Не тут-то было… Наверное эти попытки я запомню на всю жизнь. Чтобы захостить сайт в Azure я потратил в 5 раз больше времени чем на всё остальное. Вылетала внутренняя ошибка (500) еще на какой-то самой ранней стадии и даже не получалось посмотреть из-за чего. Сначала я решил, что какая-то функционально еще не поддерживается. Начал пробовать хостить как можно более базовую функциональность. Но все другие примеры задеплоить получалось. Думал может какая-то проблема с базой или миграциями, но опять же на простых примерах все работало. В конце концов начал искать методом добавления функциональности по чуть-чуть. Оказалось, что все падает из-за:
Array.Empty
UPDНапишу еще раз:! код не несет никакой практической ценности, не отрефакторен, написан на коленке.