-
Notifications
You must be signed in to change notification settings - Fork 16
T4 Templates
DynamicsCrm.DevKit uses T4 templates to generate C# item template code inside Visual Studio. Use this area when the default generated code works but your team wants a different class layout, tracing pattern, guard clause style, base class, helper calls, or test scaffold.
Custom T4 templates are managed from the item template wizard. Open a supported item template, select a template from the Template dropdown, then use Customize to edit, save, review, and reuse your own template.
| Item template | Default T4 template | Help |
|---|---|---|
| C# Plugin Class | Plugin.tt |
C# Plugin T4 Customize |
| C# Workflow Class | Workflow.tt |
C# Workflow T4 Customize |
| C# Custom Action Class | CustomAction.tt |
C# Custom Action T4 Customize |
| C# Custom API Class | CustomApi.tt |
C# Custom API T4 Customize |
| C# Data Provider Class |
DataProviderCreate.tt, DataProviderUpdate.tt, DataProviderDelete.tt, DataProviderRetrieve.tt, DataProviderRetrieveMultiple.tt
|
C# Data Provider T4 Customize |
| C# Test Class | Test.tt |
C# Test T4 Customize |
| C# UI Test Class | UiTest.tt |
C# UI Test T4 Customize |
| Review generated code | Processed output preview | Review T4 Output |
- The item wizard builds a
T4Contextfrom the selected class name, namespace, entity, message, stage, execution mode, order, data source, or test target. - DynamicsCrm.DevKit loads the selected custom template. If the selected template is
Default, the embedded.tttemplate is used. - The Visual Studio T4 engine processes the template with
Context. - The generated code is written into the new item.
DynamicsCrm.DevKit automatically injects the T4 parameter directive, so custom templates can use Context directly:
<#=Context.PluginNameSpace#>
<#=Context.ClassWithOrder#>
<#=Context.PluginMessage#>This list is based on the current DynamicsCrm.DevKit.Shared.Models.T4Context model.
| Property | Description |
|---|---|
Context.PluginNameSpace |
Namespace selected by the item wizard. |
Context.PluginMessage |
Dataverse message such as Create, Update, Delete, CreateMultiple, or UpdateMultiple. |
Context.PluginLogicalName |
Entity logical name. |
Context.PluginSchemaName |
Entity schema name. |
Context.PluginStage |
Plugin stage selected by the wizard. |
Context.PluginExecution |
Synchronous or Asynchronous. |
Context.PluginOrder |
Execution order. |
Context.PluginComment |
Registration comment generated from the wizard selection. |
Context.Class |
Base class name selected by the user. |
Context.PluginSharedNameSpace |
Shared project namespace. |
Context.DataSource |
Data source name for data provider templates. |
Context.ProxyTypes |
Proxy types namespace value when available. |
Context.TestTargetFullClassName |
Selected target class for generated tests. |
| Property | Description |
|---|---|
Context.EntityLogicalName |
Alias for PluginLogicalName; returns empty string when null. |
Context.EntitySchemaName |
Alias for PluginSchemaName; returns empty string when null. |
Context.EntityDisplayName |
Entity display name from metadata. |
Context.EntitySetName |
Web API collection name from metadata. |
Context.EntityTypeCode |
Dataverse object type code. |
Context.IsCustomEntity |
True when the selected entity is custom. |
| Helper | Description |
|---|---|
Context.ClassWithOrder |
Class name plus execution order suffix when order is not 1. |
Context.FullClassName |
Namespace plus ClassWithOrder. |
Context.RegistrationName |
Same value as FullClassName; intended for registration attributes. |
Context.HasTestTarget |
True when a test target class was selected. |
Context.HasPluginTestGuardContext |
True when a test template has target class, stage, message, entity, and execution mode. |
| Helper | Description |
|---|---|
Context.IsPreValidation |
True when stage is PreValidation. |
Context.IsPreOperation |
True when stage is PreOperation. |
Context.IsPostOperation |
True when stage is PostOperation. |
Context.StageNumber |
Returns Dataverse stage number: 10, 20, or 40. |
| Helper | Description |
|---|---|
Context.IsSynchronous |
True when execution mode is synchronous. |
Context.IsAsynchronous |
True when execution mode is asynchronous. |
| Helper | Description |
|---|---|
Context.IsCreateMessage |
True for Create. |
Context.IsUpdateMessage |
True for Update. |
Context.IsDeleteMessage |
True for Delete. |
Context.IsCreateMultipleMessage |
True for CreateMultiple. |
Context.IsUpdateMultipleMessage |
True for UpdateMultiple. |
| Helper | Description |
|---|---|
Context.HasPreImage |
True when the selected message supports a pre image. |
Context.HasPostImage |
True when the selected message and stage support a post image. |
Context.IsPluginSupportedPreImage |
Original pre-image helper used by older templates. |
Context.IsPluginSupportedPostImage |
Original post-image helper used by older templates. |
| Property | Description |
|---|---|
Context.GeneratedDate |
Date and time when the template is processed, formatted as yyyy-MM-dd HH:mm:ss. |
Context.DevKitVersion |
DynamicsCrm.DevKit version used during generation. |
Insert a context value:
namespace <#=Context.PluginNameSpace#>
{
public class <#=Context.ClassWithOrder#>
{
}
}Conditionally generate code:
<#if (Context.IsAsynchronous) {#>
// Async-only code
<#}#>Generate repeated code:
<#for (var i = 1; i <= 3; i++) {#>
private void Step<#=i#>() { }
<#}#>When a custom T4 template generates code that depends on external NuGet packages, use a Package Project Template, not a normal Server Project. Package projects are designed for plugin assemblies with dependencies: add the NuGet package to the package project, then use the package deploy scripts so the dependency is included during deployment.
For example, the test project contains this custom plugin template:
DynamicsCrm.DevKit.Tests\TestSdkProjects\Dev.DevKit.Package\t4\Niam.XRM.Framework.t4
That template generates a plugin using Niam.XRM.Framework:
using Microsoft.Xrm.Sdk;
using Niam.XRM.Framework.Interfaces.Plugin;
using Niam.XRM.Framework.Plugin;
using <#=Context.PluginSharedNameSpace#>;
namespace <#=Context.PluginNameSpace#>
{
[CrmPluginRegistration("<#=Context.PluginMessage#>", "<#=Context.EntityLogicalName#>",
StageEnum.<#=Context.PluginStage#>,
ExecutionModeEnum.<#=Context.PluginExecution#>,
"",
"<#=Context.RegistrationName#>Package",
<#=Context.PluginOrder#>,
IsolationModeEnum.Sandbox,
PluginType = PluginType.Plugin)]
public class <#=Context.ClassWithOrder#>Package : PluginBase<Entity>, IPlugin
{
protected override void ExecuteCrmPlugin(IPluginContext<Entity> context)
{
context.TracingService.DebugContext(context.PluginExecutionContext);
new <#=Context.EntitySchemaName#>Operation(context).Execute();
}
}
}The important parts are:
| Template part | Why it matters |
|---|---|
using Niam.XRM.Framework... |
Requires the NuGet package to exist in the project. |
PluginBase<Entity> |
The generated class depends on the framework runtime. |
Context.RegistrationName |
Keeps the registration name tied to the selected namespace, class, and order. |
Context.EntityLogicalName and Context.EntitySchemaName
|
Bind generated code to the selected Dataverse entity. |
Context.HasPreImage and Context.HasPostImage
|
Allow the template to generate image registration only when supported. |
Context.IsAsynchronous |
Allows async-only registration options such as deleting async operation records. |
Recommended flow:
- Create a Package Project.
- Add the required NuGet package, for example
Niam.XRM.Framework. - Open the C# Plugin item wizard.
- Open Customize from the Template dropdown.
- Save the custom T4 template, for example
Niam.XRM.Framework. - Generate the plugin item using that template.
- Deploy from the package project so NuGet dependencies are included.
Custom templates are stored in the solution-level DynamicsCrm.DevKit.Config.json file as compressed template bodies. This allows the team to keep template choices with the solution when that file is committed.