With the release of Visual Studio 2013 last October, we introduced the concept of Scaffolding to Web Application projects. Scaffolding is the framework on which code generation for MVC and WebAPI is built. For more information on Scaffolding or the MVC Scaffolders check the following blog post: http://www.asp.net/visual-studio/overview/2013/aspnet-scaffolding-overview.
However, the true potential for the scaffolding framework comes from the new extensibility surface released in Update 2. With this new functionality, any VSIX can code against the Scaffolding API surface and have their scaffolds added to the Add New Scaffold Dialog. This blog post will walk through the creation of a custom scaffolder.
To get started make sure you have the following installed on your machine:
- Visual Studio 2013 Pro+
- Visual Studio 2013 Update 2 RC (or later)
Creating a New Scaffolder Project Using Sidewaffle
- Go to create a new project.
- Click on the C#->Extensibility->Sidewaffle Node.
- Select new “Basic Scaffolder”.
- Input the desired name of your Scaffolder.
- Create the Project.
Now, that your custom scaffolder solution has been created, you will notice that it has two projects in the solution, one named BasicScaffolder1 and the other BasicScaffolder1Extension (with the Italics substituted with the name you gave your solution). The former is a class library project which will contain most of the code for your custom scaffolder, the latter is the VSIX project which will build the vsix that you will be able to share with others (or upload to the VS Extension Gallery). The template has also added dependencies to the Microsoft.AspNet.Scaffolding.12.0 and Microsoft.AspNet.Scaffolding.EntityFramework.12.0 dlls necessary to interface with the scaffolding framework. Finally, the template has configured your project and solution settings to correctly launch the experimental instance of VS to test out your Scaffolder on F5.
Fixing the Metadata
The first step is filling out the metadata of your Custom Scaffolder. There are two files that need to be updated, the source.extension.vsixmanifest in the VSIX project (the one ending in Extention), and the CustomCodeGeneratorFactory class in the Class Library Project.
Within the source.extension.vsixmanifest (which will look very familiar to anyone who has built a VSIX extension before), make sure to modify all the values that you want (these are the values that will show up on the VS Extension Gallery), but leave the “Tags” unchanged. Having this set to “Scaffolding” will allow for the VSIX to show up in the Tools->Scaffolders node in the VS Extension Gallery.
In the CodeGeneratorFactory class, there is a CodeGeneratorInfo static field that contains the data that will be displayed in the Add Scaffold Dialog.
privatestatic CodeGeneratorInformation _info = new CodeGeneratorInformation(
//Text for the selection in the main Add Scaffold Selection
displayName: "Custom Scaffolder",
//On the right sidebar, this is the description
description: "This is a custom scaffolder.",
//On the right sidebar this is the author
author: "Microsoft",
//On the right sidebar this is the version
version: new Version(1, 1, 0, 0),
//On the right sidebar this is the id value
id: typeof(CustomCodeGenerator).Name,
//In the main Selection area this is the icon
icon: ToImageSource(Resources._TemplateIconSample),
//These are the right click gestures this scaffolder will display for
gestures: new[] { "Controller", "View", "Area" },
//These are the categories the scaffolder will display in
categories: new[] { Categories.Common, Categories.MvcController, Categories.Other });
And this is the Dialog where that info will be rendered:
Customizing your Scaffolding UI
With your metadata set, it is time to actually implement your scaffolder. The class that actually implements your scaffolder is the CodeGenerator class. This class implements the ICodeGenerator Interface. The interface specifies two methods, ShowUIAndValidate() and GenerateCode(). These are the two methods that the scaffolding core will use to run your scaffolder.
The ShowUIAndValidate() method is run after your scaffolder is selected from the Add Scaffold Dialog. This code should launch any UI you wish to show for the end user to provide input into your scaffolder, and then validate both the state of the project and the inputted values before returning. No changes to the project should be made within this method call.
In the sidewaffle project we have added a basic UI that allows the user to select a .cs class from their current project. However, more complex examples can be seen using the WebAPI or MVC scaffolders shipped with Visual Studio. Note that in the template, that the UI selection is persisted after the dialog is accepted, so that the value can be used in the Code Generation part of the Scaffolder, this is not done by default, and if you want to have the state persisted, you should make sure that the values are perserved.
Final Note: There is no requirement to show UI, and you can skip straight to the GenerateCode() method by just returning true. However, this would not allow any user input into your scaffolder.
Writing Your Scaffolding Code Generator
Now that the scaffolder has the user inputs necessary to do your scaffolding (and you have persisted that data), it is time to modify the project. There are several types of actions built into the Scaffolding Framework to help you build your scaffolder, these are in the ICodeGeneratorActionsService interface of the Microsoft.AspNet.Scaffolding.12.0 dll. Of course you can create your own actions, but the benefit of using the method calls exposed by the Scaffolding Framework is that they can be Rolled Back in case any step of the Scaffolding Fails. Rolling back will return the project to the state it was in prior to the scaffolder being invoked.
The subsections that follow go over the most common ActionServices that Scaffold Authors will use. Full documentation of all of these methods can be found in the Object Browser when expanding the Scaffolding dll.
Adding a static file
The most basic action a scaffolder can make to a project is adding a static file to the project. The Action Service exposed by the Scaffolding Framework to accomplish this is AddFile, which takes in a DTE.Project (which can be gotten from the from CodeGeneratorContext.ActiveProject.get()), the relative path of the file in the project, the absolute path to the file on disk, and a Boolean indicating if the file should be overwritten if it already exists.
Creating a Folder
Pairing with the adding of a static file, the scaffolder can also create a new folder in the project. Using the AddFolder() method in the Action Service, you can create a new folder in the targeted project by passing in the Project DTE object, and a relative path to where the folder should be added.
Using T4 Templates to add a file or update a file
This Action is more interesting than the previous two, as we will be generating a new file for the targeted project based on a t4 template. On the surface, this method is similar to the AddFile() method described above, but there are some slight differences.
First, the path to the template file can either be absolute, or it can be relative to the “Templates/YourScaffolderCodeGenerator” filepath. Remember also that the .cs.t4 or .vb.t4 extension is not needed in the path, it will be resolved at execution time to match the project language of the project being scaffolded. If you only wish to support certain languages go to the IsSupported() method of the CodeGeneratorFactory class and make sure to return false for all non-supported language types.
Additionally, the method takes in a dictionary of key/value pairs to use as the parameters of the t4 template.
The example below shows how this method is invoked, the T4 template being invoked, and what the generated class is:
publicoverridevoid GenerateCode()
{
var parameters = new Dictionary<string, object>()
{
{ "ClassName", "DemoClass" },
{ "NameSpace", "DemoNamespace" }
};
this.AddFileFromTemplate(Context.ActiveProject,
"DemoFile",
"CustomTextTemplate",
parameters,
skipIfExists: false);
}
Here is my CustomTextTemplate.cs.t4:
<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ parameter name="ClassName" type="System.String" #>
<#@ parameter name="NameSpace" type="System.String" #>
namespace<#= NameSpace #>
{
publicclass<#= ClassName #>
{
public<#= ClassName #>()
{
//This is where you will instantiate this class
}
}
}
And the generated code from my Scaffolder is:
Of course that is a very basic t4 template example, but it illustrates how the process will work. In creating your own scaffolders please feel free to make the t4 templates as complex as your scaffolding requires.
Adding NuGet Packages
Another common action that a custom scaffolder may wish to perform is installing a NuGet package (either one included with the VSIX, or one from a remote repository like the NuGet Gallery). There are three ways to accomplish this.
If you need a NuGet package installed before any of your code generation is done, add the following override in your CustomCodeGenerator class:
publicoverride IEnumerableDependencies
{
get
{
Listt = new List ();
t.Add(new NuGetPackage("jquery",
"1.6.4",
new NuGetSourceRepository()));
return (IEnumerable)t;
}
}
If you just want a NuGet package installed to be able to run your generated code in the user’s project, modify your CreateInstance() method in your CustomCodeGeneratorFactory class:
publicoverride ICodeGenerator CreateInstance(CodeGenerationContext context)
{
context.Packages.Add(new NuGetPackage("jquery",
"1.6.4",
new NuGetSourceRepository()));
returnnew CustomCodeGenerator(context, Information);
}
Finally, you can always do installations directly. This is useful in cases where you want to not just fail if the install fails, but have different code paths to follow in that scenario. The code below is used to install the jquery 1.6.4 package to the project running the scaffolder, assuming that the package (and any version thereof) is already installed. It has been encapsulated in a try/catch block in the case that the package is already installed, as we do not want the scaffolder to fail if the required dependency is already installed. Again this code is in the GenerateCode() block in the CodeGenerator class.
NuGetPackage demoPackage = new NuGetPackage("jquery",
"1.6.4",
new NuGetSourceRepository());
try
{
var nugetService = (INuGetService)Context.ServiceProvider.GetService(typeof(INuGetService));
nugetService.InstallPackage(Context.ActiveProject, demoPackage);
}
catch (Exception e)
{
System.Console.Out.Write(e);
}
Next Steps
Now that you have the basics of creating a scaffolder down, here are some additional resources for what to do next:
- Deploying your Scaffolder to the VSIX gallery - http://msdn.microsoft.com/en-us/library/ff363239.aspx (make sure to have the category type as Tools->Scaffolding, as that will allow it to be more discoverable to end-users)
- Look at a sample scaffolder – Web Forms Scaffolder Source Code
Additionally you can look to create more complex scaffolders using the following services:
- ICategoryRegistrationService – to add new Categories in the Add Scaffold Dialog
- IServiceRegistrar – to add new ActionServices that you can invoke during scaffolding
- IRollbackService – to make the services registered above be able to use the Scaffolding rollback feature
- The Scaffolding.EntityFramework dll – to help with the processing of EF models (this is used by the MVC and WebAPI Entity Framework Scaffolders to create the controllers and for MVC the views)