Пользовательские шаблоны и расширения для Visual Studio под проект (Часть 1: шаблоны элементов)
В магазине Visual Studio есть множество различных расширений на все случаи жизни. Есть в сети различные их подборки, которые могут упростить жизнь в общих или конкретных случаях. Однако я, почему-то, никогда не встречал расширения заточенные под проект.
Я подумал, мы разворачиваем под проект огромную инфраструктуру для соблюдения процессов, разворачивания, логирования, локального запуска. На все это уходит немало времени на старте и это требует поддержки.
А что с общим функционалом? Если нам, например, нужно вынести общий функционал, который используется для ряда микросервисов, мы выносим его в Nuget. Очень удобно, кстати, создал новый микросервис, подсмотрел в соседнем инфраструктуру, склонировал, подцепил общие нагет пакеты, включил все это в построителе приложения... Раз плюнуть! Или два раза плюнуть? Или три? Хмм... Очень удобно, когда перечисленные процедуры приходится выполнять изо дня в день, рука набита и вообще это делает специально обученный человек, который больше ничем не занимается. На практике же приходится лезть в документацию и читать рекомендации, которые могут быть устаревшими на момент их использования. Начинаешь опрашивать членов команды, кто что помнит, собирать информацию по кусочкам. И в итоге на простую шаблонную инициализацию тратишь полдня или даже целый день. 8 ч/ч в рамках 1-3 годового проекта с командой из 5-10 человек это не так уж и много, но немного выбивает из колеи. Меня. А вас?
А как на счет вот такого контроллера?
public class SomeController : MyGenericControllerBase
{
public SomeController(BaseDep1 dep1, BaseDep2 dep2, BaseDep3 dep3)
: base(dep1, ddep2, dep3)
{}
}
Ну это легко. Мы просто создаем класс по шаблону, добавляем модификатор public (если это просто стоковый дотнетовский шаблон, решарперовский шаблон класса сразу создает public), добавляем базовый класс, дальше стандартными студийными средствами или средствами решарпера реализуем базовый класс, который сразу создает конструктор и реализует абстрактные члены. Кстати, все абстрактные члены будут по умолчанию либо пустыми либо выбрасывать NotImplementedException, в зависимости от ваших настроек.
Не то что бы это очень сложно, но все же. Это те рутинные вещи, которые нам приходится делать регулярно. А что инженеры обычно делают с рутинными вещами? Автоматизируют. Но как? Вынести в нагет мы эту рутину не можем. Зато, мы можем создавать собственные шаблоны классов, проектов и даже группы проектов.
Шаблоны классов
Итак, предположим, что у нас есть такой вот базовый контроллер
using Microsoft.AspNetCore.Mvc;
using VsExtensionsPolygon.ClassesForTemplate.Core;
namespace VsExtensionsPolygon.ClassesForTemplate
{
public abstract class MyControllerBase : ControllerBase, IMyControllerAbstraction
{
protected MyControllerConstructorArgumentType Argument { get; }
protected MyControllerBase(MyControllerConstructorArgumentType argument)
{
Argument = argument;
}
public void DoAny()
{
AbstractMethod();
}
protected abstract void AbstractMethod();
}
}
Мы не хотим каждый раз реализовывать его из шаблона класса. Мы очень просто можем создать шаблон.
Для начала, все таки, потребуется реализовать его один раз. Предположим, что каждая новая реализация должна выглядеть вот так
namespace VsExtensionsPolygon.ClassesForTemplate
{
public class MyControllerImplementationTemplate : MyControllerBase
{
public MyControllerImplementationTemplate(MyControllerConstructorArgumentType argument) : base(argument)
{
}
protected override void AbstractMethod()
{
throw new System.NotImplementedException();
}
}
}
Здесь, кстати, вместо просто throw new System.NotImplementedException(); мы можем написать рекомендации и подсказки по реализации данного метода.
Далее в студии мы щелкаем по пункту меню Проект и выбираем пункт Экспорт шаблона...
Попадаем в это окно:
Выбираем пункт Шаблон элемента выбираем проект, в котором наша заготовка для шаблона (по умолчанию выбран проект, который выделен в обозревателе решений) и нажимаем Далее. Затем выбираем файл, в котором наша заготовка для шаблона, снова жмем Далее, следующий пункт пропускаем (если надо, можем это потом обсудить подробнее), снова жмем далее и попадаем в окно настройки шаблона:
Имя шаблона - то, что будет выведено в списке шаблонов при создании нового элемента в проекте.
Описание шаблона - То что будет в описании шаблона при его выделении.
Изображение значка - иконка, которая будет возле шаблона в списке шаблонов при создании элемента.
Просмотр изображения - понятия не имею :)
Автоматически импортировать шаблон в Visual Studio - если установлено, шаблон сразу попадет в папку для пользовательских шаблонов. В данном примере я эту галку оставил, чтобы сразу посмотреть результат, но в дальнейшем предлагаю ее снимать.
Открыть окно проводника в папке выходных файлов - открывает в проводнике тот путь, куда копируются шаблоны после создания.
Теперь, если мы в каком либо проекте решим создать элемент, мы сможем увидеть там наш шаблон.
Все, теперь мы можем единолично пользоваться этим шаблоном. Не очень красивое название, но мы сами его выбрали :)
Конечно же, надо самостоятельно побеспокоиться о том, чтобы проект, в который мы добавляем наш контроллер, имел все необходимые референсы. В рамках шаблона элемента мы этот процесс автоматизировать не можем. Но мы можем это сделать в рамках шаблона проекта. Но об этом в следующей статье.
Теперь немного о том, как внести правки в существующий шаблон.
Редактирование шаблона элемента
После создания шаблона, у нас открылась папка с ним. Как видите, получившийся шаблон имеет формат .zip. Если его открыть, внутри будет 2 файла: файл с расширением .vstemplate и файл с расширением .cs.
vstemplate содержит настройки шаблона. cs - сам шаблон, по которому будет создан наш контроллер.
Для начала откроем cs.
namespace $rootnamespace$
{
public class $safeitemname$ : MyControllerBase
{
public $safeitemname$(MyControllerConstructorArgumentType argument) : base(argument)
{
}
protected override void AbstractMethod()
{
throw new System.NotImplementedException();
}
}
}
Здесь мы видим почти то же самое, что написали в нашем шаблонном контроллере, по которому создавали шаблон, за исключением пространства имен и имени класса. Вместо них вставлены переменные, которые будут заменены на их значения при добавлении элемента в проект.
$rootnamespace$ - будет заменен на пространство имен,
сформированное в зависимости от того, где вы создадите элемент.
$safeitemname$ - будет заменен на название файла, которое вы укажете
при создании элемента.
С полным списком переменных можно ознакомиться здесь.
Теперь посмотрим что внутри vstemplate файла
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
<TemplateData>
<DefaultName>VsExtensionsPolygon.ClassesForTemplate.cs</DefaultName>
<Name>VsExtensionsPolygon.ClassesForTemplate</Name>
<Description><Нет описания></Description>
<ProjectType>CSharp</ProjectType>
<SortOrder>10</SortOrder>
<Icon>__TemplateIcon.ico</Icon>
</TemplateData>
<TemplateContent>
<References />
<ProjectItem SubType="" TargetFileName="$fileinputname$.cs" ReplaceParameters="true">MyControllerImplementationTemplate.cs</ProjectItem>
</TemplateContent>
</VSTemplate>
Здесь формат XML. Давайте разберемся в тегах.
VSTemplate - это контейнер для всех настроек шаблонов. Имеет парамтер версии,
пространства имен и типа шаблона. В данном случае Item. Доступные значения
Project и ProjectGroup.
TemplateData - контейнер для параметров шаблона.
DefaultName - имя создаваемого файла по умолчанию.
Name - имя шаблона в списке шаблонов при добавлении элемента.
Description - описание шаблона.
ProjectType - тип проекта, для которого доступен шаблон.
SortOrder - индекс сортировки.
Icon - имя файла иконки. Если ничего не выбрано, будет установлена
какая-то базовая иконка.
TemplateContent - информация о содержимом шаблона.
References - список референсов, необходимых для создания элемента.
ProjectItem - элемент проекта. Содержит атрибуты
TargetFileName - конечное имя файла созданного элемента.
ReplaceParameters - флаг, отвечающий за замену переменных на их значения.
File (default) - имя файла в составе шаблона (zip архива).
Давайте попробуем изменить описание на "Тестовый шаблон контроллера". Затем сохраним файл и заменим его в архиве. Вот только не в том, который у вас сейчас открыт. Вы открыли его из папки, куда складываются новые шаблоны, но студия подтягивает шаблоны не отсюда.
C:\Users\[user_name]\Documents\Visual Studio 2019\Templates\ItemTemplates
Там лежит копия архива с шаблоном, который вы видели ранее. Прямо в корне. Вот в нем и заменим наш файл.
Для того, чтобы изменения вступили в силу необходимо перезапустить студию. Перезапускаем, открываем
Как видим, описание изменилось. Теперь еще один штрих. Мне не очень то хочется, чтобы наш шаблон светился в общей куче шаблонов. Поэтому я вырежу его из корневой папки шаблонов, зайду в папку Visual C#, создам там папку MyProject и вставлю файл туда. Вновь перезапустим студию и откроем диалог создания элемента.
Как видим, у нас появилась папка MyProject и внутри нее мы можем найти наш шаблон. В корневой папке его больше нет.
Этим шаблоном мы можем поделиться с командой и после добавления архива в указанную папку, он так же появится и у них.
Заключение
Как создавать шаблоны проектов и элементов информация не новая, но я пока не встречал тех, кто создает шаблоны для конкретных долгоиграющих проектов. Думаю, имея определенное количество элементов, которые повторяют один какой то шаблон, а так же определенное количество человек в команде, воспользоваться этой возможностью будет не лишним. к тому же не придется постоянно обновлять документацию по добавлению элемента, там будет сказано "создаем элемент по шаблону с таким-то названием". Изменению будет подвержен сам шаблон.
В следующих статьях (если зайдет эта), я расскажу: