RavenDB в «одноразовых» приложениях

На выходных попробовал RavenDB на небольшой задаче обработки массива документов. Документов было не очень много — порядка 50К, их обработка — задача разовая, но её длительность получалась однозначно порядка 10 часов, плюс всё это отлично параллелилось.
Поэтому возникла мысль загнать все эти документы в БД, чтобы без проблем сохранять промежуточные результаты и не волноваться за exception’ы, безвозвратно прерывающие обработку 10-часового процесса в самом конце :)
Raven-Embedded видился неплохим кандидатом для такого использования, поскольку позволял не париться с маппингами, быстро «установить» БД, просто добавив nuget пакет, позволял динамически добавлять в документы новые структуры данных (результаты обработок) ну и по идее должен был быстро работать :)
Что же из всего этого получилось?
Continue reading

Mac OS X и горячие клавиши

Работать в Mac OS X после стольких лет программирования в windows — это настоящий стресс :) Причина проста и банальна — очень многие сочетания клавиш, к которым мы привыкли работают по-другому, или вообще не работают :)
Собственно, основные проблемы идут от того, что Ctrl переехал на клавишу Win, а Home и End переходят в начало и конец файла (а не начало и конец строки, как в windows).

К счастью, Ctrl и Win меняются местами прямо в настройках МакОси (Системные настройки -> Клавиатура -> Клавиши модификации), а Home и End возвращаются к привычному PC-функционалу с помощью DoubleCommand или ему-подобных утилит.
Эти два небольших шага сэкономили мне множество нервов и позволили сосредоточиться на деле, а не перестройке собственных привычек :)

Прочие же привычные по VisualStudio сочетания можно перенастроить уже прямо в MonoDevelop.
И да простят меня апологеты Mac OS за такие издевательства над системой :)

P.S. Спасибо Брайану Ханкинсу за наводки

Публикация исходников вместе с nuget — плюс исходники Av.Infrastructure.Web

На выходных (где-то с месяц назад, когда я только взялся за этот пост) наконец-то дошли руки до публикации исходников своей «инфраструктурной» библиотеки. По правде говоря, я это собирался сделать достаточно давно, но насущной необходимости не было, да и сейчас наврядли кому-то понадобиться заточенная по большому счету под личные нужды одна из сотен тысяч других библиотек-собраний-всяких-мелочей, однако публикация исходников вместе со своими собственными nuget-пакетами таки может кому-нибудь пригодиться.
Continue reading

Презентация и исходники с вводного семинара по NHibernate

В рамках MccTomskCamp 18 июля проводил небольшой вводный семинар по NHibernate. Ссылки на видео, презентацию и исходные коды можно найти в конце поста, а можно и посмотреть прямо здесь:

P.S. Большое спасибо всем посетившим семинар за проявленное внимание к мероприятию :)/a

Ускорение билда 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.

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

Model unbinder в T4MVC

В предыдущей заметке я рассказывал об удобствах использования кастомного model-binder’a для получения MVC-экшенов вида public ActionResult UserInfo(User user) (с использованием доменных классов User вместо int userId). Там же я упомянул и о неудобствах использования этого метода с T4MVC.
Последние несколько дней я активно работал над решением этой проблемы (пути решения которой мы предварительно обсудили с Дэвидом Эббо), а сегодня пулл-реквест с изменениями был принят в основную ветку (версия 2.10.0, доступна в том числе и через Nuget), и сейчас я хотел бы кратко описать, что в итоге получилось.

Для начала вспомним, в чем собственно была проблема.
Допустим, у нас есть экшен вида public ActionResult UserInfo(User user) и мы хотим сгенерировать для него URL.
Мы пишем @Url.Action(MVC.Home.UserInfo(user)) и получаем: /home/userinfo?user=MvcApplication.Models.User. Это происходит потому, что для преобразования объекта в параметр запроса MVC просто вызывает .ToString() от объекта. Нам же надо получить что-то вроде /home/userinfo?user=2 (подразумевая, что user.Id == 2).
Можно, конечно, переопределить метод .ToString() у класса User, но очень часто это решение не подходит (например, .ToString() уже переопределен человекочитаемым вариантом).

