Пишем код

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

Archive for the ‘t4mvc’ Category

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

without comments

В рамках ModelUnbinder, об основном сценарии использования которого я уже писал, была решена еще одна немаловажная проблема — работа со «сложными» моделями.
Рассмотрим пример: допустим, у нас на сайте есть форма поиска, и есть экшен, который показывает результаты:

<br />
        public class SearchModel<br />
        {<br />
            public string SearchString { get; set; }<br />
            public bool InLocalArea { get; set; }<br />
            public int Page { get; set; }<br />
            public int PerPage { get; set; }<br />
        }</p>
<p>        public virtual ActionResult Search(SearchModel searchModel)<br />
        {<br />
            return Json(searchModel, JsonRequestBehavior.AllowGet);<br />
        }<br />

Допустим, мы хотим дать ссылку на результаты какого-то поиска и хотим воспользоваться для этого T4MVC. Попробуем так:
<br />
<a href="@Url.Action(MVC.Home.Search(new HomeController.SearchModel()
             {
                 SearchString = "qweasd",
             }))">Предзабитый поиск1</a><br />

В результате у нас получится URL вроде: http://localhost:30729/Home/Search?searchModel=MvcApplication2.Controllers.HomeController.SearchModel. Это, конечно, не совсем то, что нужно :) Для решения проблемы воспользуемся ModelUnbinder’ом:
<br />
        protected void Application_Start()<br />
        {<br />
            //blablabla<br />
            ModelUnbinderHelpers.ModelUnbinders.Add(typeof(HomeController.SearchModel), new PropertiesUnbinder());<br />
        }<br />

PropertiesUnbinder — это анбайндер, поставляемый в комплекте с T4MVC. Собственно, с его помощью в данном случае мы и получаем требуемый результат, ссылка теперь выглядит таким образом: http://localhost:30729/Home/Search?searchModel.SearchString=qweasd&searchModel.InLocalArea=False&searchModel.Page=0&searchModel.PerPage=0. Конечно, и этот URL можно чуть-чуть улучшить (например, не писать дефолтные значения value-type’ов), но по крайней мере эта ссылка вполне корректно работает, а в исходном коде нам пришлось поменять всего одну строчку — добавление анбайндера.

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

<br />
        public interface IComplexModel { }<br />
        public class SearchModel : IComplexModel<br />
        {<br />
            public string SearchString { get; set; }<br />
            /* ... */<br />
        }</p>
<p>        protected void Application_Start()<br />
        {<br />
            //blablabla<br />
            ModelUnbinderHelpers.ModelUnbinders.Add(typeof(IComplexModel), new PropertiesUnbinder());<br />
        }<br />

Вот и всё! Можно лишь добавить, что PropertiesUnbinder отлично работает и с массивами внутри модели, то есть модель вида:

<br />
        public class SearchModel : IComplexModel<br />
        {<br />
            public int[] Numbers { get; set; }<br />
        }<br />

так же будет вполне адекватно преобразована в соответствующий URL.

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

Written by Shaddix

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

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

Model unbinder в T4MVC

without comments

В предыдущей заметке я рассказывал об удобствах использования кастомного 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>.
Вот как может выглядеть, например, простейший анбайндер:

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

Как видим, для всех объектов типа BaseEntity (предположим, что это базовый класс для наших доменных сущностей), он сохраняет в запрос идентификатор этих объектов. Таким образом при использовании @Url.Action(MVC.Home.UserInfo(user)) мы получим строку запроса вида /home/userinfo?user=2 (без использования анбайндера было бы что-то вроде /home/userinfo?user=MvcApplication.Models.User)
Осталось всего-лишь зарегистрировать анбайндер в T4MVC. Для этого где-нибудь в Application_Start необходимо написать что-нибудь вроде:
<br />
        protected void Application_Start()<br />
        {<br />
            //...some initialization code</p>
<p>            ModelBinders.Binders.Add(typeof(MyUser), new MyUserBinder());  // регистрируем кастомный MVC-шный байндер<br />
            ModelUnbinderHelpers.ModelUnbinders.Add(new MyUserUnbinder()); // регистрируем T4MVC-анбайндер<br />
        }<br />

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

Written by Shaddix

Июль 3rd, 2012 at 10:23 пп

Posted in .net,agile,MVC,t4mvc

T4MVCJS отрефакторен и выложен на codeplex

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

Недавно дошли руки до выкладывания T4MVCJS в opensource. Распространение исходников в зип-архиве показалось слегка устаревшей методикой и мы переехали на codeplex :)

Попутно было слегка отрефакторено использование T4MVC, вместо простой «копипасты» теперь используется оригинальный исходник с вырезанными из него строками, отвечающими за генерацию T4MVC-хэлперов. Таким образом легко и просто используется весь парсинг, осуществляемый T4MVC, и обновление до новых версий будет представлять куда меньше проблем (скопипастить файл, выкинуть 400 подряд идущих строк — вуа-ля :)).
Помимо эстетического удовлетворения это позволило с лёгкостью обрабатывать MVC Area (предыдущая версия, этого не умела, за репорт этого бага спасибо Брайану Бетти).

Заодно я задумался о проблеме существования двух экшенов с одинаковыми именами — в Javascript перегрузка функций, к сожалению, недоступна. В результате на свет появляются экшены Edit, Edit1, Edit2, etc. :) Если кто-нибудь предложит более адекватное решение проблемы — я бы с удовольствием его обсудил :)

P.S. в качестве системы контроля версий T4MVCJS используется Mercurial, так что при желании внести изменения — форкайте с удовольствием :)

P.P.S. На момент изначальной публикации поста ареи-таки не работали. Начиная с версии 1.0.10 всё ок.

Written by Shaddix

Февраль 23rd, 2012 at 2:51 пп

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 пп

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

without comments

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

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

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

И есть javascript-код, который ищет блоки с этими атрибутами и в соответствии со значениями применяет определенную анимацию. В итоге имена атрибутов (magic-strings по сути) дублируются во вьюшках/контроллерах и js-файлах.
Еще одной похожей проблемой становится посылка аякс-запросов к контроллерам:
<br />
$('#mydiv').load("http://mysite.ru/Items/GetItemInfo?id=20");<br />

URL запроса явно грозит нам опечатками и/или ошибками, когда этот адрес изменится.
Если мы пишем яваскрипт-код прямо в html файлах (а не в отдельных подключаемых скрипт-файлах), то можно, конечно, воспользоваться T4MVC и написать что-то вроде:
<br />
$('#mydiv').load("@Url.Action(MVC.Items.GetItemInfo())");<br />

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

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

Written by Shaddix

Ноябрь 29th, 2011 at 1:39 пп

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

2 комментария

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

<br />
// в контроллере:<br />
public ActionResult Index(int a, string b) {} </p>
<p>// во вьюшке (Razor)<br />
<a href="@Url.Action(MVC.Home.Index(10, 'some_string'))">link</a>  </p>
<p>//в браузере на клиенте<br />
<a href="/Home/Index?a=10&b=some_string">link</a><br />

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

Written by Shaddix

Октябрь 27th, 2011 at 5:08 пп

Posted in .net,MVC,t4mvc,web