Недавно я озадачился проблемой «магических строк», которые регулярно появляются в яваскрипте.
Допустим, у нас на страничке динамически генерируются блоки вида:
<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 ничем не отличается от обычного яваскрипт-файла, то по объявленным в нем переменным работает интеллисенс, что сокращает вероятность ошибок.
Как же именно шаблон решает вышеуказанные проблемы? Проблема имен атрибутов решилась просто — в контроллерах определяются публичные константы, а T4 генерит соответствующий хэлпер:
//константа в контроллере (для использования на server-side) public class MainController : Controller { public const string AnimatePeriodAttribute = "animate_period"; } //результат в .js-файле (для использования атрибутов на стороне клиента) var Constants = { MainController : { Name : "Main", AnimatePeriodAttribute : "animate_period" } }
Также генерится имя контроллера, мне в некоторых случаях оно было необходимо.
Проблема генерации ссылок также решилась просто:
//экшен в контроллере (server-side) public class MainController : Controller { public ActionResult GetItem(int id) { } public class GetItemModel { public int Id { get; set; } public string Name { get; set; } } public ActionResult GetItem2(GetItemModel model) { } } //результат в .js-файле var MvcActions = { MainController : { GetItem : function (id) { var parameters = ""; if (id) { if (parameters != '') { parameters = parameters + '&'; } parameters = parameters + 'id=' + id; } return "/Main/GetItem?" + parameters; } GetItem2 : function (Id, Name) { var parameters = ""; if (Id) { if (parameters != '') { parameters = parameters + '&'; } parameters = parameters + 'id=' + Id; } if (Name) { if (parameters != '') { parameters = parameters + '&'; } parameters = parameters + 'name=' + Name; } return "/Main/GetItem2?" + parameters; } } }
Как видно, шаблон работает как в случае параметризации экшена «простыми» типами (int, string, etc.), так и в случае параметризации более сложной Моделью (в этом случае в параметрах javascript-экшена появляются публичные свойства модели).
Использовать всё это безобразие очень просто:
$('#mydiv').load(MvcActions.MainController.GetItem(3)); //полностью яваскрипт код, который обратится к url вида http://mysite.ru/Main/GetItem?id=3
Внимательный читатель, однако, заметит в результирующем яваскрипте один большой drawback, который я надеюсь в скором времени устранить и написать об этом в отдельном посте :)
Стоит также отметить, что при переименовании экшенов или изменения имен констант вся эта «псевдо строгая типизация» слетит, поскольку имена в сгенерированном js-шаблоне изменятся, а в местах использования — нет. Для избежания таких ситуация (а точнее для успешной борьбы с их последствиями) я использую «компиляцию» яваскриптов, в процессе которой я и узнаю о подобных ошибках и получаю возможность оперативно их исправить. Экскурс в яваскрипт-компиляторы и их интеграцию с VS ожидается в рамках следующего поста :)
P.S. Собственно код шаблона можно получить по ссылке. До окончательного «полирования» кода руки не дошли, но со своими задачами он вполне справляется. Легко адаптируется для работы не только с MVC-контроллерами, но и с WebForms-контролами, или вообще произвольными классами на выбор.
Код содержит большие куски из T4MVC, и выполнен в рамках ознакомления с возможностями шаблонов T4 и EnvDTE. Возможности оказались весьма широкими :)