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

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

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

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

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

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

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

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

Валидация JavaScript в ASP.Net MVC проекте — анализ существующих решений

Когда яваскрипт в веб-проекте занимает достаточно большую долю, и клиентские скрипты вырастают за рамки «инлайн-скриптов» с одной-двумя строчками вызова jquery-плагинов, вопрос о проверке и валидности яваскрипта встает достаточно явно (даже для .net-разработчиков, достаточно далеких от js в принципе :)).

Для меня эта проблема стала наиболее актуальной, когда я начал генерировать яваскрипт-хэлперы с помощью T4-шаблонов, и стало просто необходимым своевременно узнавать о том, что яваскрипт-переменная Constants.MainController.ContentId больше не существует (а была переименована в ContentsId). Также хотелось сократить цикл обнаружения банальных опечаток в названиях переменных — раньше об этом говорила консоль ошибок браузера, а хотелось видеть это на этапе билда.

В решении данной проблемы мне помогли «псевдо-компиляторы» яваскрипта, хотя, пожалуй, корректнее было бы называть их верификаторами синтаксиса.
Continue reading

Повторное использование шаблонов DisplayTemplates и EditorTemplates

При работе с шаблонами отображения и редактирования достаточно быстро возникает необходимость повторного использования однажды написанных шаблонов.

Однако, по аналогии с portable areas, при попытке выноса шаблонов в отдельную сборку возникают проблемы, связанные с тем, что MVC ищет данные шаблоны в строго отведенных местах (папка ~/Views/Shared/DisplayTemplates или ~/Views/ControllerName/EditorTemplates).

Решает эту проблему очередной open-source продукт от Дэвида Эббо — Razor Generator.

Continue reading

Шаблоны отображения и редактирования форм в ASP.Net MVC (DisplayTemplates/EditorTemplates)

Шаблоны отображения и редактирования — очень мощная и полезная фича ASP.Net MVC, которую я активно использую и рекомендую всем без исключения.

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

Предположим, что в контроллере у вас есть типичная модель регистрации:

public class RegistrationModel {
    public string Login { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime Birthdate { get; set; }
    ...
}

Continue reading

Устранение magic-strings в javascript

Недавно я озадачился проблемой «магических строк», которые регулярно появляются в яваскрипте.

Допустим, у нас на страничке динамически генерируются блоки вида:

<span animate_period="10" animate_amplitude="20"></span>

И есть javascript-код, который ищет блоки с этими атрибутами и в соответствии со значениями применяет определенную анимацию. В итоге имена атрибутов (magic-strings по сути) дублируются во вьюшках/контроллерах и js-файлах.
Еще одной похожей проблемой становится посылка аякс-запросов к контроллерам:

$('#mydiv').load("http://mysite.ru/Items/GetItemInfo?id=20");

URL запроса явно грозит нам опечатками и/или ошибками, когда этот адрес изменится.
Если мы пишем яваскрипт-код прямо в html файлах (а не в отдельных подключаемых скрипт-файлах), то можно, конечно, воспользоваться T4MVC и написать что-то вроде:

$('#mydiv').load("@Url.Action(MVC.Items.GetItemInfo())");

Как раз о таком способе решения проблемы я недавно и писал. Но если js вы всё-таки выносите в отдельные файлы (а делать это надо — для уменьшения дублирования и клиентского кэширования), то проблема так просто не решается.

Столкнувшись с проблемой на довольно-таки большом проекте, в голову пришла мысль воспользоваться всей мощью шаблонов T4 и сгенерировать соответствующие javascript-хэлперы. Всё сложилось удачно (шаблон можно скачать), в результате обработки данного шаблона получается яваскрипт-файл T4MVC-JS.js, который можно легко инклюдить через тег <script> в хтмле, и так как он генерится не в рантайме и для Visual Studio ничем не отличается от обычного яваскрипт-файла, то по объявленным в нем переменным работает интеллисенс, что сокращает вероятность ошибок.
Continue reading

Светлая сторона property-injection

О плюсах инверсии зависимостей и той гибкости, которую она даёт приложениям сказано очень многое, и я искренне надеюсь, что она повсеместно используется :)
Как известно, существует несколько способов инверсии зависимостей:

  • Constructor-based injection — инъекция через конструктор. Класс объявляет свои зависимости как параметры в конструкторе:
        public class MyImageProcessor
        {
            private readonly IScreenshooter _screenshooter;
            private readonly IImageResizer _imageResizer;
            private readonly IImageComparer _imageComparer;
    
            public MyImageProcessor(IScreenshooter screenshooter, IImageResizer imageResizer, IImageComparer imageComparer)
            {
                _screenshooter = screenshooter;
                _imageResizer = imageResizer;
                _imageComparer = imageComparer;
            }
        }
    

    Процессор получает сервисы, которые ему нужны для работы через конструктор и в дальнейшем работает с ними.

  • Property-based injection — инъекция через публичные свойства. Свойства в этом случае обычно необходимо украшать атрибутами:
        public class MyImageProcessor2
        {
            [Inject]
            public IScreenshooter Screenshooter { get; set; }
            [Inject]
            public IImageResizer ImageResizer { get; set; }
            [Inject]
            public IImageComparer ImageComparer { get; set; }
    
            public MyImageProcessor2()
            {
            }
        }

    Выглядит короче, но имеет очевидные минусы — зависимости класса непонятны (легко забыть инициализировать свойства, например, из тестов; при инъекции через конструктор с этим проблем нет), доступ к сервисам «открыт внешнему миру», что также не всегда является желаемым поведением.

