Пишем код

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

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

    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-сущностей)

Опубликовать в Facebook
Опубликовать в Google Plus

Written by Shaddix

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

Posted in .net,agile,MVC,t4mvc

Leave a Reply