- PVSM.RU - https://www.pvsm.ru -
Цель урока. Научиться использовать Scaffolding для создания прототипа проекта. Определяем и фиксируем структуру репозитория. Простая и языковая версия класса. Тестируем использование Scaffolder-а, используем «направляющие» атрибуты. Параметры для Scaffolder-а. Создание управляющих атрибутов. Полный цикл создания и управления объекта в админке.
В этом и следующем уроке мы изучим то, что поможет вам в разы быстрее разрабатывать приложения. Начнем издалека. Когда я делал первый сайт, я смотрел, как можно реализовать тот или иной функционал и использовал его у себя в приложении. Потом, когда у меня появился второй проект, я начал функционал улучшать. Я выделил основные моменты и инструменты, которые были описаны в предыдущих уроках. Я начал замечать, что я делаю часто много механичной работы, например:
И так как это было поистину скучно, я часто ошибался в одном из шагов – и нужно было править банальные ошибки. И я создал сниппеты, но они решали только половину задачи, а вот модель данных, контроллер, index.cshtml, edit.cshtml – это не было решено.
И вот я прочитал статью Стивена Сандерсона «Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package [1]» и загорелся. Скаффолдинг подходил мне идеально, но он не был написан для моего решения. И я начал изучать. В основе его стоял T4 (Text Template Transformation Toolkit [2]), в шаблонах используется именно этот синтаксис, но для работы дошаблонной логики используется Windows PowerShell [3]. Собственно, с PowerShell мы работаем в PackageManager Console (ух, как закручено!). Я совсем немного погружусь в Windows PowerShell и T4, только для того, чтобы создать пару скаффолдеров для работы с проектом.
Итак, что нам изначально необходимо, так это установить PowerGUI для работы с PowerShell. В VS2010 есть много редакторов для PowerShell. Но мы работаем с VS2012 и нам пока так не повезло.
Ок, установили. Переходим к установке редактора для t4 - http://t4-editor.tangible-engineering.com [4]. Тоже пока что единственный редактор для VS2012. Ну что ж – подсветочка есть и ладно.
Далее изучим, что у нас есть. Начнем с T4. Я пользовался этой ссылкой: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/ [2]
Создадим новый проект, библиотеку классов 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 [5].Partial():
<#@ include file="Included.tt" #>
<#@ parameter #>
— добавляет параметр при формировании шаблона. Но передача его происходит настолько сложно, что пример я приводить не буду. Ссылка [6]В общем, этих знаний и знаний по Reflection вполне хватит, чтобы сгенерировать нужные нам файлы, но перейдем к проекту 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:
$class = Get-ProjectType HomeController
Add-ClassMember $class "public string MyNewStringField;"
$class = Get-ProjectType HomeController
Add-ClassMemberViaTemplate -CodeClass $class -Template "YourTemplateName" -Model @{ SomeParam = "SomeValue"; AnotherParam = $false } -TemplateFolders $TemplateFolders
Add-ProjectItemViaTemplate -OutputPath "SomeFolderMyFile" -Template "YourTemplateName" -Model @{ SomeParam = "SomeValue"; AnotherParam = $false } -TemplateFolders $TemplateFolders
$result = Get-PluralizedWord Person # Sets $result to "People"
$result = Get-SingularizedWord People # Sets $result to "Person"
$pk = Get-PrimaryKey StockItem
$folder = Get-ProjectFolder "ViewsShared"
Write-Host "The shared views folder contains $($folder.Count) items"
$file = Get-ProjectItem "ControllersHomeController.cs"
$file.Open()
$file.Activate()
$defaultProjectLanguage = Get-ProjectLanguage
$otherProjectLanguage = Get-ProjectLanguage -Project SomeOtherProjectName
$class = Get-ProjectType HomeController
Add-ClassMember $class "public string MyNewStringField;"
Get-RelatedEntities Product
Set-IsCheckedOut "ControllersHomeController.cs"
Ну что? Чувствуете мощь, которая уже заменит копипастинг в ваших проектах? Но(!) Учтите, что все эти команды были реализованы тоже людьми. И, например, получение первичного ключа будет происходить только, если поле называется ID, а если оно называется PervichniyKlyuch – то, скорее всего, это не сработает. Также сильно не стоит уповать на переводы. Это скаффолдинг, т.е. черновое создание проекта, а не тончайшая настройка. Суть скаффолдинга – это создать, запустить и пойти пить чай, пока программа в автоматическом режиме сделает за вас самую противную механическую рутину.
Вернемся к шаблонам и изучим то, что такое EnvDTE.CodeType.
CodeType – это интерфейс, к которому может быть приведена информация об классе, полученная через Get-ProjectType.
Что мы знаем про этот интерфейс. Например, про свойства:
Есть еще методы, но мы их не используем.
Кстати, обратите внимание на EnvDTEExtensions.cs в T4Scaffolding (исходники его можно скачать отсюда: http://mvcscaffolding.codeplex.com/SourceControl/changeset/view/7cd57d172314 [7]), какие еще вспомогательные классы вам доступны.
Фух! Ну что, попробуем разложить всё по полочкам, раскрошить программно любой код, а потом объяснить компьютеру, как мы пишем программы, и идти гонять чаи.
Создадим новый проект: 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 и с учетом их тоже.
Scaffold IRepository ModelName
Scaffold Proxy ModelName -Lang:$true
Scaffold SqlRepository ModelName -Lang:$true
Scaffold ProviderRepository ModelName -Lang:$true
Scaffold Model ModelName
Scaffold SelectReference City State
Scaffold Controller ModelName –Area:Admin –Paging:$true –Lang:$true
Scaffold IndexView ModelName –Area:Admin –Paging:$true –Lang:$true
Scaffold EditView ModelName –Area:Admin –Paging:$true –Lang:$true
Скаффолдинг – это не панацея, но это хороший инструмент, с помощью которого можно быстро создать необходимый код. Написанные классы позволяют быстро начать управлять содержимым базы данных, и избавляют от множества ручной рутинной работы.
Действия при создании новой таблицы (объекта) будут следующие:
Всё это выполняется сразу на несколько таблиц, если это старт проекта или большой патч. У меня иногда генерировалось до 20-30 таблиц, это заняло около 5 минут, но без этого пришлось бы провозиться целый день.
Посмотрите на реализацию скаффолдингов, вы сможете больше понять внутренние особенности программы и ее структуру.
Все исходники находятся по адресу https://bitbucket.org/chernikov/lessons [8]
Автор: chernikov
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/asp-net-mvc/31668
Ссылки в тексте:
[1] Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package: http://blog.stevensanderson.com/2011/01/13/scaffold-your-aspnet-mvc-3-project-with-the-mvcscaffolding-package/
[2] Text Template Transformation Toolkit: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/
[3] Windows PowerShell: http://en.wikipedia.org/wiki/Windows_PowerShell
[4] http://t4-editor.tangible-engineering.com: http://t4-editor.tangible-engineering.com
[5] Html: http://habrahabr.ru/users/html/
[6] Ссылка: http://www.olegsych.com/2010/05/t4-parameter-directive/
[7] http://mvcscaffolding.codeplex.com/SourceControl/changeset/view/7cd57d172314: http://mvcscaffolding.codeplex.com/SourceControl/changeset/view/7cd57d172314
[8] https://bitbucket.org/chernikov/lessons: https://bitbucket.org/chernikov/lessons
[9] Источник: http://habrahabr.ru/post/176097/
Нажмите здесь для печати.