Цель урока. Научиться использовать Scaffolding для создания прототипа проекта. Определяем и фиксируем структуру репозитория. Простая и языковая версия класса. Тестируем использование Scaffolder-а, используем «направляющие» атрибуты. Параметры для Scaffolder-а. Создание управляющих атрибутов. Полный цикл создания и управления объекта в админке.
Scaffolding T4 для Visual Studio 2013 не применимо.
В этом и следующем уроке мы изучим то, что поможет вам в разы быстрее разрабатывать приложения. Начнем издалека. Когда я делал первый сайт, я смотрел, как можно реализовать тот или иной функционал и использовал его у себя в приложении. Потом, когда у меня появился второй проект, я начал функционал улучшать. Я выделил основные моменты и инструменты, которые были описаны в предыдущих уроках. Я начал замечать, что я делаю часто много механичной работы, например:
И так как это было поистину скучно, я часто ошибался в одном из шагов – и нужно было править банальные ошибки. И я создал сниппеты, но они решали только половину задачи, а вот модель данных, контроллер, index.cshtml, edit.cshtml – это не было решено.
И вот я прочитал статью Стивена Сандерсона «Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package» и загорелся. Скаффолдинг подходил мне идеально, но он не был написан для моего решения. И я начал изучать. В основе его стоял T4 (Text Template Transformation Toolkit), в шаблонах используется именно этот синтаксис, но для работы дошаблонной логики используется Windows PowerShell. Собственно, с PowerShell мы работаем в PackageManager Console (ух, как закручено!). Я совсем немного погружусь в Windows PowerShell и T4, только для того, чтобы создать пару скаффолдеров для работы с проектом.
Итак, что нам изначально необходимо, так это установить PowerGUI для работы с PowerShell. В VS2010 есть много редакторов для PowerShell. Но мы работаем с VS2012 и нам пока так не повезло.
Ок, установили. Переходим к установке редактора для t4 - http://t4-editor.tangible-engineering.com. Тоже пока что единственный редактор для VS2012. Ну что ж – подсветочка есть и ладно.
Далее изучим, что у нас есть. Начнем с T4. Я пользовался этой ссылкой: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/
Создадим новый проект, библиотеку классов LesssonProject.T4. И добавим туда HelloWorld.tt:
Изменим немного:
Ок, и результатом этого будет:
На самом деле .tt файл преобразуется в код, который создает конкретный класс, наследуемый от TextTransformation. Этот код запускается и генерируется файл-результат. Выглядит примерно так:
Преобразуется в:
А итогом будет файл .cs:
Изучим блоки и синтаксис задания шаблонов, который очень похож на aspx, только вместо скобок <% %> используется <# #>. Но, так как aspx мы не изучали, то:
В общем, этих знаний и знаний по Reflection вполне хватит, чтобы сгенерировать нужные нам файлы, но перейдем к проекту MvcScaffolding.
Установим T4Scaffolding:
Создадим папку CodeTemplates/Scaffolders/IRepository в LessonProject.Model в ней добавим файлы IRepository.ps1 (LessonProject.Model/CodeTemplates/Scaffolders/IRepository/IRepository.ps1):
Потом IRepositoryItemTemplate.cs.t4:
Создадим новую таблицу Notify:
UserIDint (foreignKey to User)
Messagenvarchar(140)
AddedDatedatetime
IsReadedbit
Перенесем в DbContext (LessonProjectDb.dbml) и сохраняем (ctrl-S):
В Package Manager Console пишем для проекта LessonProject.Model:
Ура! Всё работает! Просто, не правда ли? Ничего не ясно? Ок, ладно разберем IRepository.ps1 по порядку:
Это структура объявления кода скаффолдера. Особое внимание нужно обратить на
Далее мы ищем этот класс (если мы не сохранимся, то искомый класс еще не будет записан и не будет найден):
Класс найден и мы переходим к следующей части. Найти файл IRepository.cs и если его нет, то создать:
Тут как раз вызывается IRepositoryTemplate.cs.t4 при необходимости, и туда в качестве модели данных (так же, как в View) передается объект
А defaultNamespace был получен из свойства проекта
В шаблоне мы это используем (CodeTemplates/Scaffolders/IRepository/IRepositoryTemplate.cs.t4):
Ок, файл создан (или найден) и идем к следующему шагу. Если всё хорошо сгеренировалось (
Параметры:
Кстати, обратите внимание на
Т.е. нормально сформировал множественное число, а не просто прибавлением символа ‘s’, как это было бы в сниппете.
Изучим еще эти T4Scaffolding cmdlet:
Ну что? Чувствуете мощь, которая уже заменит копипастинг в ваших проектах? Но(!) Учтите, что все эти команды были реализованы тоже людьми. И, например, получение первичного ключа будет происходить только, если поле называется ID, а если оно называется PervichniyKlyuch – то, скорее всего, это не сработает. Также сильно не стоит уповать на переводы. Это скаффолдинг, т.е. черновое создание проекта, а не тончайшая настройка. Суть скаффолдинга – это создать, запустить и пойти пить чай, пока программа в автоматическом режиме сделает за вас самую противную механическую рутину.
Вернемся к шаблонам и изучим то, что такое EnvDTE.CodeType.
CodeType – это интерфейс, к которому может быть приведена информация об классе, полученная через Get-ProjectType.
Что мы знаем про этот интерфейс. Например, про свойства:
Есть еще методы, но мы их не используем.
Кстати, обратите внимание на EnvDTEExtensions.cs в T4Scaffolding (исходники его можно скачать отсюда: http://mvcscaffolding.codeplex.com/SourceControl/changeset/view/7cd57d172314), какие еще вспомогательные классы вам доступны.
Фух! Ну что, попробуем разложить всё по полочкам, раскрошить программно любой код, а потом объяснить компьютеру, как мы пишем программы, и идти гонять чаи.
Создадим новый проект: LessonProject.Scaffolding, и возьмем ту пару классов из первых уроков, с мечом и воином.
IWeapon.cs:
Bazuka.cs:
Sword.cs:
Warrior.cs:
Установим T4Scaffolding:
Создадим простейший PowerShell (/CodeTemplates/Scaffolders/Details/Details.ps1):
Заданный тип данных передаем в Details.t4 (/CodeTemplates/Scaffolders/Details/Details.cs.t4):
Выведем для Warrior.cs
Мы можем исследовать классы, использовать направляющие атрибуты и на основе этого создавать промежуточные классы, т.е. автоматизировать процессы, которые слишком рутинны для ручной работы. В то же время у нас появляется преимущество, ведь автоматически сгенерированный код содержит меньше ошибок, так как часть человеческого фактора мы убираем.
Итак, я не буду тут приводить код всех используемых мною скаффолдеров, только опишу здесь их параметры для запуска. Но прежде расскажу о ManageAttribute. Эти атрибуты присваиваются тем полям, которые мы хотим в дальшейшем использовать как маркеры для генерации определенного кода. Например, атрибут LangColumn – это атрибут, указывающий на то, что данное поле является «языковым». Тем самым мы можем сгенерировать ModelView и с учетом их тоже.
Скаффолдинг – это не панацея, но это хороший инструмент, с помощью которого можно быстро создать необходимый код. Написанные классы позволяют быстро начать управлять содержимым базы данных, и избавляют от множества ручной рутинной работы.
Действия при создании новой таблицы (объекта) будут следующие:
Всё это выполняется сразу на несколько таблиц, если это старт проекта или большой патч. У меня иногда генерировалось до 20-30 таблиц, это заняло около 5 минут, но без этого пришлось бы провозиться целый день.
Посмотрите на реализацию скаффолдингов, вы сможете больше понять внутренние особенности программы и ее структуру.
Все исходники находятся по адресу https://bitbucket.org/chernikov/lessons
Scaffolding T4 для Visual Studio 2013 не применимо.
Scaffolding. Начало.
В этом и следующем уроке мы изучим то, что поможет вам в разы быстрее разрабатывать приложения. Начнем издалека. Когда я делал первый сайт, я смотрел, как можно реализовать тот или иной функционал и использовал его у себя в приложении. Потом, когда у меня появился второй проект, я начал функционал улучшать. Я выделил основные моменты и инструменты, которые были описаны в предыдущих уроках. Я начал замечать, что я делаю часто много механичной работы, например:
- создать в БД новую таблицу
- прокинуть ее в класс DbContext
- добавить объявление в интерфейс репозитария
- добавить реализацию в SqlRepository
- добавить partial-часть класса в папке Proxy
- добавить модель данных
- объявить mapping
- создать контроллер в админке
- сделать типичные view для просмотра и редактирования
И так как это было поистину скучно, я часто ошибался в одном из шагов – и нужно было править банальные ошибки. И я создал сниппеты, но они решали только половину задачи, а вот модель данных, контроллер, index.cshtml, edit.cshtml – это не было решено.
И вот я прочитал статью Стивена Сандерсона «Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package» и загорелся. Скаффолдинг подходил мне идеально, но он не был написан для моего решения. И я начал изучать. В основе его стоял T4 (Text Template Transformation Toolkit), в шаблонах используется именно этот синтаксис, но для работы дошаблонной логики используется Windows PowerShell. Собственно, с PowerShell мы работаем в PackageManager Console (ух, как закручено!). Я совсем немного погружусь в Windows PowerShell и T4, только для того, чтобы создать пару скаффолдеров для работы с проектом.
Итак, что нам изначально необходимо, так это установить PowerGUI для работы с PowerShell. В VS2010 есть много редакторов для PowerShell. Но мы работаем с VS2012 и нам пока так не повезло.
Ок, установили. Переходим к установке редактора для t4 - http://t4-editor.tangible-engineering.com. Тоже пока что единственный редактор для VS2012. Ну что ж – подсветочка есть и ладно.
T4
Далее изучим, что у нас есть. Начнем с T4. Я пользовался этой ссылкой: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/
Создадим новый проект, библиотеку классов LesssonProject.T4. И добавим туда HelloWorld.tt:
Изменим немного:
<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core" #>
<#@ Assembly Name="System.Windows.Forms" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#
var greeting = "Hello, World!";
#>
// This is the output code from your template
// you only get syntax-highlighting here - not intellisense
namespace MyNameSpace
{
class MyGeneratedClass
{
static void main (string[] args)
{
System.Console.WriteLine("<#= greeting #>");
}
}
}
<#+
// Insert any template procedures here
void foo(){}
#>
Ок, и результатом этого будет:
// This is the output code from your template
// you only get syntax-highlighting here - not intellisense
namespace MyNameSpace
{
class MyGeneratedClass
{
static void main (string[] args)
{
System.Console.WriteLine("Hello, World!");
}
}
}
На самом деле .tt файл преобразуется в код, который создает конкретный класс, наследуемый от TextTransformation. Этот код запускается и генерируется файл-результат. Выглядит примерно так:
<#@ template language="C#" #>
Hello World!
Преобразуется в:
public class GeneratedTextTransform : Microsoft.VisualStudio.TextTemplating.TextTransformation
{
public override string TransformText()
{
this.Write("Hello, World!");
return this.GenerationEnvironment.ToString();
}
}
А итогом будет файл .cs:
Hello World!
Изучим блоки и синтаксис задания шаблонов, который очень похож на aspx, только вместо скобок <% %> используется <# #>. Но, так как aspx мы не изучали, то:
- Текстовый блок – это любой не программный текст в тексте шаблона (извините за тафтологию):
<#@ template language="C#" #> Hello World!
- Блок операторов – это любой блок, заключенный в <# #>. Всё что внутри этого, это языковая конструкция, которая задает логику построения текста:
<# var greeting = "Hello, World!"; #>
- Блок выражения – это блок, заключенный в <#= #>. Всё, что внутри этого блока, будет приведено к строке и добавлено в текст шаблона:
System.Console.WriteLine("<#= greeting #>");
- Блок функций – это блок, заключенный в
<#+ #>
. Все функции, объявленные в этом блоке, могут быть вызваны в шаблоне. Кроме того, сами функции могут содержать текст шаблона. - Директива
<#@ template #>
– позволяет задать характеристики класса преобразования из шаблона:
<#@ template language=”C#”>
– задает язык класса.<#@ template debug=”true”>
– позволяет отладить генерацию шаблона.<#@ template inherits=”MyTextTransformation”>
– указывает, какой класс должен быть использован в качестве базового для класса генерации в процедуре генерации файла.
- Директива
<#@ output #>
– задает расширение для генерируемого файла:
<#@ output extension=".cs" #>
- Директива
<#@ import #>
– добавляет использование в процедуре исполнения заданных namespace. То же самое что и using добавить (но не в результат, а при выполнении генерации текста):
<#@ import namespace="System.Collections" #>
- Директива
<#@ assembly #>
– добавляет объявление сборки. То же самое, что и в VisualStudio добавить Reference:
<#@ Assembly Name="System.Core" #>
- Директива
<#@ include #>
– добавляет некий другой шаблон в месте объявления. Это как Html.Partial():
<#@ include file="Included.tt" #>
- Директива
<#@ parameter #>
— добавляет параметр при формировании шаблона. Но передача его происходит настолько сложно, что пример я приводить не буду. Ссылка - По остальному – смотрите по ссылке.
В общем, этих знаний и знаний по Reflection вполне хватит, чтобы сгенерировать нужные нам файлы, но перейдем к проекту MvcScaffolding.
MVCScaffolding
Установим T4Scaffolding:
PM> Install-Package T4Scaffolding
Создадим папку CodeTemplates/Scaffolders/IRepository в LessonProject.Model в ней добавим файлы IRepository.ps1 (LessonProject.Model/CodeTemplates/Scaffolders/IRepository/IRepository.ps1):
[T4Scaffolding.Scaffolder(Description = "Create IRepository interface")][CmdletBinding()]
param(
[parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$ModelType,
[string]$Project,
[string]$CodeLanguage,
[string[]]$TemplateFolders,
[switch]$Force = $false
)
$foundModelType = Get-ProjectType $ModelType -Project $Project -BlockUi
if (!$foundModelType) { return }
# Find the IRepository interface, or create it via a template if not already present
$foundIRepositoryType = Get-ProjectType IRepository -Project $Project -AllowMultiple
if(!$foundIRepositoryType)
{
#Create IRepository
$outputPath = "IRepository"
$defaultNamespace = (Get-Project $Project).Properties.Item("DefaultNamespace").Value
Add-ProjectItemViaTemplate $outputPath -Template IRepositoryTemplate `
-Model @{ Namespace = $defaultNamespace } `
-SuccessMessage "Added IRepository at {0}" `
-TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
$foundIRepositoryType = Get-ProjectType IRepository -Project $Project
}
# Add a new property on the DbContext class
if ($foundIRepositoryType) {
$propertyName = $foundModelType.Name
$propertyNames = Get-PluralizedWord $propertyName
# This *is* a DbContext, so we can freely add a new property if there isn't already one for this model
Add-ClassMemberViaTemplate -Name $propertyName -CodeClass $foundIRepositoryType -Template IRepositoryItemTemplate -Model @{
EntityType = $foundModelType;
EntityTypeNamePluralized = $propertyNames;
} -SuccessMessage "Added '$propertyName' to interface '$($foundIRepositoryType.FullName)'" -TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage
}
return @{
DbContextType = $foundDbContextType
}
Потом IRepositoryItemTemplate.cs.t4:
<#@ Template Language="C#" HostSpecific="True" Inherits="DynamicTransform" #>
#region <#= ((EnvDTE.CodeType)Model.EntityType).Name #>
IQueryable<<#= ((EnvDTE.CodeType)Model.EntityType).Name #>> <#= Model.EntityTypeNamePluralized #> { get; }
bool Create<#= ((EnvDTE.CodeType)Model.EntityType).Name #>(<#= ((EnvDTE.CodeType)Model.EntityType).Name #> instance);
bool Update<#= ((EnvDTE.CodeType)Model.EntityType).Name #>(<#= ((EnvDTE.CodeType)Model.EntityType).Name #> instance);
bool Remove<#=((EnvDTE.CodeType)Model.EntityType).Name #>(int id<#= ((EnvDTE.CodeType)Model.EntityType).Name #>);
#endregion
И IRepositoryTemplate.cs.t4:
<#@ Template Language="C#" HostSpecific="True" Inherits="DynamicTransform" #>
<#@ Output Extension="cs" #>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace <#= Model.Namespace #>
{
public interface IRepository
{
IQueryable<T> GetTable<T>() where T : class;
}
}
Создадим новую таблицу Notify:
Name | DataType |
UserIDint (foreignKey to User)
Messagenvarchar(140)
AddedDatedatetime
IsReadedbit
Перенесем в DbContext (LessonProjectDb.dbml) и сохраняем (ctrl-S):
В Package Manager Console пишем для проекта LessonProject.Model:
PM> Scaffold IRepository Notify
Added 'Notify' to interface 'LessonProject.Model.IRepository'
Ура! Всё работает! Просто, не правда ли? Ничего не ясно? Ок, ладно разберем IRepository.ps1 по порядку:
[T4Scaffolding.Scaffolder(Description = "Create IRepository interface")][CmdletBinding()]
param(
[parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$ModelType,
[string]$Project,
[string]$CodeLanguage,
[string[]]$TemplateFolders,
[switch]$Force = $false
)
Это структура объявления кода скаффолдера. Особое внимание нужно обратить на
$ModelType
– это имя класса, именно его мы и передаем в команде Scaffold IRepository Notify
. Остальные параметры идут или по умолчанию, как Force, или по умолчанию известны, как Project, CodeLanguage.Далее мы ищем этот класс (если мы не сохранимся, то искомый класс еще не будет записан и не будет найден):
$foundModelType = Get-ProjectType $ModelType -Project $Project -BlockUi
if (!$foundModelType) { return }
Класс найден и мы переходим к следующей части. Найти файл IRepository.cs и если его нет, то создать:
# Find the IRepository interface, or create it via a template if not already present
$foundIRepositoryType = Get-ProjectType IRepository -Project $Project -AllowMultiple
if(!$foundIRepositoryType)
{
#Create IRepository
$outputPath = "IRepository"
$defaultNamespace = (Get-Project $Project).Properties.Item("DefaultNamespace").Value
Add-ProjectItemViaTemplate $outputPath -Template IRepositoryTemplate `
-Model @{ Namespace = $defaultNamespace } `
-SuccessMessage "Added IRepository at {0}" `
-TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
$foundIRepositoryType = Get-ProjectType IRepository -Project $Project
}
Тут как раз вызывается IRepositoryTemplate.cs.t4 при необходимости, и туда в качестве модели данных (так же, как в View) передается объект
-Model @{ Namespace = $defaultNamespace } `
А defaultNamespace был получен из свойства проекта
Get-Project $Project).Properties.Item("DefaultNamespace").Value
В шаблоне мы это используем (CodeTemplates/Scaffolders/IRepository/IRepositoryTemplate.cs.t4):
namespace <#= Model.Namespace #>
Ок, файл создан (или найден) и идем к следующему шагу. Если всё хорошо сгеренировалось (
$foundIRepositoryType
), то добавим в этот класс несколько свойств по шаблону IRepositoryItemTemplate
с параметрами:# Add a new property on the DbContext class
if ($foundIRepositoryType) {
$propertyName = $foundModelType.Name
$propertyNames = Get-PluralizedWord $propertyName
# This *is* a DbContext, so we can freely add a new property if there isn't already one for this model
Add-ClassMemberViaTemplate -Name $propertyName -CodeClass $foundIRepositoryType -Template IRepositoryItemTemplate -Model @{
EntityType = $foundModelType;
EntityTypeNamePluralized = $propertyNames;
} -SuccessMessage "Added '$propertyName' to interface '$($foundIRepositoryType.FullName)'" -TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage
}
Параметры:
-Model @{
EntityType = $foundModelType;
EntityTypeNamePluralized = $propertyNames;
}
Кстати, обратите внимание на
Get-PluralizedWord
и какую роль оно сыграло в созданном шаблоне: IQueryable<Notify> Notifies { get; }
Т.е. нормально сформировал множественное число, а не просто прибавлением символа ‘s’, как это было бы в сниппете.
Изучим еще эти T4Scaffolding cmdlet:
- Add-ClassMember – добавляет кусочек кода в существующий класс:
$class = Get-ProjectType HomeController Add-ClassMember $class "public string MyNewStringField;"
- Add-ClassMemberViaTemplate – добавляем блок кода через шаблон T4:
$class = Get-ProjectType HomeController Add-ClassMemberViaTemplate -CodeClass $class -Template "YourTemplateName" -Model @{ SomeParam = "SomeValue"; AnotherParam = $false } -TemplateFolders $TemplateFolders
- Add-ProjectItemViaTemplate – добавляем файл (project item) посредством шаблона:
Add-ProjectItemViaTemplate -OutputPath "Some\Folder\MyFile" -Template "YourTemplateName" -Model @{ SomeParam = "SomeValue"; AnotherParam = $false } -TemplateFolders $TemplateFolders
- Get-PluralizedWord / Get-SingularizedWord – получаем множественное/единственное число слова:
$result = Get-PluralizedWord Person # Sets $result to "People" $result = Get-SingularizedWord People # Sets $result to "Person"
- Get-PrimaryKey – получить первичный ключ из модели данных:
$pk = Get-PrimaryKey StockItem
- Get-ProjectFolder – получить класс проектной папки
$folder = Get-ProjectFolder "Views\Shared" Write-Host "The shared views folder contains $($folder.Count) items"
- Get-ProjectItem – получить файл
$file = Get-ProjectItem "Controllers\HomeController.cs" $file.Open() $file.Activate()
- Get-ProjectLanguage – для C# проектов – возвращает cs, для VB проектов возвращает VB
$defaultProjectLanguage = Get-ProjectLanguage $otherProjectLanguage = Get-ProjectLanguage -Project SomeOtherProjectName
- Get-ProjectType – получит модель класса (EnvDTE.CodeType)
$class = Get-ProjectType HomeController Add-ClassMember $class "public string MyNewStringField;"
- Get-RelatedEntities – находит все связанные один-ко-многим объекты класса
Get-RelatedEntities Product
- Set-IsCheckedOut – это для работы с source-control. Т.е. если надо checkOut файл какой-то, то это можно выполнить этой командой.
Set-IsCheckedOut "Controllers\HomeController.cs"
Ну что? Чувствуете мощь, которая уже заменит копипастинг в ваших проектах? Но(!) Учтите, что все эти команды были реализованы тоже людьми. И, например, получение первичного ключа будет происходить только, если поле называется ID, а если оно называется PervichniyKlyuch – то, скорее всего, это не сработает. Также сильно не стоит уповать на переводы. Это скаффолдинг, т.е. черновое создание проекта, а не тончайшая настройка. Суть скаффолдинга – это создать, запустить и пойти пить чай, пока программа в автоматическом режиме сделает за вас самую противную механическую рутину.
Снова шаблоны, EnvDTE.CodeType
Вернемся к шаблонам и изучим то, что такое EnvDTE.CodeType.
CodeType – это интерфейс, к которому может быть приведена информация об классе, полученная через Get-ProjectType.
Что мы знаем про этот интерфейс. Например, про свойства:
- Access – какой это тип, публичный, приватный и т.д..
- Attributes – коллекции атрибутов, связанные с этим типом.
- Bases – коллекции классов, из которых этот элемент происходит.
- Children – возвращает коллекцию объектов, содержащихся в этом CodeType.
- Comment – комментарий относящийся к классу. (Можно сделать автоматическую документацию).
- DerivedTypes – Наследуемые типы. Это свойство не поддерживается в Visual C#.
- DocComment – Документальный комментарий или атрибут, который выполняет эту роль.
- DTE – возвращает главный объект расширения
- EndPoint – строка в файле, с которой начинается описание этого класса.
- FullName – полное имя, типа System.Int32
- InfoLocation – возвращает возможности объектной модели.
- IsCodeType – можно ли CodeType получить из этого объекта.
- IsDerivedFrom – возвращает CodeType базового объект.
- Kind – свойства типа объекта.
- Language – на каком языке это написано.
- Members – члены объекта. Вот это очень полезная функция.
- Name – имя объекта.
- Namespace – пространство имен объекта.
- Parent – непосредственный родитель объекта.
- ProjectItem – файл объекта.
- StartPoint – строка, в которой началось описание объекта.
Есть еще методы, но мы их не используем.
Кстати, обратите внимание на EnvDTEExtensions.cs в T4Scaffolding (исходники его можно скачать отсюда: http://mvcscaffolding.codeplex.com/SourceControl/changeset/view/7cd57d172314), какие еще вспомогательные классы вам доступны.
Фух! Ну что, попробуем разложить всё по полочкам, раскрошить программно любой код, а потом объяснить компьютеру, как мы пишем программы, и идти гонять чаи.
Создадим новый проект: LessonProject.Scaffolding, и возьмем ту пару классов из первых уроков, с мечом и воином.
IWeapon.cs:
public interface IWeapon
{
void Kill();
}
Bazuka.cs:
public class Bazuka : IWeapon
{
public void Kill()
{
Console.WriteLine("BIG BADABUM!");
}
}
Sword.cs:
public class Sword : IWeapon
{
public void Kill()
{
Console.WriteLine("Chuk-chuck");
}
}
Warrior.cs:
/// <summary>
/// This is LEGENDARY WARRIOR!
/// </summary>
public class Warrior
{
readonly IWeapon Weapon;
public Warrior(IWeapon weapon)
{
this.Weapon = weapon;
}
public void Kill()
{
Weapon.Kill();
}
}
Установим T4Scaffolding:
Install-Package T4Scaffolding
Создадим простейший PowerShell (/CodeTemplates/Scaffolders/Details/Details.ps1):
[T4Scaffolding.Scaffolder(Description = "Print Details for class")][CmdletBinding()]
param(
[parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$ModelType,
[string]$Project,
[string]$CodeLanguage,
[string[]]$TemplateFolders,
[switch]$Force = $false
)
$foundModelType = Get-ProjectType $ModelType -Project $Project -BlockUi
if (!$foundModelType) { return }
$outputPath = Join-Path "Details" $ModelType
Add-ProjectItemViaTemplate $outputPath -Template Details `
-Model @{ ModelType = $foundModelType } `
-SuccessMessage "Yippee-ki-yay"`
-TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
Заданный тип данных передаем в Details.t4 (/CodeTemplates/Scaffolders/Details/Details.cs.t4):
<#@ template language="C#" HostSpecific="True" Inherits="DynamicTransform" debug="true" #>
<#@ assembly name="System.Data.Entity" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="EnvDTE" #>
<#@ Output Extension="txt" #>
<#
var modelType = (EnvDTE.CodeType)Model.ModelType;
#>
FullName : <#= modelType.FullName #>
Name : <#= modelType.Kind #> <#= modelType.Name #>
Access : <#= modelType.Access #>
Attributes :
<# foreach(var codeElement in modelType.Attributes) {
var attr = (EnvDTE.CodeAttribute)codeElement;
#>
<#= attr.Name #>
<# } #>
Bases :
<# foreach(var codeElement in modelType.Bases) {
var @base = (EnvDTE.CodeType)codeElement;
#>
<#= @base.Name #>
<# } #>
Comment : <#= modelType.Comment #>
DocComment : <#= modelType.DocComment #>
StartPoint : Line: <#= ((EnvDTE.TextPoint)modelType.StartPoint).Line #>
EndPoint : Line : <#= ((EnvDTE.TextPoint)modelType.EndPoint).Line #>
Members :
<# foreach(var codeElement in modelType.Members) {
var member = (EnvDTE.CodeElement)codeElement;
#>
<#= member.Kind #> <#= member.Name #>
<# } #>
Выведем для Warrior.cs
PM> Scaffold Details Warrior -Force:$true
Yippee-ki-yay
- Имя\ полное имя
- Тип модели
- Доступ
- Атрибуты модели
- Базовый класс
- Комментарий
- Комментарий для документации
- Строка файла, с которой началось объявление, и строка, на которой закончилось объявление
- Имена членов класса с типом класса
Мы можем исследовать классы, использовать направляющие атрибуты и на основе этого создавать промежуточные классы, т.е. автоматизировать процессы, которые слишком рутинны для ручной работы. В то же время у нас появляется преимущество, ведь автоматически сгенерированный код содержит меньше ошибок, так как часть человеческого фактора мы убираем.
Описание скаффолдеров
Итак, я не буду тут приводить код всех используемых мною скаффолдеров, только опишу здесь их параметры для запуска. Но прежде расскажу о ManageAttribute. Эти атрибуты присваиваются тем полям, которые мы хотим в дальшейшем использовать как маркеры для генерации определенного кода. Например, атрибут LangColumn – это атрибут, указывающий на то, что данное поле является «языковым». Тем самым мы можем сгенерировать ModelView и с учетом их тоже.
- IRepository (Model). Мы уже с ним знакомы, он создает интерфейс IRepository и вносит CRUD-методы для заданного типа:
Scaffold IRepository ModelName
- Proxy (Model). Создает Proxy partial class. Если задан параметр Lang:$true, то скаффолдер ищет языковую модель данных ModelName+”Lang” и добавляет языковые поля в partial class.
Scaffold Proxy ModelName -Lang:$true
- SqlRepository (Model). Создает реализацию CRUD-методов класса ModelName. Также имеет параметр Lang для создания приватного метода, работающего с языковыми полями
Scaffold SqlRepository ModelName -Lang:$true
- ProviderRepository (Model). Запускает три вышеперечисленных скаффолдинга за один раз
Scaffold ProviderRepository ModelName -Lang:$true
- Model (Web). Создает модель ModelNameView в Models/ViewModels и создает обработчик Automapper в Mappers/MappersCollection.cs. После этого во View-классе необходимо прописать управляющие атрибуты для создания контроллера и Index/Edit view:
- ShowIndex – это поле будет отображено в таблице Index
- PrimaryField – поле ID
- CheckBox – для этого поля будет создан элемент ввода CheckBox
- DropDownField – для этого поля будет создан элемент ввода DropDownField
- HiddenField – скрытое поле
- HtmlTextField – элемент ввода textarea, помеченный классом htmltext
- RadioField – поле c радио-кнопками (на практике практически не использовалось)
- TextAreaField – элемент ввода textarea
- TextBoxField – обычное текстовое поле ввода
Scaffold Model ModelName
- SelectReference (Web). Создает во view-классе зависимость один-ко-многим, т.е. элемент выбора. Например, если создается город (City) с принадлежностью к штату (State), то при создании города указывается выпадающий список штатов, задающий значение StateID. Для этого необходимо использовать SelectReference, который добавит необходимый код к CityView:
Scaffold SelectReference City State
- Controller (Web). Создает контроллер для данного ModelName типа. Дополнительно генерирует и Index\Edit View. Параметрами являются:
- Area (по умолчанию – нет), создает контроллер в определенном Area
- Paging (по умолчанию – false), использует или не использует постраничный вывод
- Lang (по умолчанию – false), генерирует код с использованием языковых настроек
Scaffold Controller ModelName –Area:Admin –Paging:$true –Lang:$true
- IndexView\EditView (Web). Создает просмотр списка или редактирование объекта. Дополнительные параметры — те же, что и у Controller:
- Area (по умолчанию – нет), создает контроллер в определенном Area
- Paging (по умолчанию – false), использует или не использует постраничный вывод
- Lang (по умолчанию – false), генерирует код с использованием языковых настроек
Scaffold IndexView ModelName –Area:Admin –Paging:$true –Lang:$true Scaffold EditView ModelName –Area:Admin –Paging:$true –Lang:$true
Итог
Скаффолдинг – это не панацея, но это хороший инструмент, с помощью которого можно быстро создать необходимый код. Написанные классы позволяют быстро начать управлять содержимым базы данных, и избавляют от множества ручной рутинной работы.
Действия при создании новой таблицы (объекта) будут следующие:
- Описать таблицу(ы) с полями в БД
- Перенести ее в DBContext.dbml
- Запустить ProviderRepository для необходимых таблиц, убрать лишние методы
- Запустить Model для необходимых таблиц
- Прописать управляющие атрибуты во view-классах, убрать лишние поля
- Создать контроллеры в админке
- Допилить напильником сложные поля (например, загрузку файлов)
Всё это выполняется сразу на несколько таблиц, если это старт проекта или большой патч. У меня иногда генерировалось до 20-30 таблиц, это заняло около 5 минут, но без этого пришлось бы провозиться целый день.
Посмотрите на реализацию скаффолдингов, вы сможете больше понять внутренние особенности программы и ее структуру.
Все исходники находятся по адресу https://bitbucket.org/chernikov/lessons