Доклад о веб-компонентах и Polymer

Сегодня отметился докладом на томском локальном митапе фронтэнд разработчиков TomskJS.
Рассказывал про веб-компоненты и Polymer. Презентацию смотреть на Слайдшаре

или скачивать по ссылке.

Видео:

Polymer и NotifyPropertyChanged в JavaScript

Есть замечательный фреймворк построения веб-приложений — 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!');

Continue reading

Только React.js, только хардкор (aka долой Angular и Knockout)!

Последние годы только ленивый не писал о javascript-фреймворках. AngularJS/KnockoutJS/Backbone/Ember — выбирай на вкус :) Но, озадачившись выбором фреймворка для небольшого веб-приложения с год назад, оказалось, что серебряной пули в мире js все еще не придумали.
Backbone и Ember слишком низкоуровневы и многословны, Knockout расстраивает постоянными конвертациями данных в observable, AngularJS.. да, почему бы не попробовать Angular, подумал я тогда. Энгуляр обладал приятным синтаксисом, структура Контроллеров была более менее близка и понятна и всё шло хорошо, пока.. пока мы не уперлись в довольно стандартную для Angular проблему произодительности.
Continue reading

Баг в help-page для WebAPI

Как известно, в 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 — других вариантов не найдено :)

Ускорение билда asp.net MVC в 10 раз или RazorGenerator как альтернатива MvcBuildViews

Долгое время одним из первых шагов после создания нового 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 в T4MVC — работа со сложными моделями

В рамках 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.

Как обычно, тестовый проект с примером можно скачать и полюбопытствовать. Буду рад, если это кому-нибудь пригодиться.

Используем инфраструктуру MVC3 — скажи НЕТ повторяющимся session.Load(id);

Раньше в веб-проектах на 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 и MvcBuildViews в asp.net MVC

При использовании 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

Хитрость байндинга булевых полей в asp.net mvc

Сегодня мне вдруг стало любопытно, как происходит байндинг булевых полей в 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. Что произойдет, если

  • мы отсабмитим Форму1 со снятым флагом?
  • мы отсабмитим Форму2, в которой флага IsDeleted просто нет?

Как и ожидалось, в обоих случаях MVC отработал отлично, и в случае 1 в базе у сущности TestModel поле IsDeleted стало false, а в случае 2 — осталась в true.
В чем же причина любопытства? Мне было любопытно, как же именно это работает.
В «классическом html» чекбоксы обычно представлены в виде <input name=’IsDeleted’ type=’checkbox’ />. А при сабмите формы на сервер это отправляется в виде:

  • http://localhost/?name=zcx&IsDeleted=on — если флажок проставлен
  • http://localhost/?name=zcx — если флажок не стоит

Как видно, в «классическом случае» ситуация отсутствия чекбокса в форме как такового и ситуация, когда он есть, но «галочка не стоит» абсолютно одинаковы, и выполнить корректный байндинг не представляется возможным.

Именно поэтому мне было интересно заглянуть «под капот» и узнать, как же это реализовано в mvc3.
Решение оказалось простым: на каждое булево поле mvc генерит такой html:

<input type="checkbox" name="IsDeleted" value="true" /><input type="hidden" name="IsDeleted" value="false" />

Вот и весь секрет :)
Возможно, это вполне стандартный прием у веб-программистов, но я о нём почему-то не знал, потому восполнить пробел было очень любопытно, и, уверен, этот приём мне еще не раз пригодится на практике.

Варианты реализации WebServices в .net — WCF, REST, OData, WebAPI и другие умные слова — Part I

Недавний релиз ASP.Net MVC4 Beta заставил меня внимательно посмотреть на новые возможности и без того замечательного фреймворка. Среди этих возможностей наиболее любопытной мне показалась Web API, которая предоставляет удобный интерфейс создания открытых API для сайтов/сервисов, для использования их из различных клиентов (javascript, мобильные приложения, b2b-сервисы, а то и вовсе desktop-клиенты :)).

Заодно я решил слегка обновить свои знания о вариантах создания веб-сервисов на .net в принципе, и вспомнить, что нам на сегодняшний день предлагает WCF. Оказалось, что нового весьма немало.
Continue reading