Upgrading CodeSmith Generator templates
CodeSmith Generator templates are designed to stay backward compatible. In rare cases, compatibility is broken because the benefit clearly outweighs the cost. Use this page after you upgrade Generator to make the move as smooth as possible.
Recompile template assembly references
Follow these steps if a custom template references an assembly that itself references CodeSmith Generator, or if you use one of the template frameworks such as PLINQO or CSLA.
Note
Generator is compiled against the .NET Framework 4.8, so any code that uses the Generator API must target .NET Framework 4.8. This applies only to your Generator class libraries. Your templates can still generate code for any platform, or any text at all.
-
Open the Visual Studio solution that contains your template source code. For the template frameworks, the source code is in the templates'
Sourcefolder. -
Update every project that references
CodeSmith.Engineto target .NET Framework 4.8. -
Update every assembly reference to a Generator assembly:
- In Solution Explorer, right-click the project and select Add Reference.
- Select the Generator assembly. If it isn't listed, browse to it in the Generator program files folder.
- Click OK.
-
Add a reference to
CodeSmith.Coreto every project that references Generator assemblies. It's in thebinfolder of the Generator program files folder. -
Rebuild the solution.
Warning
If you're updating a template framework solution, make sure each project copies its compiled assembly to the right folder after the build (for example, from
\ProjectName\bin\Debugto the templates'\Commonfolder). -
Regenerate your existing Generator project files.
Update Generator projects
Make sure your Generator project files point at the current templates. Open Manage Outputs and check that each template location points to the templates that came with your current version of Generator.
Get the current templates
Generator installs the current sample templates, so you don't need to download them separately. They're also available in the CodeSmith Generator Templates repository on GitHub.
The template frameworks, such as PLINQO and CSLA, aren't installed with Generator. Download them from the releases page on GitHub.
Warning
Templates run code on your computer. Only use templates from sources you trust.
The .NET runtime restricts code that was downloaded from the internet or that runs from a network share. Before you run downloaded templates:
- Move the downloaded templates or
.zipfile to a local drive, not a network share. - Unblock the
.zipfile before you extract it, or unblock every assembly (.dll,.exe) before you generate:- In File Explorer, right-click the file and select Properties.
- Select Unblock, then click OK.
Breaking changes to check
SqlCodeTemplate no longer treats TypeCode columns as enumerations
These virtual methods of CodeSmith.BaseTemplates.SqlCodeTemplate and CodeSmith.BaseTemplates.VBSqlCodeTemplate used to treat any column whose name ends with TypeCode as an enumeration:
- GetSqlReaderAssignmentStatement
- GetPropertyName
- IncludeEmptyCheck
- GetCSharpVariableType
- GetVBVariableType
They no longer do, because the special case could cause unexpected bugs in generated code. This affects you only if both of these are true:
- You have a database column whose name ends with
TypeCode. - You use the
Database\DbSnapshotorDatabase\StoredProcedurestemplates, or a custom template that derives fromSqlCodeTemplateorVBSqlCodeTemplateand calls one of these methods.
Prefer the built-in property editors
Generator's property grid is built with WPF. Use its built-in editors rather than Windows Forms UITypeEditors; see Building a custom UITypeEditor for the legacy option.
Reference transitive dependencies
Add an Assembly directive for every transitive dependency of your template. Otherwise the template fails to compile in some scenarios, such as the MSBuild integration.
For example, the CodeSmith.BaseTemplates library references CodeSmith.CustomProperties, but many templates reference only CodeSmith.BaseTemplates. Add both:
<%@ Assembly Name="CodeSmith.BaseTemplates" %>
<%@ Assembly Name="CodeSmith.CustomProperties" %>
Update older templates
Templates and code written for much older versions of Generator may need these changes.
Every template needs a CodeTemplate directive
Every template must have a CodeTemplate directive. It must be the first thing in the file, except for template comments.
Use the current comment syntax
Older templates sometimes used these formats for template comments. They're no longer accepted:
<% // some template header %>
<%-- some comment %>
Use this format for template headers and comments:
<%-- some template header --%>
<%-- some comment --%>
To write a comment into the generated code (rather than a comment in the template), you still use the comment syntax of your template language. For C#:
<% // some C# comment %>
For Visual Basic:
<% ' some VB comment %>
Register sub-templates declaratively
You can still compile sub-templates programmatically, but the Register directive is more efficient.
Replace Validate with GetCustomValidationErrors
If a custom template overrides Validate:
public override void Validate() {
}
change it to override GetCustomValidationErrors and return a list of validation errors:
public override System.Collections.Generic.IEnumerable<ValidationError> GetCustomValidationErrors() {
}
This keeps the template's State property correct, and exceptions thrown in GetCustomValidationErrors() don't affect the generation process.
Update the IMergeStrategy.Merge signature
If you wrote a custom merge strategy, change its Merge method from:
public string Merge(CodeTemplate context, string sourceContent, string templateOutput) {
return templateOutput;
}
to:
public string Merge(CodeSmith.Engine.MergeContext context) {
return context.OutputContent;
}
Then update the implementation to use the properties of MergeContext. MergeContext gives merge strategies (such as the InsertClass merge strategy) more information, and lets more information be added without breaking the API.
Note
Template frameworks such as PLINQO already use the new signature and don't need to be recompiled.
Output encoding
Templates generate UTF-8 output, which is consistent with editors such as Visual Studio. To generate ASCII or another encoding, set the ResponseEncoding attribute of the CodeTemplate directive on your master template. This also keeps your output the same in every culture.
Move from CodeTemplateCompiler to TemplateEngine
CodeTemplateCompiler is a wrapper around TemplateEngine, the template compiler, and works the same as before. Its Cleanup() method has been removed; you can safely delete calls to it.
Moving your code to TemplateEngine is recommended but not required. See Using the Generator SDK.
IEngineHost and DefaultEngineHost changes
IEngineHost and DefaultEngineHost changed to improve in-memory content management. Some methods were renamed to more generic names: a host that works with database objects, for example, doesn't deal with files.
| Previous member | Current member | Action |
|---|---|---|
IEngineHost.GetFileContent(string) |
IEngineHost.GetContent(string, bool) |
Rename your overloads and add the Boolean parameter. |
| None | IEngineHost.ContentExists(string) |
Implement this method if you implement IEngineHost or want to override the default behavior. |
| None | IEngineHost.RegisteredContent |
Implement this property if you implement IEngineHost. |
DefaultEngineHost(string) |
DefaultEngineHost(string) |
The constructor now throws an ArgumentException if the base path doesn't exist. |
DefaultEngineHost.RegisterInMemoryContent(string, string) |
None | Removed. Use RegisteredContent instead, as shown below. |
If you called RegisterInMemoryContent:
var host = new DefaultEngineHost(Path.GetDirectoryName(filePath));
host.RegisterInMemoryContent(filePath, content);
use RegisteredContent instead:
var host = new DefaultEngineHost(Path.GetDirectoryName(filePath));
host.RegisteredContent[filePath] = content;
SchemaExplorer type converters
The individual SchemaExplorer type converters were replaced by a single SchemaObjectFactoryTypeConverter, which converts every SchemaExplorer type that derives from SchemaObjectBase or SchemaObjectCollection. If your template uses a SchemaExplorer type converter like this, remove it:
[TypeConverter(typeof(SchemaExplorer.TableSchemaCollectionTypeConverter))]
TableSchema.Indexes sort order
The TableSchema.Indexes collection is sorted for consistency. Primary keys are no longer listed first.
SchemaExplorer collection enumerators
SchemaExplorer uses generics, so each collection's GetEnumerator() method returns a generic IEnumerator<T> instead of a type-specific enumerator interface. Templates that use the result of GetEnumerator() directly need to change.
| Collection | Previous return type | Current return type |
|---|---|---|
ColumnSchemaCollection |
IColumnSchemaEnumerator |
IEnumerator<ColumnSchema> |
CommandResultColumnSchemaCollection |
ICommandResultColumnSchemaEnumerator |
IEnumerator<CommandResultColumnSchema> |
CommandResultSchemaCollection |
ICommandResultSchemaEnumerator |
IEnumerator<CommandResultSchema> |
CommandSchemaCollection |
ICommandSchemaEnumerator |
IEnumerator<CommandSchema> |
DataObjectBaseCollection |
IDataObjectBaseEnumerator |
IEnumerator<DataObjectBase> |
DataSourceCollection |
IDataSourceEnumerator |
IEnumerator<DataSource> |
ExtendedPropertyCollection |
IExtendedPropertyEnumerator |
IEnumerator<ExtendedProperty> |
IDbSchemaProviderCollection |
IIDbSchemaProviderEnumerator |
IEnumerator<IDbSchemaProvider> |
IndexSchemaCollection |
IIndexSchemaEnumerator |
IEnumerator<IndexSchema> |
MemberColumnSchemaCollection |
IMemberColumnSchemaEnumerator |
IEnumerator<MemberColumnSchema> |
ParameterSchemaCollection |
IParameterSchemaEnumerator |
IEnumerator<ParameterSchema> |
SchemaObjectBaseCollection |
ISchemaObjectBaseEnumerator |
IEnumerator<SchemaObjectBase> |
TableKeySchemaCollection |
ITableKeySchemaEnumerator |
IEnumerator<TableKeySchema> |
TableSchemaCollection |
ITableSchemaEnumerator |
IEnumerator<TableSchema> |
TabularObjectBaseCollection |
ITabularObjectBaseEnumerator |
IEnumerator<TabularObjectBase> |
ViewSchemaCollection |
IViewSchemaEnumerator |
IEnumerator<ViewSchema> |
For example, change:
<% IColumnSchemaEnumerator allTableColumns = SourceTable.Columns.GetEnumerator() %>
to:
<% IEnumerator<ColumnSchema> allTableColumns = SourceTable.Columns.GetEnumerator() %>