Для решения этой проблемы и получилась у нас система, обратная ModelBinder’ам, которая с легкой руки Дэвида получила название ModelUnbinder.
Суть ModelUnbinder’a противоположна ModelBinder’у, если байндер создает и инициализирует объект по значениям HttpRequest’a, то анбайндер, наоборот, формирует параметры запроса по объекту.

Анбайндер, по аналоги с байндером, должен релизовывать интерфейс IModelUnbinder или IModelUnbinder<T>.
Вот как может выглядеть, например, простейший анбайндер:

    public class NHibernateModelUnbinder : IModelUnbinder<BaseEntity>
    {
        public void UnbindModel(RouteValueDictionary routeValueDictionary, string routeName, BaseEntity routeValue)
        {
            if (routeValue != null)
                routeValueDictionary.Add(routeName, routeValue.Id);
        }
    }

Как видим, для всех объектов типа BaseEntity (предположим, что это базовый класс для наших доменных сущностей), он сохраняет в запрос идентификатор этих объектов. Таким образом при использовании @Url.Action(MVC.Home.UserInfo(user)) мы получим строку запроса вида /home/userinfo?user=2 (без использования анбайндера было бы что-то вроде /home/userinfo?user=MvcApplication.Models.User)
Осталось всего-лишь зарегистрировать анбайндер в T4MVC. Для этого где-нибудь в Application_Start необходимо написать что-нибудь вроде:

        protected void Application_Start()
        {
            //...some initialization code

            ModelBinders.Binders.Add(typeof(MyUser), new MyUserBinder());  // регистрируем кастомный MVC-шный байндер 
            ModelUnbinderHelpers.ModelUnbinders.Add(new MyUserUnbinder()); // регистрируем T4MVC-анбайндер
        }

Вот и всё! Можно скачать простейший тестовый пример с применением анбайндеров в T4MVC (и байндеров — в ASP.NET MVC :)), а можно и почитать подробнее про использование кастомных байндеров для удобства обращения с БД-сущностями в asp.net mvc (там же можно посмотреть и пример байндера для NHibernate-сущностей)

Mono for android и русские имена пользователей в windows

Пробовал на выходных поиграться с Mono For Android, и внезапно оказалось, что наличие в имени моей учетной записи кириллических символов вносит некоторые проблемы :)

Проблемы в основном связаны с конвертациями юникода-cp1251 в путях к файлам и их решение достаточно тривиально, и тем не менее, сохраню пару пунктов на будущее:

  • НЕ УСТАНАВЛИВАЙТЕ Mono For Android ПОД ПОЛЬЗОВАТЕЛЯМИ С КИРИЛЛИЦЕЙ В ИМЕНИ! :) Это самый простой путь и остальные шаги в этом случае не понадобятся
  • Проблема с путём к Android-SDK. По умолчанию он располагается в c:/Users/%USERNAME%/AppData/Local/Android/android-sdk/. Проще всего скопировать его в корень диска и изменить путь в Visual Studio -> Tools -> Options -> Mono For Android -> Android SDK Location
  • Проблема с путём к образам эмулируемых устройств. Образы устройств, которые запускаются в эмуляторе хранятся в папкахc:/Users/%USERNAME%/.android/avd/%DEVICENAME%. Пути к этим папкам прописываются в файлах c:/Users/%USERNAME%/.android/avd/%DEVICENAME%.ini. Папку устройства скопировать куда-нибудь в «нормальный» путь (без русских букв) и поменять путь в ini-файле. Несмотря на то, что в пути к самим ini файлам тоже есть русские буквы, это, как ни странно, к проблемам не приводит :)

Всем удачи с Mono for android!

Локализация сообщений о переполнении int в MVC3

Довольно давно я уже писал о локализации MVC3-сайтов, уделив достаточно большое внимание способу перевода валидационных сообщений на другие языки.

Однако недавно в проекте я столкнулся с одной ошибкой валидации, перевести которую оказалось не так просто. Вот пример этой ошибки:

Как нетрудно заметить, я просто ввел в числовое поле очень большое значение. При этом сам факт возникновения ошибки меня вполне устраивал (большие значение обрабатывать и не планировалось), а вот текст на «иностранном» тестеров слегка смутил :)

Continue reading

Используем инфраструктуру 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