Skip to content

T4 Templates

PhuocLe edited this page Jun 20, 2026 · 2 revisions

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.

Supported Templates

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

How It Works

  1. The item wizard builds a T4Context from the selected class name, namespace, entity, message, stage, execution mode, order, data source, or test target.
  2. DynamicsCrm.DevKit loads the selected custom template. If the selected template is Default, the embedded .tt template is used.
  3. The Visual Studio T4 engine processes the template with Context.
  4. 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#>

T4Context Reference

This list is based on the current DynamicsCrm.DevKit.Shared.Models.T4Context model.

Core Properties

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.

Entity Properties

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.

Naming Helpers

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.

Stage Helpers

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.

Execution Mode Helpers

Helper Description
Context.IsSynchronous True when execution mode is synchronous.
Context.IsAsynchronous True when execution mode is asynchronous.

Message Helpers

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.

Image Helpers

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.

Metadata

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.

Basic Syntax

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#>() { }
<#}#>

Example: Package Project With NuGet Framework

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:

  1. Create a Package Project.
  2. Add the required NuGet package, for example Niam.XRM.Framework.
  3. Open the C# Plugin item wizard.
  4. Open Customize from the Template dropdown.
  5. Save the custom T4 template, for example Niam.XRM.Framework.
  6. Generate the plugin item using that template.
  7. Deploy from the package project so NuGet dependencies are included.

Storage

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.

Clone this wiki locally