Сегодня отметился докладом на томском локальном митапе фронтэнд разработчиков TomskJS.
Рассказывал про веб-компоненты и Polymer. Презентацию смотреть на Слайдшаре
или скачивать по ссылке.
Видео:
Заметки о .net и React разработке
Есть замечательный фреймворк построения веб-приложений — Polymer. Это даже скорее не фреймворк, а реализация идеологии WebComponents в современных реалиях веб-браузеров.
Если кратко, то с его помощью можно реализовывать полноценный, удобный и быстрый (как с точки зрения производительности решения, так и скорости разработки) MVVM в JavaScript:
<dom-module id="my-component"> <template> <h4>ToDo list for {{name}}:</h4> <ul> <template is="dom-repeat" items="{{model.todoitems}}"> <li>{{item.title}}</li> </template> </ul> <button title="ChangeName" on-tap="ChangeName"></button> </template> </dom-module> <script> Polymer({ is: 'my-component', name: 'Artur', model: { items: [ {title: '1'} ] }, ChangeName: function() { this.model.name = this.name + "_1"; }, }); </script>
В WPF мы привыкли, что интерфейс перерисовывается автоматически, при изменении значения свойства: this.name = ‘New Name’;. Однако у многих js-mvvm-фреймворков, присвоения приходится производить с помощью магических функций: this.set(‘name’, this.name + «_1»). В Polymer успешно работает первый (удобный :)) вариант, однако при работе с массивами и вложенными объектами эти прелести заканчиваются. Код добавления новых элементов в список ToDo выглядит примерно так:
Add: function() { this.push('items', {title: 'another item'}); }
Редактирование же элементов в списке (например, изменение title) будет еще более ужасным:
this.set('items.1.title', 'new title!');
Последние годы только ленивый не писал о javascript-фреймворках. AngularJS/KnockoutJS/Backbone/Ember — выбирай на вкус :) Но, озадачившись выбором фреймворка для небольшого веб-приложения с год назад, оказалось, что серебряной пули в мире js все еще не придумали.
Backbone и Ember слишком низкоуровневы и многословны, Knockout расстраивает постоянными конвертациями данных в observable, AngularJS.. да, почему бы не попробовать Angular, подумал я тогда. Энгуляр обладал приятным синтаксисом, структура Контроллеров была более менее близка и понятна и всё шло хорошо, пока.. пока мы не уперлись в довольно стандартную для Angular проблему произодительности.
Continue reading
Как известно, в ASP.Net MVC4 одной из главных появившихся фишек стал WebAPI.
Вместе с обновлением Web Tools 2012.2, вышедшем, как ни странно, в 2013, Майкрософт добавил к WebAPI интересную функцию — генерацию страницы помощи (help-page) с описанием вашего API в человеко-читаемом виде, и даже с примерами в виде xml и json. Большой плюс этой страницы в том, что информацию и описание API можно брать прямо из xml-комментариев к методам API-контроллера
Сегодня, однако, я натолкнулся на досадную ошибку в работе этой самой страницы помощи:
для метода вида
/// <summary> /// description /// </summary> [HttpGet] public string Tst([FromUri] TstModel filter) { return null; }
Проблема, как оказалось, была в том, что TstModel я, как водится, объявил вложенным классом (nested class) в самом контроллере. Из-за этого help page слегка потерял голову и необходимой документации не увидел :)
Для интересующихся и/или столкнувшихся с той же самой проблемой, фикс прост:
XmlDocumentationProvider.GetTypeName
line 109: return type.FullName.Replace("+", "."); //was: return type.FullName line 101: string typeName = genericType.FullName.Replace("+", "."); //was: string typeName = genericType.FullName
Может, кто подскажет, куда можно послать по этому поводу bug-report? :)
Кроме как через nuget письмом автору Microsoft.AspNet.WebApi.HelpPage — других вариантов не найдено :)
Долгое время одним из первых шагов после создания нового MVC-сайта у меня было редактирование .csproj и добавление магического элемента
<mvcbuildviews>true</mvcbuildviews>
Напомню, что этой директивой включается компиляция Razor-вьюшек, таким образом об ошибках в .cshtml файлах можно узнать не в рантайме (при открытии веб-странички), а во время компиляции проекта. Опция, безусловно, очень удобная, но очень сильно замедляющая процесс сборки. Так, при отключенной опции наш средних размеров MVC-проект собирается за 1 секунду, а со включенной — за 10 секунд (в проекте всего лишь чуть больше 100 cshtml файлов).
Такая немаленькая задержка очень серьезно сказывается на разработке — по сути при каждом билде можно легко успеть «переключиться» на браузер и пролистать пару страничек :)
Таким образом от MvcBuildViews очень захотелось отказаться, но при этом не потерять возможность обнаружения ошибок во вьюшках во время компиляции.
Проблему решил замечательный RazorGenerator от Дэвида Эббо. РазорГенератор интегрируется в качестве Custom Tool для Visual Studio и преобразует каждую вьюшку в соответствующий c#-код, который и компилируется в момент компиляции проекта и выдает ошибки абсолютно аналогичные MvcBuildViews. При этом время компиляции с RazorGenerator’ом составляет(в нашем случае) ту же 1 секунду, что и без него (с отключенным MvcBuildView). Итоговый выигрыш — в 10 раз (10 сек — компиляция + MvcBuildViews, 1 сек — компиляция + RazorGenerator).
Continue reading
В рамках ModelUnbinder, об основном сценарии использования которого я уже писал, была решена еще одна немаловажная проблема — работа со «сложными» моделями.
Рассмотрим пример: допустим, у нас на сайте есть форма поиска, и есть экшен, который показывает результаты:
public class SearchModel { public string SearchString { get; set; } public bool InLocalArea { get; set; } public int Page { get; set; } public int PerPage { get; set; } } public virtual ActionResult Search(SearchModel searchModel) { return Json(searchModel, JsonRequestBehavior.AllowGet); }
Допустим, мы хотим дать ссылку на результаты какого-то поиска и хотим воспользоваться для этого T4MVC. Попробуем так:
<a href="@Url.Action(MVC.Home.Search(new HomeController.SearchModel() { SearchString = "qweasd", }))">Предзабитый поиск1</a>
В результате у нас получится URL вроде: http://localhost:30729/Home/Search?searchModel=MvcApplication2.Controllers.HomeController.SearchModel. Это, конечно, не совсем то, что нужно :) Для решения проблемы воспользуемся ModelUnbinder’ом:
protected void Application_Start() { //blablabla ModelUnbinderHelpers.ModelUnbinders.Add(typeof(HomeController.SearchModel), new PropertiesUnbinder()); }
PropertiesUnbinder — это анбайндер, поставляемый в комплекте с T4MVC. Собственно, с его помощью в данном случае мы и получаем требуемый результат, ссылка теперь выглядит таким образом: http://localhost:30729/Home/Search?searchModel.SearchString=qweasd&searchModel.InLocalArea=False&searchModel.Page=0&searchModel.PerPage=0. Конечно, и этот URL можно чуть-чуть улучшить (например, не писать дефолтные значения value-type’ов), но по крайней мере эта ссылка вполне корректно работает, а в исходном коде нам пришлось поменять всего одну строчку — добавление анбайндера.
В реальной жизни в таких случаях я предпочитаю создавать пустой интерфейс IComplexModel, который реализуют все подобные модельки, и добавлять анбайндер уже только для этого интерфейса. Таким образом код выглядит как-то так:
public interface IComplexModel { } public class SearchModel : IComplexModel { public string SearchString { get; set; } /* ... */ } protected void Application_Start() { //blablabla ModelUnbinderHelpers.ModelUnbinders.Add(typeof(IComplexModel), new PropertiesUnbinder()); }
Вот и всё! Можно лишь добавить, что PropertiesUnbinder отлично работает и с массивами внутри модели, то есть модель вида:
public class SearchModel : IComplexModel { public int[] Numbers { get; set; } }
так же будет вполне адекватно преобразована в соответствующий URL.
Как обычно, тестовый проект с примером можно скачать и полюбопытствовать. Буду рад, если это кому-нибудь пригодиться.
Раньше в веб-проектах на asp.net mvc у меня часто встречался повторяющийся код типа:
public ActionResult UserProfile(int id) { var user = Session.Load<user>(id); // do something... }
Действительно, этот код очень типичен, обычно все объекты адресуются по ID и экшены обычно оперируют с этими объектами. Поэтому аргументами экшенов часто становятся идентификаторы, и где-то в начале экшенов мы запрашиваем из БД собственно объекты.
Задуматься над этим кодом меня заставил недавний пост Айенды. Немного подумав, стало ясно, что подобные участки очень просто автоматизировать и превратить во что-то вроде:
public ActionResult UserProfile(User user) { // do something... }
Дополнительным плюсом такой конструкции будет большая читаемость (очевидно, что метод работает именно с пользователем, а не с абстрактным int id) и строго-типизированность при вызове из Url.Action, RenderAction и тестов.
Continue reading
При использовании one-click publish я как-то раз столкнулся с подобной ошибкой:
It is an error to use section registered as allowDefinition=’MachineToApplication’ beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS.
Происходит она при попытке компиляции проекта после первого же паблиша (на удаленный сервер или даже в локальную папку).
Столкнувшись сегодня с ней во второй раз и осознав, что я не помню, «что это было» и как это лечится, решил написать заметку.
Continue reading
Сегодня мне вдруг стало любопытно, как происходит байндинг булевых полей в MVC.
Причину любопытства можно пояснить на примере:
Допустим, у нас есть модель:
public class TestModel { public string Name {get;set;} public bool IsDeleted {get;set;} }
И есть две хтмл-формы для редактирования этой модели, в первой из которых присутствует флаг IsDeleted, а во второй — нет:
Форма 1:
@using (Html.BeginForm) { @Html.TextboxFor(x => x.Name) @Html.CheckboxFor(x => x.IsDeleted) <input type="submit" value="OK" /> }
Форма 2:
@using (Html.BeginForm) { @Html.TextboxFor(x => x.Name) <input type="submit" value="OK" /> }
При этом серверный обработчик этой формы выглядит вполне типично:
public ActionResult UpdateModel(int id) { var model = Db.Load<TestModel>(id); TryUpdateModel(model); return RedirectToAction(); }
Предположим, что в базе у TestModel IsDeleted == true. Что произойдет, если
Как и ожидалось, в обоих случаях MVC отработал отлично, и в случае 1 в базе у сущности TestModel поле IsDeleted стало false, а в случае 2 — осталась в true.
В чем же причина любопытства? Мне было любопытно, как же именно это работает.
В «классическом html» чекбоксы обычно представлены в виде <input name=’IsDeleted’ type=’checkbox’ />. А при сабмите формы на сервер это отправляется в виде:
Как видно, в «классическом случае» ситуация отсутствия чекбокса в форме как такового и ситуация, когда он есть, но «галочка не стоит» абсолютно одинаковы, и выполнить корректный байндинг не представляется возможным.
Именно поэтому мне было интересно заглянуть «под капот» и узнать, как же это реализовано в mvc3.
Решение оказалось простым: на каждое булево поле mvc генерит такой html:
<input type="checkbox" name="IsDeleted" value="true" /><input type="hidden" name="IsDeleted" value="false" />
Вот и весь секрет :)
Возможно, это вполне стандартный прием у веб-программистов, но я о нём почему-то не знал, потому восполнить пробел было очень любопытно, и, уверен, этот приём мне еще не раз пригодится на практике.
Недавний релиз ASP.Net MVC4 Beta заставил меня внимательно посмотреть на новые возможности и без того замечательного фреймворка. Среди этих возможностей наиболее любопытной мне показалась Web API, которая предоставляет удобный интерфейс создания открытых API для сайтов/сервисов, для использования их из различных клиентов (javascript, мобильные приложения, b2b-сервисы, а то и вовсе desktop-клиенты :)).
Заодно я решил слегка обновить свои знания о вариантах создания веб-сервисов на .net в принципе, и вспомнить, что нам на сегодняшний день предлагает WCF. Оказалось, что нового весьма немало.
Continue reading