updated: July 15th, 2023
I recently updated my Visual Studio template for creating Markdown Monster addins in order to support the newer .NET SDK style projects, which are easier to work with and provide a 'ready-to-run' Markdown Monster Addin project which was not quite possible with the older project type and a project template.
Visual Studio templates are nice when you're using Visual Studio as you can easily install an extension from the Visual Studio Gallery. Easy, but... these days not every .NET developer is using Visual Studio anymore, even when building a Windows specific solution like a Markdown Monster Addin.
Creating anything for the Visual Studio Extension system also is a royal pain in the ass: While creating a basic project template is simple enough (you can just export a project to a template), customizing the template to support generic naming and custom output paths and debugging, testing and then wrapping the template into a VSIX in order to publish to the MarketPlace is an exercise in hellish frustration.
dotnet new: Sanity returns
Thankfully, more recently Microsoft has introduced new project formats and alongside with them a newer and decidedly simpler mechanism for distributing project templates via the dotnet new
templating experience. The new command line (CLI) tooling that allows performing many tasks like building, restoring, creating, publishing etc. via the dotnet
CLI is easy and works for any development environment.
Although this tooling was introduced primarily to support .NET Core, you can also use it on full framework projects, as long as the project uses the new .NET SDK format and so it would also work for my Markdown Monster addin project template which requires full .NET Framework 4.7.2.
Custom Templates
The new project system also supports a straight forward mechanism for creating your own Item or Project templates that work from the command line and therefore can be used with any development environment. It's maybe not quite as smooth as installing an extension from the Visual Studio Marketplace, but command line syntax for installing custom templates and then creating new projects is only a few easily typed commands.
You can also create your own templates and easily publish and share them via NuGet. They are easy to create: You create a template project that contains all the files you want to be contained in your template, and wrap around a project folder hierarchy and add a small metadata file that describes your template.
The wrapped template - or several of them - can then be bundled up into a NuGet package that can be published and shared on the NuGet Repository and can be installed via dotnet new install <package-name>
.
While this is process is not vastly different from the process for Visual Studio Templates, the syntax and metadata used, plus the debug and run cycle is much more manageable. For example, you can simply update your template by reinstalling it and re-running the template. Templates can be installed from online NuGet packages, local NuGet packages, or a local folder. This makes it quick and easy to make changes to a template, test it out again and get it perfect. When done publishing to NuGet for sharing your template with the world is just a few more simple commands away.
Creating a Project Template
Creating a new project template is pretty straight forward but it does involve a few manual steps of creating folders and creating project and template metadata files.
At a high level you need to create:
- A top level Template Packaging Project
- A template folder that contains your project files
- A meta data file that describes the template
The top level project acts as the container for one or more templates and is used to package the templates into a NuGet package. The actual template contains all of your files and the metadata file describes the template, and allows you to customize the files with template expansions when the project is created, so you can customize the project name and class and namespaces of the new project.
The explicit steps are a bit more involved as you end up creating a nested folder hierarchy along with some specifically formatted project and meta data files and I'll take you through these steps next.
Here are the specific steps in summary
- Create a working project and make sure it works
- Create a new folder
- Create the top level template container
.csproj
file (paste text from below)
- Inside of the project folder add a
templates
folder
- Inside the
templates
folder create a new folder for your specific template
(you can create multiple templates into a single template pack)
- Inside of the custom template folder add a
.template.config
folder
- Create a
template.json
that provides template metadata
- Optionally add a square
icon.png
for the icon to display in Visual Studio
- Optionally use
sourceName
to replace file and class names and namespaces
The project structure is always the same save for your custom project files. To make this easier to visualize here's what a typical project layout looks like in this case for my MarkdownMonsterAddinProject
template:
Figure 1 - A ready to build template project
The root folder holds only the package project and associated icon. The nested templates
folder holds one or more child project templates that can be packaged into the single NuGet package.
The actual template folder then contains a .template.config
folder with a template.json
file that contains the specific template's meta data.
Creating the Folder Structure
The best way to start is to create the folder structure. To demonstrate, I'm going to go through this with my MarkdownMonster addin so the naming reflects that.
At the top level create a folder that matches your template name - think of that as your 'solution' folder: A container for your template project. I used MarkdownMonsterAddinTemplate
for the name. This folder only contains the .csproj
file and an optional icon for the NuGet package.
The top level Container Project File
Next we'll need to set up the container project file. This file is mostly generic and can just be copied as it with a few replacements. Its sole purpose is to act as a container of meta data for the NuGet package. This project doesn't have specific information about any of the contained templates.
The content project is a standard SDK project with a <PackageType>Template</PackageType>
and looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageType>Template</PackageType>
<PackageVersion>1.0</PackageVersion>
<PackageId>MarkdownMonster.AddinProject.Template</PackageId>
<Title>Markdown Monster Addin Project Template</Title>
<Authors>Rick Strahl</Authors>
<Description>Project Template that creates a Markdown Monster Addin that can be customized.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageOutputPath>./nupkg</PackageOutputPath>
<PackageTags>markdown-monster;markdown;wpf;documentation</PackageTags>
<PackageCopyright>Rick Strahl, West Wind Technologies 2020</PackageCopyright>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://github.com/RickStrahl/MarkdownMonsterAddinProjectTemplate</PackageProjectUrl>
</PropertyGroup>
<ItemGroup>
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>
<ItemGroup>
<None Include="icon.png" Pack="true" PackagePath="" />
</ItemGroup>
</Project>
This is all boilerplate except for the names and icon. This project is a content project (<includeBuildOutput>false</includeBuildOutput>
) that's simply a container for the NuGet package.
The key items that are specific to getting the package built:
<PackageType>Template</PackageType>
marks this as a template project
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
makes sure that dotnet build
creates a package
<PackageOutputPath>.\nupkg</PackageOutput>
a specific path where the .nupkg
is generated
Setting up the Template Project Folders
Next you need to create the actual Project Template folder that contains one or more projects.
To do this create this folder structure:
MarkdownMonsterAddinTemplate
-- templates
-- MarkdownMonsterAddinProject
-- templates
-- template.json
-- icon.png
-- $safeprojectname$.csproj
-- Addin.cs
-- ... more files
where MarkdownMonsterAddinProject
contains your specific project files. The project folder should contain all the files you want to ship with your template. If you look in Figure 1 you can see I have the required .csproj
file, a couple of C# files (Addin.cs
and AddinConfiguration.cs
) plus a whole bunch of extra related resource files: A couple of images (an icon, screen shot and logo), a custom addin version file and a license file. Anything goes here: I even include binary 7Zip files in there, so that the addin can be packaged into a .zip
file for publishing in the MM addin repository. All of these files are specific to my custom project type, so your project template is going to look very different than mine here.
You can add multiple project templates here too: You could add another folder at the same level as the MarkdownMonsterAddinProject
and that project type also installs when the template is installed.
Notice that the project file uses $safeprojectname$.csproj
for the filename - this is project specific too and that value will be replaced based on a replaceable value that gets created in the template.json
file described in the next section.
Each template includes meta data via a .templates/template.json
in the template's project folder. template.json
describes the template and optionally provides for some replacement values you can use to replace values in both the content of text files as well as for file names.
Here's what my templates.json
looks like:
{
"$schema": "http://json.schemastore.org/template",
"author": "Rick Strahl, West Wind Technologies",
"classifications": ["common", "library", "markdown-monster", "WPF"],
"identity": "MarkdownMonsterAddin.Project",
"name": "Markdown Monster Addin Project",
"shortName": "markdownmonsteraddin",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "$safeprojectname$", // replace w/ project name or -n val
"defaultName": "MarkdownMonsterAddin" // Visual Studio default display name
}
Most of these are pretty obvious as they describe the template. The identity
is a unique Id that identifies the addin, but the shortname
is what displays in the dotnet new
list of project types - the latter is the one you want to name carefully since that's what end users see.
Customizing Template Content and File Names with sourceName
and symbols
One thing that almost any generic template has to do, is to customize the name of the of the generated output file names and some of the content for things like class name(s) and namespaces.
For example, in Markdown Monster's Addin Template I want the main class name to match the name of the project that user selected. If my project's name is MySweetAddin
, I want to change the main class name to public class MySweetAddin
as well as the namespace. I also want the main addin file to be renamed to MySweetAddin.cs
and the configuration file to MySweetAddinConfigurat.cs
.
There are two ways you can customize templates using:
- The built-in
sourceName
Property
- The custom
symbols
Collection
The sourceName
Property
"sourceName": "$safeprojectname$";
The not so obvious sourceName
property can be used to replace a token value with the name of the project that is generated when the project is created.
To keep with the MySweetAddin
example from before, all instances of text $safeprojectname$
are replaced with MySweetAddin
in the content of any of the text files. It also replaces filenames - so $safeprojectname$.csproj
gets renamed to MySweetAddin.csproj
, which indirectly controls the name of the output assembly as MySweetAddin.dll
.
To give you an idea what replacement values in a source file look like here is my Addin.cs
class file with replacements for $safeprojectname$
:
Figure 2 - you can replace the project name using the sourceName
configuration
Note: I'm using $safeprojectname$
as my replacement value merely out of convenience because that's what the old Visual Studio extension used. The string doesn't have to have the $
at the beginning and end - it can be any string that gets replaced. Microsoft's examples often use namespaces, but I find that not a good choice because you're likely to use this value for your main class, file name and probably part of the name for secondary files and namespaces. I do recommend you use some sort of delimiter just to make it obvious that you're looking at a replaceable value in your template.
Note that project name used for sourceName
replacement is determined by the project name created with dotnet new
or dotnet new -n <projectName>
:
# Project name is MySweetAddin based on **source folder**
c:\temp\MySweetAddin> dotnet new
# Project is MySweetAddin via **-n** parameter
c:\temp\projects\test> dotnet new -n MySweetAddin
Custom Symbol Mappings
The sourceName
property is most likely all that you need, but if you need to pass additional parameters for expansion or behavior you can do so via the symbols
setting. This key is a collection of 'parameters' that can be applied as replacement values similar to the way that sourceName
works, except that you get to specify the names.
Here's a custom -c|--company
parameter you can pass for example:
"symbols": {
"company": {
"type": "parameter",
"defaultValue": "Your Company",
"replaces": "$company$"
}
}
This replaces any occurrence of $company$
with whatever is passed via the -c
command line:
dotnet new markdownmonsteraddin -c "West Wind Technologies"
In the example below the $company$
value in the author
key is replaced with West Wind Technologies
.
{
"id": "$safeprojectname$",
"name": "$safeprojectname$",
"summary": "Description of $safeprojectname$ in a short paragraph",
"description": "Describe $safeprojectname$ with a long description",
"version": "0.01",
"minVersion": "1.70",
"author": "© $company$", // <---- Company replaced with West Wind Technologies
"updated": "2020-10-01T12:00:00Z"
}
Now we have all the bits and pieces in place and we should be able to build our new project.
Building and Publishing the Project
To build a project is very simple now. From the root project's folder (or from within Visual Studio or other IDE) just build your project:
dotnet build
This creates a NuGet Package in the .\nupkg
folder (or whatever you specified as your output folder) that you can distribute. But... before you do that you probably want to test the project and see how it works. Once you install, most likely you'll discover a few things that aren't set up quite right and you'll go back and forth a few times fixing small issues with your template to get it just right.
Trying out your Template Locally
The good news is that unlike Visual Studio extensions it's super easy to install, update and uninstall project/item templates using the dotnet new
command.
You can also install your project template from:
- A NuGet package from NuGet.org
dotnet new install <package-id>
- A local NuGet Package from a folder
dotnet new install <path-to-nuget-package>
- A Project Folder
dotnet new install <path-to-project-template-folder>
For initial testing the easiest is usually to use your project template folder, which in my example here is the path to the MarkdownMonsterAddinProject
folder in Figure 1.
To test the addin:
# Install template from local folder
dotnet new install "<project-path>\templates\MySweetAddin"
# create a new project folder
md \projects\MySweetAddin
cd \projects\MySweetAddin
# create a new project
dotnet new -n MySweetAddin -c "West Wind Technologies"
# Open project in IDE (VS Code here, but you can use Visual Studio)
code .
If you install your template and you find out that it's not working quite right, you can simply fix the template in your project and then re-run the dotnet new install
command to re-install the template from the folder. You can then simply delete the contents of the generated project folder and re-run the install:
# assuming you're still in the MySweetAddin folder
remove-item *.* -f -recurse
# install the template
dotnet new install "<project-path>\templates\MySweetAddin"
# create the project
dotnet new -n MySweetAddin --company "West Wind Technologies"
code .
Rinse and repeat until you get it right!
Building the NuGet Package
Once you have the project template working the way you want it you can package it up and create a NuGet package and optionally publish it on NuGet.org.
# From the top level Project Folder
dotnet build -c Release
Here's what the NuGet package looks like in the excellent Package Explorer tool:
Figure 3 - The compiled template as a NuGet package in NuGet Package Explorer
Publishing to NuGet
If you want to share your project template you can build the NuGet package and push it to NuGet. To do this you can use the Package Explorer and simply click on the Publish button after you've set up your NuGet account and have configured your NuGet Publish Id.
Alternately you can use a publish script like this:
if (test-path ./nupkg) {
remove-item ./nupkg -Force -Recurse
}
dotnet build -c Release
$filename = gci "./nupkg/*.nupkg" | sort LastWriteTime | select -last 1 | select -ExpandProperty "Name"
$len = $filename.length
if ($len -gt 0) {
Write-Host "Signing... $filename"
nuget sign ".\nupkg\$filename" -CertificateSubject "West Wind Technologies" -timestamper " http://timestamp.digicert.com"
nuget push ".\nupkg\$filename" -source "https://nuget.org"
}
Here's the package, published on NuGet:
Figure 4 - The published package on NuGet
Installing and Running from NuGet
Once the package has been published you can now install it from NuGet.org by simply running:
# First-time install/update the project template
dotnet new install MarkdownMonster.AddinProject.Template
# Create the new project
md \projects\MySweetAddin
cd \projects\MySweetAddin
dotnet new markdownmonsteraddin -n MySweetAddin -c "West Wind Technologies"
# Open in editor
code .
# should just work!
dotnet build -c Release
# Run Markdown Monster with the addin installed
mm
If all went well and I did my job testing, the project should just compile and in this case just run. And sure enough, here's what it looks like installing and running the addin:
Figure 5 - Putting it all together: Installing, creating, building and running the project template
Cool. One nice thing about the new .NET SDK templates is that they can be customized easily to work generically so I've been able to make this template work so it installs the addin in the default addin location and 'just works out of the box'.
Sweet!
Open generated Project in Visual Studio
You can of course also open this generated project in Visual Studio. You don't need to compile or run from the command line as I've shown above.
To open the project in Visual Studio:
- Open the project folder
- Click on the
.csproj
file
- Save a solution file
Here's what the generated folder and Visual Studio look like when the project is loaded there:
Figure 6 - Opening the new project in Visual Studio
If you want to auto-launch an addin you'll probably want to add a Properties\Launchsettings.json
file that points at your main executable that launches the app and then launches your addin.
Visual Studio Extension or Dotnet New Template?
In this article I've gone into detail on how to create a project template with the dotnet new
SDK tooling and it's pretty easy to create these templates. Compared to creating a Visual Studio addin the process of the project template creation is much simpler and much more maintainable. It's easier to create the templates, much, much easier to test and update them, and the format layouts are straight-forward so that coming back to them after a few months won't be a shock. By comparison Visual Studio Project Template Extensions are a bit painful, especially the VSIX creation process.
Dotnet New Templates: Also playing in Visual Studio now
The good news is that dotnet new
installed templates also show up in Visual Studio as a project template, so from a purely functional point of view you no longer need a VSIX extension.
dotnet new
installed templates don't show up in the Extensions Manager, so you have to know what you're looking for when creating a new project by searching for the project type.
Figure 8 - using a dotnet new template in Visual Studio: It's just available
The only downside compared to a full VSIX project is the visibility and discoverability in the Extension Manager and Marketplace app. But functionally the dotnet new
template provides most of the same features - unless you need explicit shell Visual Studio Shell integration - that a VSIX does, **without all the complexity of setting up and maintaing a VSIX project.
Dotnet New Templates in Rider: Look for 'Other`
JetBrains Rider picks up my new template in the New Solution dialog on the bottom of the Template list in the Other section:
Figure 7 - JetBrains Rider shows dotnet new
templates in its New Solution dialog
Template Installation Location (Windows)
Since templates are just an installed component any environment that supports .NET projects can use them to create a new project by checking for installed templates in a folder:
%userprofile\.templateengine\packages
The folder holds NuGet packages which are .zip files so you can peek into the package:
The template details are found in the template configuration file which lives in:
content/templates/YourTemplateName/template.config/template.json
which holds the template detail information that can be used to display the template name and other details which would allow for integration in other tooling.
Summary
Creating project templates can be very productive for certain scenarios. In my scenario of allowing users to easily create new Addins for Markdown Monster, I certainly want to make that process as easy as possible. Nothing sucks more than trying to get stuck before you ever get your first bit of code to run. Project templates make it possible to create a new project that's ready to run with just a few dotnet CLI commands which is awesome.
Compared to the Visual Studio project template and VSIX process, the new dotnet new
template based approach is much simpler, works with any type of development IDE and is much more flexible in terms of maintaining the template. It's also vastly easier to test, debug and rebuild templates during the template creation process.
For the time being I'll publish both project templates for the Visual Studio VSIX and the dotnet new tooling
. I'm curious what people prefer. I suspect anybody using Visual Studio probably would prefer the Extension manager for installation over jumping out to the command line, but even if you do, it's literally 2 short CLI commands to create a new project which is hardly difficult.
I think going forward, the .NET Template system is going to be the way to go, but for the time being I guess I'll have both the Visual Studio Extension and the dotnet new
template in place. At least until MS breaks VSIX again. 😄
Resources
Other Posts you might also like