Пишем код

Заметки о .net разработке

Archive for the ‘web’ Category

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

without comments

Долгое время одним из первых шагов после создания нового 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).
Read the rest of this entry »

Written by Shaddix

Июль 15th, 2012 at 1:40 пп

ModelUnbinder в T4MVC — работа со сложными моделями

without comments

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

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

Written by Shaddix

Июль 5th, 2012 at 10:56 дп

Posted in .net,agile,MVC,t4mvc,web

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

6 комментариев

Раньше в веб-проектах на 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 и тестов.
Read the rest of this entry »

Written by Shaddix

Июнь 9th, 2012 at 5:51 пп

Posted in .net,agile,MVC,web

Проблема one-click publish и MvcBuildViews в asp.net MVC

without comments

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

Происходит она при попытке компиляции проекта после первого же паблиша (на удаленный сервер или даже в локальную папку).
Столкнувшись сегодня с ней во второй раз и осознав, что я не помню, «что это было» и как это лечится, решил написать заметку.
Read the rest of this entry »

Written by Shaddix

Апрель 17th, 2012 at 6:59 пп

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

7 комментариев

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

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

Written by Shaddix

Апрель 15th, 2012 at 10:52 пп

Posted in .net,MVC,web

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

without comments

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

Заодно я решил слегка обновить свои знания о вариантах создания веб-сервисов на .net в принципе, и вспомнить, что нам на сегодняшний день предлагает WCF. Оказалось, что нового весьма немало.
Read the rest of this entry »

Written by Shaddix

Март 9th, 2012 at 3:18 пп

Posted in .net,MVC,WCF,web

Настройка Web-Deploy на IIS7

without comments

Эта заметка из разряда «напоминалок чтобы не забыть».

Установить web-deploy проще всего через Web Platform Installer (если уже установлен — запустить из меню Пуск->Web Platform Installer).
В Web Platform Installer поиском по слову «deploy» найти «Web Deploy 3.0» или «Web Deployment Tool 2.1 blablabla».
Установить. Радоваться :)

В настройках one-click publish в VS прописать адрес сервера и имя сайта, в который будет идти деплой.
Обработчик web-deploy на стороне сервера будет работать по адресу http://xxxxxxx/MSDEPLOYAGENTSERVICE, на 80-м порту.

Деплоить на этот адрес смогут администраторы сервера. При получении ошибки 401 Unauthorized добавить ключ в реестр: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System (DWORD: LocalAccountTokenFilterPolicy = 1)
Это решит проблему, если пользователь, от имени которого идет деплой не «админ», но состоит в группе «Администраторы».

Настройка деплоя под неадминистративным пользователем требует доп. настроек прав доступа в IIS->Управление(Management)->Делегирование службы управления(Management Service Delegation). Требуется добавить правило «Развертывания приложений» для нужных пользователей. Детали хорошо описаны по ссылке.

В проблеме 401 Unauthorized может также помочь ссылка.

Written by Shaddix

Март 8th, 2012 at 2:17 пп

T4MvcJs — строготипизированный яваскрипт-хелпер для URL

6 комментариев

Не так давно я уже писал о решении проблемы «магических строк» в яваскрипте, примером таких строк могут служить url экшенов («/Home/Index?name=John&lastname=Doe»)

В разор-вьюшках проблему написания урлов «напрямую» можно решить с помощью T4Mvc: @Url.Action(MVC.Actions.Home.Index(«John», «Doe)). Проблема в том, что этот код — серверный, и написать что-то подобное в script.js — не получится.
В предыдущем посте я уже предлагал решение проблемы, однако(об этом я также писал) в нём была существенная недоработка. Новая версия T4MvcJs эти недоработки устраняет, и в результате мы, как и прежде, спокойно сможем писать в яваскрипт файлах что-то вроде: MvcActions.Home.Index(«John», «Doe»), и это будет полностью клиентский код.
Read the rest of this entry »

Written by Shaddix

Январь 18th, 2012 at 9:32 пп

Валидация JavaScript в ASP.Net MVC проекте — еще один велосипед или jsvalidator.codeplex.com

without comments

В предыдущей заметке я рассматривал существующие решения «псевдо-компиляции» яваскрипта и интеграции этих решений в asp.net проекты.

В итоге не найдя ничего идеального и решив, что собственный велосипед здесь не помешает, я и написал утилиту под названием jsvalidator. Найти её можно на codeplex, там же есть и краткое описание установки и конфигурирования. Данная заметка, по сути, будет переводом «официальной документации» собственного же сочинения :)

Если коротко, то jsvalidator это build-step с json-like конфигурационным файлом, проверяющая яваскрипт с помощью java-библиотеки google closure. Из этого следует и первое требование — для работы утилиты необходима установленная на компьютере java (с java.exe добавленным в системные пути (system PATH)).

После интеграции утилиты на каждый билд в вашей Visual Studio будет нечто вроде:

(в данном случае утилита сообщает нам о необъявленной переменной asd, по двойному клику на ошибке откроется js-файл на строчке с ошибкой, как мы и привыкли).
Read the rest of this entry »

Written by Shaddix

Январь 10th, 2012 at 3:01 пп

Unobtrusive validation и загрузка форм через аякс (full ajax website)

6 комментариев

При использовании «ненавязчивой» (unobtrusive) валидации форм в MVC проекте можно заметить, что если валидируемая форма была подгружена асинхронно, то валидация не сработает.

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

$.validator.unobtrusive.parse($("#loadedContent"));

(где #loadedContent — это айдишник подгруженной области).
В простейшем случае можно даже добавить этот код в обработчик события ajaxSuccess, чтобы уж точно не забыть :)

$.ajaxSuccess(function(e) { $.validator.unobtrusive.parse(document); });

P.S. Заметить факт неработоспособности клиентской валидации может с легкостью помешать принцип graceful degradation — по-умолчанию валидация отработает нормально, но это будет серверная валидация :) При тестировании на localhost это и правда можно не заметить.

Традиционный пример проекта с этой небольшой функцией.

Written by Shaddix

Январь 8th, 2012 at 5:57 пп