От разработчиков ПО ждут все более сложные решения за меньшее время и с меньшим числом дефектов. Одна из областей, часто приводящая к потере времени, — начальная подготовка новых проектов на F#, C# или Visual Basic под конкретные требования. Лучший способ сократить количество повторяющихся задач по подготовке проектов — создать шаблон проекта Visual Studio Extension (VSIX).
Шаблон проекта VSIX также может понадобиться, чтобы продвигать в организации стандарты разработки ПО, продемонстрировать новый продукт или обеспечить быстрый переход на новую технологию. Какова бы ни была причина, появление возможности создавать эти шаблоны станет хорошим пополнением вашего арсенала разработки и, безусловно, принесет пользу.
В этой статье я покажу, как создать шаблон проекта VSIX, состоящий из веб-приложения ASP.NET MVC 3 на C#, библиотеки на F# с кодом, выполняемым на серверной стороне, и библиотеки F#, которая содержит модульные тесты. Кроме того, вы научитесь некоторым продвинутым приемам, которые позволят вам расширять свои шаблоны проектов и делать их более гибкими. Вы также узнаете, как существенно сократить ранее упомянутые повторяющиеся задачи за счет создания собственных шаблонов проектов.
Подготовка
Сначала вы должны выполнить несколько задач, чтобы подготовить свою среду разработки. Прежде всего убедитесь, что у вас установлены Visual Studio 2010 Professional (или выше) с компонентами F# и C#. Затем установите Visual Studio 2010 SDK, который можно скачать по ссылке bit.ly/vs-2010-SDK. Visual Studio 2010 SDK предоставляет все, что нужно для создания проекта VSIX. Чтобы сократить рутинные операции, связанные с созданием шаблона для нескольких проектов, вы должны также скачать Export Template Wizard по ссылке bit.ly/export-template-wiz. и установить его. Наконец, чтобы следовать примеру в этой статье, скачайте и установите ASP.NET MVC 3 Tools Update, доступный по ссылке bit.ly/mvc3-tools-update. В этом обновлении содержится ряд полезных средств и инструментов, в том числе NuGet 1.2. Подробнее об этом обновлении см. по ссылке bit.ly/introducing-mvc3-tools-update.
Создание начального приложения
Подготовив среду, можно приступить к написанию базового приложения, которое будет создаваться при каждом запуске этого шаблона проекта из New Project Wizard в Visual Studio. Это позволяет настроить все именно так, как нужно вам, чтобы сократить потери времени на выполнение повторяющихся задач, связанных с инициализацией проекта. Решение на этот момент может быть настолько простым или сложным, насколько вам требуется.
Первым делом вам понадобится создать проект веб-приложения ASP.NET MVC 3 на C#, который мы назовем MsdnWeb. Роль этого проекта — предоставить презентационный уровень для решения в целом. Вы должны запустить New Project Wizard в Visual Studio и выбрать ASP.NET MVC 3 Web Application, который находится в Visual C# | Web. Для этого примера нужно убедиться, что выбраны шаблон Empty, механизм представлений Razor и параметр Use HTML5 semantic markup options, а затем щелкнуть OK. Как только Visual Studio закончит генерацию проекта, найдите файлы Global.asax и Global.asax.cs. Большую часть кода для Global.asax вы будете писать на F#, поэтому теперь вы можете удалить файл Global.asax.cs и обновить разметку в Global.asax, как показано на рис. 1. В качестве финального шага в этом проекте можно создать новую папку в папке Views, присвоить ей имя Home и добавить в нее новое представление с именем Index.
Рис. 1. Обновление разметки в Global.asax
Далее создаем новый проект F# Library с именем MsdnWebApp и удаляем создаваемые по умолчанию файлы .fs и .fsx. Этот проект предназначен главным образом для того, чтобы помещать в него контроллеры, модели и другой серверный код, управляющий представлениями. Поскольку это проект на F#, вы сможете написать этот серверный код в очень четком и лаконичном стиле, присущим F#. Чтобы задействовать этот проект для предполагаемых целей, нужно добавить ссылки на сборки System.Web, System.ComponentModel.DataAnnotations, System.Web.Abstractions и System.Web.Mvc (версии 3.0+). Также добавьте файл Global.fs, содержащий код, который показан на рис. 2.
Шаблон проекта VSIX также может понадобиться, чтобы продвигать в организации стандарты разработки ПО, продемонстрировать новый продукт или обеспечить быстрый переход на новую технологию.
Рис. 2. Файл Global.fs
namespace MsdnWeb
open System
open System.Web
open System.Web.Mvc
open System.Web.Routing
type Route = { controller : string
action : string
id : UrlParameter }
type Global() =
inherit System.Web.HttpApplication()
static member RegisterRoutes(routes:RouteCollection) =
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
routes.MapRoute("Default",
"{controller}/{action}/{id}",
{ controller = "Home"; action = "Index"
id = UrlParameter.Optional } )
member this.Start() =
AreaRegistration.RegisterAllAreas()
Global.RegisterRoutes(RoutTable.Routes)
Кроме того, потребуется добавить файл HomeController.fs, содержащий следующий код:
namespace MsdnWeb.Controllers
Open System.Web
Open System.Web.Mvc
[<HandleError>]
type HomeController() =
inherit Conroller()
member this.Index() =
this.View() :> ActionResult
И последняя задача на этом этапе: вы должны создать второй проект F# Library и назвать его MsdnWebAppTests. Как и предполагает название, проект MsdnWebAppTests будет содержать модульные тесты для MsdnWebApp. Окончательная версия этого проекта должна быть снабжена модульными тестами. Однако, чтобы не усложнять пример, вы можете удалить генерируемые по умолчанию файлы .fs и .fsx и оставить проект пустым как контейнер для будущих тестов. Если вы хотите увидеть пример этого кода (его можно было бы добавить в данный проект), то установите NuGet-пакет FsUnit.MvcSample со страницы bit.ly/fsunit-mvc3-tests.
Создание шаблона проекта VSIX
Пример шаблона проекта, который мы разберем в этой статье, имеет несколько целей, и эти цели являются фундаментом, на котором базируется остальная часть моей статьи.
- Предоставить шаблон проекта VSIX, состоящий из проекта на C# и двух проектов на F#.
- Динамически добавлять каждый из этих проектов в общее решение при создании нового проекта.
- Программным способом добавлять любые необходимые ссылки при каждом запуске шаблона проекта.
- Устанавливать различные пакеты NuGet в созданный проект ASP.NET MVC 3 Web Application.
- Предоставить UI, позволяющий указывать различные параметры создания проекта.
Visual Studio предоставляет богатую модель расширения, которая позволяет легко добиться первой цели. Самый простой способ — запустить Export Template Wizard из File | Export Template as VSIX. После этого Visual Studio выводит мастер создания пакета VSIX, где можно выбрать несколько проектов для сжатия в пакет VSIX. Хотя этот вариант помогает в простых случаях использования нескольких проектов, для более сложных ситуаций (цели 2–5) он не годится. Для таких вещей вам потребуется вариант с более широкими возможностями.
Этот вариант — разновидность композиции через интерфейс IWizard. Обычно это называют созданием мастера шаблона. Чтобы создать простой мастер шаблона на F#, нужно создать новый проект F# Library, добавить класс, реализующий интерфейс IWizard (рис. 3) и добавить ссылки на EnvDTE и Microsoft.VisualStudio.TemplateWizardInterface.
Рис. 3. Реализация интерфейса IWizard
namespace MsdnFsTemplateWizard
open System
open System.Collections.Generic
open EnvDTE
open Microsoft.VisualStudio.TemplateWizard
type TemplateWizard() =
interface IWizard with
member this.RunStarted (automationObject:Object,)
replacementsDictionary:Dictionary<string,string>,
runKind:WizardRunKind, customParams:Object[]) =
"Not Implemented" |> ignore
member this.ProjectFinishedGenerating project =
"Not Implemented" |> ignore
member this.ProjectItemFinishedGenerating projectItem =
"Not Implemented" |> ignore
member this.ShouldAddProjectItem filePath = true
member this.BeforeOpeningFile projectItem =
"Not Implemented" |> ignore
member this.RunFinished() = "Not Implemented" |> ignore
Для обработки более продвинутой функциональности, такой как программное добавление ссылок на проекты и установка пакетов NuGet, вам также понадобится добавить ссылки на EnvDTE80, VSLangProj, VSLangProj80, Microsoft.VisualStudio.ComponentModelHost, Microsoft.VisualStudio.OLE.Interop, Microsoft.VisualStudio.Shell, Microsoft.VisualStudio.Shell.Interop, Microsoft.VisualStudio.Shell.Interop.8.0, NuGet.Core (версии 1.2+) и NuGet.VisualStudio (версии 1.2+). К=Если вы следовали инструкциям по подготовке среды, все эти библиотеки уже имеются на вашей локальной машине. Несколько ключевых библиотек можно также найти в папке lib исходного кода, который можно скачать для этой статьи по ссылке fsharpmvc3vsix.codeplex.com.
Поскольку это проект на F#, вы сможете написать данный серверный код в очень четком и лаконичном стиле, присущим F#.
В качестве заключительного шага в создании базового мастера шаблона вы должны подписать сборку этого мастера. Сначала нужно сгенерировать файл ключа со строгим именем (.snk) (как это делать, см. по ссылке bit.ly/how-to-create-snk ). Получив файл .snk, вы можете подписать сборку мастера шаблона F#, зайдя в свойства проекта, выбрав вкладку Build и введя в поле Other flags следующий текст (<Path> и <snk File Name> следует заменить значениями, относящимися к вашему файлу .snk):
'--keyfile:"<Path><snk File Name>.snk"'
Чтобы использовать этот мастер шаблона, нужно создать шаблон нескольких проектов (multiproject template), который ссылается на подписанную сборку мастера шаблона. Самый простой способ сделать это — задействовать расширение Export Template Wizard Visual Studio 2010 для запуска процесса, а потом вручную подправить полученный результат. В мастере, который появляется при выборе File | Export Template as VSIX, выберите проекты MsdnWeb, MsdnWebApp и MsdnWebAppTests, а затем щелкните Next. На итоговой странице введите имя и описание шаблона, которое появится в окне Visual Studio New Project Wizard, и укажите местонахождение DLL мастера шаблона для Wizard Assembly. Щелкнув Next, сбросьте флажок Automatically import the template into Visual Studio и щелкните Finish. После этого Visual Studio создаст пакет VSIX, запустит Windows Explorer и переключит вас в каталог, содержащий файл пакета VSIX.
Это дает отличную отправную точку, но теперь вы должны засучить рукава и вручную ввести несколько модификаций. Так как VSIX-файлы на самом деле являются файлами .zip с другим расширением, вы можете влезть в содержимое этого пакета, просто изменив расширение файла на .zip и распаковав его. Далее перейдите в каталог Solution в папке, появившейся в результате распаковки, и извлеките содержимое сжатого файла внутри него. После распаковки вы обнаружите проекты, которые образуют ваш шаблон проекта, и файл .vstemplate.
Чтобы реализовать предназначение этого шаблона проекта, вы должны модифицировать данный файл .vstemplate. Первым делом переименуйте его в MsdnFsMvc3.vstemplate. Поскольку этот файл .vstemplate является простым XML-файлом, вы можете теперь открыть его в любом текстовом редакторе и выполнить следующее.
- Убедитесь, что элементы<Name> и <Description> содержат информацию, которая должна отображаться для этого шаблона вVisual Studio New Project Wizard.
Измените значение в элементе <ProjectType> на FSharp.
- Удалите все дочерние элементы элемента <ProjectCollection>.
Теперь вы можете сохранить и закрыть файл, а затем запаковать три папки и модифицированный файл .vstemplate в файл MsdnFsMvc3.zip.
К этому моменту MsdnFsMvc3.zip можно было бы легко включить обратно в пакет VSIX, но тогда вы лишились бы возможности тестировать или расширять этот пакет с поддержкой средств отладки. Было бы намного лучше, если бы Visual Studio можно было использовать для задач такого типа. К счастью, Visual Studio 2010 SDK, установленный вами ранее, как раз и позволяет делать именно это. Для начала нужно создать новый VSIX Project в New Project Wizard в разделе Visual C# | Extensibility. Проект, создаваемый Visual Studio в этом случае, включает файл source.extension.vsixmanifest, который можно просматривать и редактировать простым двойным щелчком. Далее вы можете ввести основные метаданные с информацией о вашем шаблоне проекта. Пример дан на рис. 4.
Рис. 4. Заполнение метаданных
После этого файл MsdnFsMvc3.zip можно добавить в проект VSIX в Visual Studio. Для этого сначала перейдите в корневую папку проекта VSIX в Windows Explorer и создайте новую папку с именем ProjectTemplates. В ней вы должны добавить новую папку ASPNET. Создание этой второй папки определяет подтип проекта для шаблона. Скопируйте MsdnFsMvc3.zip в папку ASPNET.
В Visual Studio в режиме проектирования файлаsource.extension.vsixmanifest щелкните кнопку Add Content. На рис. 5 показаны элементы, которые вы можете задать.
Рис. 5. Добавление контента в режиме проектирования файла манифеста
Теперь нужно подправить структуру каталогов, автоматически создаваемую этим процессом в проекте VSIX. Для этого щелкните правой кнопкой мыши папку ProjectTemplates и создайте новую папку с именем ASPNET. Наконец, вы должны переместить сжатый файл из папки ProjectTemplates в папку ASPNET и щелкнуть Yes, когда вам будет предложено перезаписать существующий файл. Компиляция решения позволяет быстро убедиться в том, что ваш шаблон проекта VSIX был успешно создан Visual Studio. При желании вы могли бы теперь добавить этот шаблон в Visual Studio, дважды щелкнув файл .vsix, созданный Visual Studio, и пройдя все операции в мастере установки, который появляется после такого действия.
Расширение шаблона проекта
Операции, выполненные на данный момент, позволили добиться первой цели этого шаблона и подготовить почву для реализации остальных целей. Эти дополнительные цели в основном достигаются добавлением кода в метод RunFinished реализации IWizard мастера шаблона.
Как вы помните, второй целью мы ставили динамическое добавление каждого из проектов шаблона в решение при создании нового проекта. Необходимый код показан на рис. 6.
Рис. 6. Динамическое добавление проектов в решение
// Этот метод содержится в полном исходном коде
// как часть класса TemplateWizard
member this.RunFinished() =
// Нерелевантный код опущен
// this.solution задается в методе RunStarted
let templatePath = this.solution.GetProjectTemplate(
"MsdnFsMvc3.zip", "FSharp")
let AddProject status projectVsTemplateName projectName =
// this.dte2 задается в методе RunStarted
this.dte2.StatusBar.Text <- status
let path = templatePath.Replace(
"MsdnFsMvc3.vstemplate", projectVsTemplateName)
this.solution.AddFromTemplate(path,
Path.Combine(this.destinationPath, projectName),
projectName, false) |> ignore
// webName и webAppName — значения, связанные со строками,
// которые идентифицируют конкретные проекты
AddProject "Installing the C# Web project..."
(Path.Combine("MsdnWeb", "MsdnWeb.vstemplate")) webName
AddProject "Adding the F# Web App project..."
(Path.Combine("MsdnWebApp",
"MsdnWebApp.vstemplate")) webAppName
// Нерелевантный код опущен
Первая строка кода в методе, показанном на рис. 6, возвращает местонахождение многопроектного шаблона на компьютере, с которого был запущен New Project Wizard. Вторая — определяет функцию с именем AddProject. Эта функция содержит код, необходимый для обновления строки состояния Visual Studio, указывает файл .vstemplate, соответствующий нужному шаблону проекта, и добавляет проект из шаблона в решение. В последних строках кода вызывается функция AddProject для проектов MsdnWeb и MsdnWebApp. Аналогичный вызов AddProject можно было бы добавить и для проекта MsdnWebAppTests, если бы в том была нужда.
Visual Studio предоставляет богатую модель расширения.
Для достижения третьей цели (динамического добавления любых нужных ссылок проекта) вы должны сначала создать сопоставление (map) имен проектов и соответствующих объектов Project, образующих решение (о сопоставлениях в F# см. по ссылке bit.ly/fsharp-maps ). Для этого вызывается собственная функция BuildProjectMap, аргументом которой является набор Projects в решении.
Затем осуществляется связывание со значением projects, как показано ниже:
// Эта функция содержится в полном исходном коде
// как часть модуля ProjectService
let BuildProjectMap (projects:Projects) =
projects
|> Seq.cast<Project>
|> Seq.map(fun project -> project.Name, project)
|> Map.ofSeq
// Этот метод содержится в полном исходном коде
// как часть класса TemplateWizard
member this.RunFinished() =
// Нерелевантный код опущен
// this.dte задается в методе RunStarted
let projects = BuildProjectMap (this.dte.Solution.Projects)
// Нерелевантный код опущен
Теперь, когда у вас есть сопоставление проекта (project map), вы можете вызвать другую собственную функцию для запуска процесса добавления ссылок проекта. Первая строка следующего кода создает список tuple-объектов:
// Этот метод содержится в полном исходном коде
// как часть класса TemplateWizard
member this.RunFinished() =
// Нерелевантный код опущен
// webName, webAppName и webAppTestsName — значения,
// связанные со строками, которые используются
// для идентификации конкретных проектов
[(webName, webAppName); (webAppTestsName, webAppName)]
|> BuildProjectReferences projects
// Нерелевантный код опущен
Каждый tuple-объект в списке представляет имя целевого проекта, за которым следует имя проекта, на который есть ссылка. Например, первый tuple указывает имя «MsdnWeb» (целевого проекта) со связанным именем «MsdnWebApp» (проекта, на который есть ссылка). Этот список затем передается по конвейеру в функцию BuildProjectReferences.
Процесс достижения последней цели — добавление UI, с помощью которого можно собирать информацию от пользователя в ходе создания проекта, — несильно изменился за последние несколько лет.
Ниже показана функция BuildProjectReferences:
// Эта функция содержится в полном исходном коде
// как часть модуля ProjectService
let BuildProjectReferences (projects:Map<string, Project>)
projectRefs = projectRefs
|> Seq.iter (fun (target,source) ->
AddProjectReference (projects.TryFind target)
(projects.TryFind source))
Эта функция просто принимает список tuple-объектов, перебирает их, пытается получить из сопоставления проекта подходящий объект Project по имени и вызывает функцию AddProjectReference для выполнения реальной работы.
Функция AddProjectReference заканчивает процесс, проверяя, что и в аргументе target, и в аргументе projToReference содержатся допустимые проекты:
// Эта функция содержится в полном исходном коде
// как часть модуля ProjectService
let AddProjectReference (target:Option<Project>)
(projToReference:Option<Project>) =
if ((Option.isSome target) &&
(Option.isSome projToReference)) then
let vsTarget = target.Value.Object :?> VSProject
vsTarget.References
|> Seq.cast<Reference>
|> Seq.filter(fun (reference) -> reference.Name =
projToReference.Value.Name)
|> Seq.iter(fun reference -> reference.Remove())
vsTarget.References
.AddProject((projToReference.Value.Object :?>
VSProject).Project)
|> ignore
Если они содержат допустимые проекты, эта функция удаляет любую существующую ссылку, а затем добавляет ссылку в проект.
Четвертая цель этого шаблона проекта связана с концепцией, недавно введенной группой ASP.NET MVC. Она предоставляет отличный способ добавления ссылок на библиотеки или инфраструктуры, которые скорее всего будут расширены в недалеком будущем. NuGet 1.2, которая поставляется с ASP.NET MVC 3 Tools Update, включает сборку NuGet.VisualStudio; она позволяет легко устанавливать пакеты NuGet из мастера шаблона. ASP.NET MVC 3 Tools Update также устанавливает на локальный компьютер несколько пакетов NuGet, чтобы ускорить установку этих специфических пакетов при создании нового проекта.
В примере есть несколько функций, используемых для выполнения установок пакетов NuGet. Самая важная из них показана ниже:
// Эта функция содержится в полном исходном коде
// как часть модуля NuGetService
let InstallPackages (serviceProvider:IServiceProvider)
(project:Project) packages =
let componentModel =
serviceProvider.GetService(typeof<SComponentModel>) :?>
IComponentModel
let installer = componentModel.GetService<
IVsPackageInstaller>()
let nugetPackageLocalPath = GetNuGetPackageLocalPath()
packages
|> Seq.iter (fun packageId ->
installer.InstallPackage(nugetPackageLocalPath,
project, packageId, null, false))
С помощью первых трех строк кода функция получает конкретную реализацию интерфейса IVsPackageInstaller, который будет использоваться для установки различных пакетов NuGet в указанном проекте. В четвертой строке кода вызывается функция GetNuGetPackageLocalPath, которая обращается к системному реестру, чтобы определить путь установки ASP.NET MVC 3. Наконец, предоставленный список имен пакетов передается по конвейеру в функцию Seq.iter, которая перебирает список и устанавливает каждый пакет.
Теперь, когда вся эта функциональность реализована в вашем мастере шаблона, вы просто добавляете проект мастера шаблона как контент в проект VSIX с типом Template Wizard. На рис. 7 показано заполненное окно Add Content. Цели2–4 достигнуты.
Рис. 7. Заполненное окно Add Content
Добавление Windows Presentation Foundation UI
Процесс достижения последней цели — добавление UI, с помощью которого можно собирать информацию от пользователя в ходе создания проекта, — несильно изменился за последние несколько лет. В статье Рона Петруши (Ron Petrusha) за 2007 г. (oreil.ly/build-vs-proj-wiz) дан отличный обзор на эту тему. Хотя суть этого процесса остается прежней, вам нужно знать несколько вещей, чтобы реализовать эту функциональность в Windows Presentation Foundation (WPF) и связать ее с вашим шаблоном проекта.
Шаблон проекта VSIX для F# и C# способствует повторному использованию.
Для начала нужно создать новую C# User Control Library. Теперь вы должны сменить целевую инфраструктуру с .NET Framework 4 Client Profile на .NET Framework 4. Затем можно удалить файл UserControl1.xaml по умолчанию и добавить новое WPF-окно. Далее вы можете любым способом, какой вам нравится, манипулировать XAML-разметкой для проектирования подходящего UI. Наконец, вы должны обеспечить доступ к любым нужным свойствам, а затем определить любые требуемые обработчики событий. Вот простой пример отделенного кода для WPF-окна:
// Содержится в полном исходном коде
// как часть класса MsdnCsMvc3Dialog
public bool IncludeTestsProject { get; set; }
private void btnOk_Click(object sender, RoutedEventArgs e)
{
IncludeTestsProject =
cbIncludeTestsProject.IsChecked.HasValue ?
cbIncludeTestsProject.IsChecked.Value : false;
DialogResult = true;
Close();
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
Close();
}
После создания нужного UI вы должны подписать сборку. Затем добавить проект — как контент типа Template Wizard — в проект VSIX. В проекте мастера шаблона вы добавляете ссылку на проект, содержащий ваш UI. Закончив, введите код в метод RunStarted реализации IWizard, которая будет отображать этот UI и получать результат:
// Этот метод содержится в полном исходном коде
// как часть класса TemplateWizard
member this.RunStarted () =
// Нерелевантный код опущен
let dialog = new TemplateView()
match dialog.ShowDialog().Value with
| true ->
this.includeTestProject <- dialog.IncludeTestsProject
| _ ->
raise (new WizardCancelledException())
Наконец, в метод RunFinished мастера шаблона можно добавить следующий код:
// Этот метод содержится в полном исходном коде
// как часть класса TemplateWizard
member this.RunFinished() =
// Нерелевантный код опущен
// webAppTestsName — значение, связанное со строкой,
// которая представляет проект Tests
if this.includeTestProject then
AddProject "Adding the F# Web App Tests project..."
(Path.Combine("MsdnWebAppTests",
"MsdnWebAppTests.vstemplate")) webAppTestsName
// Нерелевантный код опущен
Заключение
Создание шаблона проекта VSIX для F# и C# — простой вариант, способствующий повторному использованию и сокращению времени, затрачиваемому на повторяющиеся задачи при настройке каждого нового проекта. Теперь, располагая нужными знаниями, вы сможете повысить эффективность своего труда, создавая шаблоны проектов, которые могут быть настолько простыми или сложными, насколько вам требуется.