Есть еще field-injection, когда инъекция идет не через публичные свойства, а через публичные поля, но этот вариант уже почти официально считается «говнокодом» :)

До недавнего времени в подавляющем большинстве случаев я использовал инъекцию через конструктор, и с трудом представлял причины, по которым можно предпочесть property-injection.
Continue reading

Строготипизированные URL в Javascript c T4MVC

Если кто не знает, то T4MVC — это замечательная штука, которая позволяет строготипизировать в MVC3 то, что еще недостроготипизировано из коробки :)
В частности, с его помощью очень удобно генерировать ссылки на MVC-экшены в хтмл:

// в контроллере:
public ActionResult Index(int a, string b) {} 

// во вьюшке (Razor)
<a href="@Url.Action(MVC.Home.Index(10, 'some_string'))">link</a>  

//в браузере на клиенте
<a href="/Home/Index?a=10&b=some_string">link</a>

Проблемы возникают, когда параметры экшена — динамические, и их значения можно определить только в рантайме (например, параметром является какой-нибудь javascript-атрибут).
Continue reading

Русификация ASP.Net MVC3-приложений

Мой первый «коммерческий» проект на ASP.Net MVC — небольшой сервис, ориентированный на русскоговорящую аудиторию. В этой заметке я хотел бы собрать проблемы, с которыми я столкнулся в процессе «русификации» MVC3 — то есть адаптации к российской локали и русификация интерфейса и сообщений об ошибках.

Проблемы, описываемые в этом посте:

  • Указание культуры, использующейся по-умолчанию в байндингах
  • Создание кастомного байндера
  • Русификация сообщений от DataAnnotations-атрибутов
  • Русификация сообщений от дефолтного байндера
  • Проблемы интеграции локализации и Ninject

Continue reading

TDD шагает по планете

О TDD в последнее время очень много говорят, и бытует мнение, что его применение фактически гарантирует стабильный продукт и этому есть «множество примеров в отрасли». Я засомневался на предмет того, что «в отрасли есть множество примеров», и спросил у хабрасообщества, используете ли вы TDD? Опрос, впрочем, предполагал куда более широкие ответы, и получилась интересная статистика не только по TDD как таковому, но и по распространенности автоматизированных тестов в отрасли в принципе.

Результаты неподготовленного читателя могут просто шокировать.
Continue reading

IoC-контейнер для ASP.Net MVC — Ninject

Уверен, что очень скоро сам забуду, почему же выбрал Ninject, так что оставлю здесь пару причин. До недавнего времени использовал Unity, но пришло время что-то менять :)

Ninject vs Unity — если «made by Microsoft» для многих является аргументом «за», то для меня, подсознательно, совсем наоборот :) По факту — Unity слабо развивается, и интеграции в MVC «из коробки» в нём банально нет. Ключевым аргументом к отказу от Unity стало отсутствие PerRequestLifetimeManager (синглтон в рамках одного запроса, очень полезен для Session/ObjectContext).

Ninject vs Castle Windsor — windsor как-то совсем не обрадовал. Опять-таки нет встроенной интеграции в MVC, нет инъекции в проперти (а в атрибуты иначе никак), и нет резолвинга классов, без их предварительной регистрации. Собственно, до «тестового приложения» здесь даже и не дошло. Совсем плохо.

StructureMap и AutoFac не рассматривались, уж больно понравилась ninject-овская интеграция :) Встает из nuget, настройки не требует вовсе (в контроллеры и атрибуты всё замечательно инъектится), очевидное место вставки регистраций, приятный синтаксис и множество тонких настроек. Да, выбор очевиден :)

Да, по скорости Ninject уступает и кастлу, и юнити, но производительность здесь не настолько критична — даже если отличие «в разы» — то все равно разница при создании десятка объектов — микросекунды. Преждевременно оптимизировать такие тонкие моменты не будем :)