воскресенье, 27 мая 2012 г.

Генерация JS из C# с помощью T4

В Ajax-приложениях данные передаются между клиентской и серверной стороной. Естественно, что структуру этих данных необходимо знать и на клиенте, и на сервере. Обычно это означает ведение одних и тех же классов, интерфейсов и перечислений и в виде JS-файлов, и в виде C#-файлов. Это лишняя работа, неудобно и вообще опасно.

В одном месте поменял, в другом забыл – получил баг. У меня был такой опыт: изменил перечисление на сервере, на клиенте забыл. В итоге логика работы кнопок иногда стала подглючивать, только при определенной комбинации фильтров и т.д. Ошибку обнаружили тестировщики,  через пару дней проверок, к тому времени я конечно уже давно забыл, что я менял перечисление и зачем я это делал. В итоге, чтобы локализовать ошибку, мне пришлось потратить больше часа времени...

Чтобы избавиться от такого рода ошибок и от лишней работы, я использую T4-преобразования, и генерирую JS-файлы на основе C#. В этой статье я расскажу, как такие преобразования устроены и работают. В конце статьи выложен код итогового T4-преобразования, которое можно скачать и использовать в ваших собственных проектах без изменений. Итак, поехали!

Code Model

Code Model – это функциональность, предоставляемая Visual Studio, позволяющая просматривать (и даже изменять) описания классов, методов, свойств и полей во всем вашем проекте. Похоже на Reflection, но намного удобнее. Подробнее о Code Model можно прочитать на MSDN.


Замечание: Code Model работает только в среде Visual Studio, поэтому использующие этот функционал T4-преобразования нельзя запускать за пределами VS, например в Continious Integration-билдах.

Code Model позволяет перебирать все элементы кода, и у каждого из этих элементов есть коллекция Children, которую тоже можно перебрать, и т.д.
Простейший пример использования Code Model выглядит примерно так:

foreach (EnvDTE.CodeElement element in projectItem.FileCodeModel.CodeElements)
{
    if (element.Kind == EnvDTE.vsCMElement.vsCMElementNamespace)
    {
        foreach (EnvDTE.CodeElement innerElement in element.Children)
        {
            switch (innerElement.Kind)
            {
                case EnvDTE.vsCMElement.vsCMElementClass:
                    Debug.WriteLine("Class: " + innerElement.Name);
                    break;
                case EnvDTE.vsCMElement.vsCMElementEnum:
                    Debug.WriteLine("Enum: " + innerElement.Name);
                    break;
                case EnvDTE.vsCMElement.vsCMElementInterface:
                    Debug.WriteLine("Interface: " + innerElement.Name);
                    break;
            }
        }
    }
}

Соответственно, если взглянуть дальше вглубь классов/перечислений/интерфейсов, сгенерировать их описания для JavaScript довольно легко.

Кстати, очень хороший пример с генерацией SQL-таблиц на основе C#-перечислений с использованием T4 и Code Model можно найти у Олега Сыча. У Олега в блоге вообще очень много про T4, рекомендую почитать.

ООП и ASP.Net Ajax

Для реализации ООП в JavaScript я использую в своих проектах библиотеку ASP.Net Ajax. К слову, эта библиотека (версии 3.5) уже присутствует в составе SharePoint, так что SharePoint-разработчикам ничего дополнительно подключать не потребуется.

Как известно, JavaScript не является объектно-ориентированным языком, поэтому ООП-объекты JavaScript выглядят порой довольно неказисто. Например, чтобы объявить перечисление в ASP.Net Ajax, при этом используя inline-документацию, нужно написать довольно много кода. Реальный пример из рабочего проекта:

DW.TasksDashboard.DashboardFilter = function() {
    /// <summary>Предустановленные фильтры для Центра задач</summary>
    /// <field name='AllTasks' type='Number' integer='true' static='true'>Все задачи (без фильтрации)</field>
    /// <field name='TasksAssignedToMe' type='Number' integer='true' static='true'>Задачи, назначенные на текущего пользователя</field>
    /// <field name='TasksCreatedByMe' type='Number' integer='true' static='true'>Задачи, созданные текущим пользователем</field>
    /// <field name='ManagerTasks' type='Number' integer='true' static='true'>Задачи, созданные руководителем текущего пользователя и назначенные на текущего пользователя</field>
    /// <field name='DepartmentTasks' type='Number' integer='true' static='true'>Задачи, назначенные на членов отдела текущего пользователя (включая его самого)</field>
    /// <field name='DeputyTasks' type='Number' integer='true' static='true'>Задачи, назначенные на пользователей, которых замещает текущий пользователь</field>
    /// <field name='SecretaryTasks' type='Number' integer='true' static='true'>Задачи, назначенные на пользователей, для которых текущий пользователь - секретарь</field>}

DW.TasksDashboard.DashboardFilter.prototype = {
    AllTasks: 1,
    TasksAssignedToMe: 2,
    TasksCreatedByMe: 3,
    ManagerTasks: 4,
    DepartmentTasks: 5,
    DeputyTasks: 6,
    SecretaryTasks: 7
}
DW.TasksDashboard.DashboardFilter.registerEnum('DW.TasksDashboard.DashboardFilter');

Как описывать другие ООП-объекты с помощью ASP.Net Ajax (классы, интерфейсы, события и т.д.), вы можете найти в документации по этой библиотеке, например в статье Extending JavaScript with ASP.NET AJAX. Имея это знание, написать итоговый код несложно. Взамен, вы можете использовать t4-файл, который написал я. Просто добавьте его в проект, и измените пути к файлам, которые необходимо обрабатывать.

Заключение

Принцип DRY (Do not repeat yourself) – это один из основополагающих принципов в программировании. Его соблюдение дает множество преимуществ. И даже если без повторения одного и того же кода не обойтись, как это имеет место быть в случае Ajax-приложений с общей моделью данных, T4-шаблоны могут помочь в этой ситуации, и сгенерировать нужный код автоматически.

К слову, генерация JS-кода на основе C# также является паттерном, одним из т.н. Ajax-паттернов, т.е. специально заточенных под Ajax-приложения. Этот паттерн называется Server-Side Code Generation.

Другим способом генерации JS на основе C#, более общим, является использование Script# – этот проект позволяет компилировать С#-код в JavaScript, и активно используется даже во внутренней разработке в компании Microsoft. В частности, судя по всему, SharePoint Client Object Model тоже написан на Script#.

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

  1. спасибо за статью, полезно
    буду применять, сам страдаю от того, что приходится дважды определять функциональность как на стороне сервера, так и на клиенте
    что касается asp.net ajax - мне лично нравится та модель, которая там реализована и применительно к объявлениям классов, неймспейсов, и валидации параметров, объявления делегатов и прочее...и не понимаю подхода, когда в приложениях asp.net эта библиотека полностью игнорируется (начиная от sys.application.add_load handler) и вместо нее используется лес из всякого рода надстроек и плагинов jQuery, но это тема отдельной дискуссии:)

    ОтветитьУдалить
    Ответы
    1. согласен! кстати, мне даже ASP.Net AJAX Templates нравятся немного больше, чем KnockOutJs - хотя тоже не идеальны.

      и я за собой заметил, что вообще стараюсь избегать jQuery, тем более что она по умолчанию в SharePoint'e не подключена, в отличие опять же от ASP.Net Ajax... ведь если её вдруг несколько разных солюшенов захотят подключить на одну страницу, будут проблемы.

      в конце концов, есть ведь еще HTML5, который уже сейчас дублирует большую часть фундаментального функционала jQuery...

      Удалить

Внимание! Реклама и прочий спам будут беспощадно удаляться.