Пишем код

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

Устранение 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 ничем не отличается от обычного яваскрипт-файла, то по объявленным в нем переменным работает интеллисенс, что сокращает вероятность ошибок.

Как же именно шаблон решает вышеуказанные проблемы? Проблема имен атрибутов решилась просто — в контроллерах определяются публичные константы, а T4 генерит соответствующий хэлпер:

<br />
//константа в контроллере (для использования на server-side)<br />
public class MainController : Controller {<br />
    public const string AnimatePeriodAttribute = "animate_period";<br />
}</p>
<p>//результат в .js-файле (для использования атрибутов на стороне клиента)<br />
var Constants = {<br />
    MainController : {<br />
        Name : "Main",<br />
        AnimatePeriodAttribute : "animate_period"<br />
    }<br />
}<br />

Также генерится имя контроллера, мне в некоторых случаях оно было необходимо.

Проблема генерации ссылок также решилась просто:

<br />
//экшен в контроллере (server-side)<br />
public class MainController : Controller {<br />
    public ActionResult GetItem(int id) {<br />
    }</p>
<p>    public class GetItemModel {<br />
         public int Id { get; set; }<br />
         public string Name { get; set; }<br />
    }<br />
    public ActionResult GetItem2(GetItemModel model) {<br />
    }<br />
}</p>
<p>//результат в .js-файле<br />
var MvcActions = {<br />
	MainController : {<br />
		GetItem : function (id) {<br />
			var parameters = "";<br />
			if (id) {<br />
				if (parameters != '') { parameters = parameters + '&'; }<br />
				parameters = parameters + 'id=' + id;<br />
			}<br />
			return "/Main/GetItem?" + parameters;<br />
        	}<br />
		GetItem2 : function (Id, Name) {<br />
			var parameters = "";<br />
			if (Id) {<br />
				if (parameters != '') { parameters = parameters + '&'; }<br />
				parameters = parameters + 'id=' + Id;<br />
			}<br />
			if (Name) {<br />
				if (parameters != '') { parameters = parameters + '&'; }<br />
				parameters = parameters + 'name=' + Name;<br />
			}<br />
                	return "/Main/GetItem2?" + parameters;<br />
        	}<br />
	}<br />
}<br />

Как видно, шаблон работает как в случае параметризации экшена «простыми» типами (int, string, etc.), так и в случае параметризации более сложной Моделью (в этом случае в параметрах javascript-экшена появляются публичные свойства модели).

Использовать всё это безобразие очень просто:

<br />
$('#mydiv').load(MvcActions.MainController.GetItem(3));<br />
//полностью яваскрипт код, который обратится к url вида http://mysite.ru/Main/GetItem?id=3<br />

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

Стоит также отметить, что при переименовании экшенов или изменения имен констант вся эта «псевдо строгая типизация» слетит, поскольку имена в сгенерированном js-шаблоне изменятся, а в местах использования — нет. Для избежания таких ситуация (а точнее для успешной борьбы с их последствиями) я использую «компиляцию» яваскриптов, в процессе которой я и узнаю о подобных ошибках и получаю возможность оперативно их исправить. Экскурс в яваскрипт-компиляторы и их интеграцию с VS ожидается в рамках следующего поста :)

P.S. Собственно код шаблона можно получить по ссылке. До окончательного «полирования» кода руки не дошли, но со своими задачами он вполне справляется. Легко адаптируется для работы не только с MVC-контроллерами, но и с WebForms-контролами, или вообще произвольными классами на выбор.
Код содержит большие куски из T4MVC, и выполнен в рамках ознакомления с возможностями шаблонов T4 и EnvDTE. Возможности оказались весьма широкими :)

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

Written by Shaddix

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

Leave a Reply