Thursday, July 22, 2010

Кодогенератор для создания шаблона мета-модели

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

Предлагаемый метод не даёт на выходе 100% готовый к использованию код. Скорее, это помощник для генерации заготовки кода, которую далее всё равно придётся менять. Конечно, можно было бы написать плагин с UI для Visual Studio, но это заняло бы гораздо больше времени. :)

Мы будем использовать встроенный в студию движок T4 для генерации заготовки кода. Никаких дополнительных плагинов не нужно.

Хочется порекомендовать отличный блог Олега Сыча (en), в котором он подробнейшим образом описывает возможности этого движка, делится секретами и лучшими практиками. Для быстрого ознакомления с T4, можно прочесть статью Александра Полозова на хабре.

Итак, что нам нужно получить от кодогенератора? Linq to SQL создал для нас класс контекста, а также объектную модель данных (набор partial классов). Для каждого класса этой модели мы должны создать заготовку мета-класса и навесить на её свойства базовые атрибуты. Таким образом, то, что мы получили в предыдущем посте, будет производиться автоматически для всех классов модели. Нам нужно будет потом только править результат.

Начнём с того, что создадим новый файл в проекте с расширением *.tt. Я назвал его MetaClassesGenerator.tt. Мы должны указать путь к сборке, содержащей сгенерированные LINQ To SQL классы, а также указать пространство имён, в котором их надо искать.

Код T4 шаблона:

<#@ template language="C#v3.5" debug="true" #>
<# // Результирующий файл будет в формате txt, сгруппирован с tt файлом #>
<#@ output extension="txt" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.Linq.dll" #>
<# // Добавьте путь к своей dll #>
<#@ assembly name="...\My Documents\Visual Studio 2008\Projects\DynamicDataMvcScaffolding\DynamicDataMvcScaffolding\bin\DynamicDataMvcScaffolding.dll" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>

<#
 string ModelsNamespace = "DynamicDataMvcScaffolding.Models";

 try
 {
  // Подставьте тип своего контекста
  Type someTypeInNamespace = typeof(DynamicDataMvcScaffolding.Models.NorthwindDataContext);
  var asm = System.Reflection.Assembly.GetAssembly(someTypeInNamespace);
 
  List<Type> TableTypes = new List<Type>();
     
  foreach (Type type in asm.GetTypes())
  {
   if (type.Namespace == ModelsNamespace)
   {
    Attribute[] attrs = System.Attribute.GetCustomAttributes(type);
    if ((from a in attrs
      where a.GetType().Name == "TableAttribute"
      select type.Name).ToList().Count > 0)
    {
     TableTypes.Add(type);
    }
   }
  }
#>
/*** 
This code was generated by MetaClassesGenerator tool
http://aspnetmvcfan.blogspot.com/
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.DynamicData;

namespace <#= ModelsNamespace #>
{

<#
   foreach (Type type in TableTypes)
   {
#>

 #region Meta data for <#= type.Name #>
 
 [ScaffoldTable(true)]
 [MetadataType(typeof(<#= type.Name #>MetaModel))]
    public partial class <#= type.Name #>
    {
    }
    
    [DisplayName("<#= type.Name #>")]
    public class <#= type.Name #>MetaModel
    {
  <#
   var props = (from p in type.GetProperties() 
                        from a in System.Attribute.GetCustomAttributes(p)
                            where a.GetType().Name == "ColumnAttribute" || a.GetType().Name == "AssociationAttribute"
                            select p.Name).ToList();
            foreach (var prop in props)
            {
    PushIndent("\t\t");
    WriteLine(String.Format("[DisplayName(\"{0}\")]", prop));
    WriteLine("[ScaffoldColumn(true)]");
    WriteLine(String.Format("public object {0} {{get; set;}}", prop));
    WriteLine("");
    PopIndent();
            }
  #>
    }
    
 #endregion


<#  
   }
#>
}
<#
 }
 catch (Exception e)
 {
  WriteLine("// "+ e.Message);
 }
#>

Код во многом похож на шаблон ASP, не так ли? В конечном итоге, после сохранения tt файла, будет произведена автоматическая компиляция и под MetaClassesGenerator.tt появится файл MetaClassesGenerator.txt. Всё, что нам надо сделать — скопировать всё, что внутри и вставить в новый cs файл, в котором и будем в дальнейшем настраивать нашу мета-модель..

No comments:

Post a Comment