How to create a multi-layered project with Visual Studio IDE
When managing development, many organizations struggle with governance and look for process improvement tools to add value. Project templates take the guesswork out of managing the development process increasing consistency and decreasing delivery times. They are useful when you want to create many different multi-project solutions with the same file structure or standardize projects within your organization.
Why Work with Project Templates?
Every time we start a new project by using the New Project dialog box within the Visual Studio the files are immediately visible, generated from the project template. The item template is the little piece of code created when new classes or interfaces are developed. These templates are useful if you want to create files in any projects with the same code structure.
Process Improvement Tools in Visual Studio
Using Third Party Templates or Building Them
Within Visual Studio, the are a lot of online templates in “Extensions and Updates” gallery, but one of the biggest collections of templates can be found in SideWaffle Template Pack project. The project is 100% community-driven, and templates are available for almost everything — from Azure WebJobs to AngularJs. You can create your own SideWaffle template and upload it to the project. After approval, it will be available in next version of the extension for others to use.
Creating a Project or Item Template from Existing Code
If you have a ‘role model’ solution or file, it’s possible to create a template from this existing code.
Use “File/Export Template…”
From the wizard, you can pick a single file (for the item template) or project(s). The exported template is located in: {user}\Documents\Visual Studio {version}\My Exported Templates\{templateName}.zip.
Customize Existing Templates Used in the Visual Studio
It is possible to update all the templates already built in Visual Studio. If you want to change the default new class code or the default API controller, find your template here: \Program Files (x86)\Microsoft Visual Studio {version}\Common7\IDE\ItemTemplates\.
After saving the updated template file, clear the template cache so your changes are available the next time you run Visual Studio. Simply open command prompt window as an administrator and execute: devenv /installvstemplates.
Create a Project or Item Template from Scratch
Open Visual Studio and from New Project menu select: \Visual C#\Extensibility.
The files within the project for both item and project template are similar. There are the template files from which the output file will be generated and the *.vstemplate file where all data for the template is set (name, description, icon, category etc.)
Components in Template Projects
Understanding the *.vstemplate xml File
This file contains two required tags:
The TemplateData tag contains the name, description, icon, category, project type and other metadata about your template. For example, if your template is made for Visual Basic then the ProjectType tag will look like:
for C#.
The TemplateContent tag contains all files that will be generated during the creation of new project or item.
Working with the Template Base File
Notice how these files contain a lot of tokens with dollar signs around them. The tokens will be replaced with actual values during generation of the file. For example, the $safeitemrootname$ will be replaced with the name provided by the user in the Add New Item dialog box, with all unsafe characters and spaces removed. The list with all parameters can be found here. You can define your own parameters and use them in the file, but we will cover this later.
Process Improvement Tools at Work: Demo Tutorial
Recently our team (6 developers) started a brand new project. (Let’s call it Project X). Our group led the creation of a solution with 6 assemblies bundles:
- ProjectX.Web — MVC administration website
- ProjectX.API — REST WebAPI project which provides data to the MVC website
- ProjectX.Core — The project contains enums, helpers, extensions and interfaces for the data and business layer classes.
- ProjectX.Models — Here we store all domain and business models.
- ProjectX.Data — The DAL classes implementations live here.
- ProjectX.Business — BLL classes implementations live here.
The architecture looks like this:
Common Team Build Problems
Each team member needed to develop a module, and usually one module operates 1–2 domain objects. Each domain has a repository and service classes. We use the IoC (Inversion of Control) pattern. That means each repository and service implements an interface. For each domain object, the developer needed to create 5 classes. Because we are a team of 6, this resulted in the following problems:
- Frequent merge conflicts in *.proj files
- The same methods were named differently in the services or the repositories. For example: Add() vs Insert() vs Create()
- If 3 domain objects were needed, developers used to create 15 classes in 15 files spread through 4 projects. This tedious work can result in many copy-paste errors mainly in the documentation of these classes.
Process Improvement Tools: Steps to Create an Item Template
Building an item template can help teams avoid merge conflicts, naming challenges and tedious copy/paste errors. We demo the code used in each phase to:
- Define several template files in single Item Template project
- Inject custom logic during generating of the files and distribute them across different projects
- Wrap up everything in VSIX package for a easy use by all team members
Create and Define Your Template Classes
Start a new project, select C# Item template and name it ModelEx. After that, remove the default Class.cs, and add new class DomainModel.cs:
Now that we have a basic domain object, we must create the classes for the Service and for the Repository. Add four more class files and name them IModelService, ModelService, IModelRepository and ModelRepository. For these template files to be generated, we need to register them in our ModelEx.vstemplate file. This is done with the following change:
The TargetFileName attribute is for the actual name of the file after executing of the template. In our case, if the user set “Employee” then the generated files will have following names: Employee.cs, IEmployeeRepository.cs, IEmployeeService.cs, EmployeeRepository.cs and EmployeeService.cs.
In order to continue, we need to use a workaround, because using the $safeitemname$ token in multiple template files can present a challenge. (Read here for more info). Next, we will add a custom parameter in the vstemplate file. This will keep the correct value of the user’s input. Add the following code right after last tag:
This is how we define a custom parameter in the Item/Project templates.
Define Template Files
IModelRepository: From the name, you may be able to guess this is the template file for the repository interface. Add the following code to this file:
ModelRepository: The implementation of the IModelRepository
IModelService: The interface of our service class
ModelService: The implementation of IModelService
Now that we’ve completed our template files, we’re ready to deploy, use and share our item template. The solution explorer should look like this:
Deploying a Custom Project or Item Template using the VSIX Project
Now that we have built an item template, let’s look at how to deploy it. Add a new project to ModelEx solution. Select “Extensibility/VSIX Project,” and name it ModelExDeploy. The entire project is a single source.extension.vsixmanifest file. It provides metadata for the custom template we want to deploy.
- Use the designer to fill all necessary data you want. Right now, we just need to add our template as an asset to the VSIX package.
- Go to Assets tab, and click on New button.Select Microsoft.VisualStudio.ItemTemplate for Type.
- Pick a project in the current solution for Source, and select the ModelEx template project in Project field.
- Click OK, and close the designer.
- Set the ModelExDeploy project as a startup project, and hit F5.
- A new test instance of VS will start where your template is installed and ready to use.
- Open or create solution and from context menu. Select Add/New Item… There you can find our template ready for use. Pick a name. When generating is complete, you can see all five files are ready to use.
Of course when it comes to real projects, every file from these five are located in a different folder or even in a different project in the solution. The IWizard interface must be used during the template generation process.
Using the IWizard Interface
Add Custom Logic and UI to Customize the Template
From the visual studio documentation:
“The IWizard interface methods are called at various times while the template is being created, starting as soon as a user clicks OK on the New Item dialog box. Each method of the interface is named to describe the point at which it is called. For example, Visual Studio calls RunStarted immediately when it starts to create the item, making it a good location to write custom code to collect user input.”
Let’s create a new class library project in our solution and call it ModelExWizard. In order to use the IWizard interface, we need to add references:
- EnvDTE
- EnvDTE80
- Microsoft.VisualStudio.TemplateWizardInterface
- Microsoft.VisualStudio.Shell.14.0
- System.Windows.Forms
Next we must create a new class that implements the IWizard interface:
For now, we will move on and connect the wizard with our template. First, we must register our wizard assembly to GAC, and for that reason it is important to run VS as an administrator.
Go to project properties, and sign the assembly:
Add the following code in the Post-build event section
“C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\gacutil.exe” /if “$(TargetPath)”
“C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe” -T “$(TargetPath)”
The paths can vary depending on your system.
Now you can build the project. If correct, you will be shown this in the output window:
After retrieving the public token, go to ModelEx.vstemplate file and add the following tag:
Set the PublicKeyToken attribute with the token value generated in the build output window of the ModelExWizard project.
Last, register the ModelExWizard assembly as an asset in the source.extension.vsixmanifest file, and include the debugging symbols in order to debug our code.
To enable debugging, command F4 while ModelExDeploy project is selected, and change the following options to true:
- IncludeAssemblyInVSIXContainer
- IncludeDebugSymbolsInVSIXContainer
- IncludeDebugSymbolsInLocalVSIXDeployment
Add the assembly as an asset through the source.extension.vsixmanifest designer. Go to the Assets tab, and click on New button, select:
- Type: Microsoft.VisualStudio.Assembly
- Source: A project in current solution
- Project: ModelExWizard
Add Code to Your IWizard Implementation Class
Now we are ready to add some real code in our IWizard implementation class.
We will create a windows form control where the user will be prompted to choose a project from within the solution for each individual file. The end result will look like this:
All projects are sent to the dropdowns via form’s constructor, and all projects are fetched via VS SDK. Our RunStarted method will look like this:
After closing the form, we can fetch the selected project names picked by the user and set some custom template parameters that we will use later to construct the correct namespaces and usage in our template files.
Modifying for the ModelServices Template
We can use the parameters we set above in all template files. This is how the ModelServices.cs template changed with the custom parameters:
Last, we must move the already generated files to projects selected by the user. By default, templates are created where the user right clicks on the context menu in solution explorer and selects the Add New Item … option. We don’t want this. Instead, we will implement the
ProjectItemFinishedGenerating(ProjectItem projectItem) method.
This method is called for each of our template files. The projectItem object is the actual generated object which lives in the VS solution project. First we will identify which template the projectItem is using by examining the Name property of that object. Then, we will search through all our projects in the solution to find the one selected by the user. When we find the project, we use again the VS SDK to copy the contents of the projectItem object to the project.
project.ProjectItems.AddFromFileCopy(projectItem.FileNames[0]);
Because this is a copy operation, make sure to remove the source file from the solution.
projectItem.Delete()
The final version of this method also adds the files to folders. This will separate them from the other project files. For example, the repository is placed under “Repositories/” folder. The model is placed under “Domain/” folder, and the service goes under “Services/” folder.
With a few clicks, the developer can create a fully functional domain, repository and service classes.
The full source code of the project can be found here.
Innovate with us. Click here to access all of our free resources.
Authored by Deyan Stoynov.