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

Как же именно шаблон решает вышеуказанные проблемы? Проблема имен атрибутов решилась просто — в контроллерах определяются публичные константы, а 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. Возможности оказались весьма широкими :)

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *