How to Write Plugins in Dynamics 365: A Practical Guide to Pre-operation Event Handling
Dynamics 365 plugins are custom business logic that you can integrate with Dynamics 365 to modify or augment the standard behavior of the platform. This article focuses on a specific, crucial aspect of plugin development: handling pre-operation events. We’ll dive into the process of writing plugins that execute before the core operation of a Dynamics 365 action, allowing you to validate data, prevent operations from occurring under certain conditions, and modify data before it’s committed to the database. This deep dive provides practical examples and step-by-step instructions to empower you with the knowledge to create robust and effective pre-operation plugins. By the end of this article, you’ll understand how to leverage pre-operation events to enhance your Dynamics 365 implementations.
Table of Contents
- Understanding Pre-operation Events
- Setting Up Your Development Environment
- Writing a Basic Pre-operation Plugin
- Advanced Pre-operation Techniques: Modifying Data and Preventing Execution
- Debugging and Deploying Your Plugin
Understanding Pre-operation Events
Pre-operation events in Dynamics 365 occur before the system’s core logic executes for a given operation, such as creating, updating, or deleting a record. This pivotal timing provides a unique opportunity to intercept and influence the outcome of these operations. Imagine a scenario where you need to prevent the creation of a new account if a mandatory field is missing or if a duplicate account already exists. Pre-operation plugins are perfectly suited for these scenarios. They allow you to inspect the incoming data, perform validations, and take actions before the data is written to the database. There are key advantages to using pre-operation events. These include the ability to cancel an event to prevent undesired data modifications, modify the target entity’s attributes, and perform additional checks that native Dynamics 365 workflows cannot handle.
Dynamics 365 classifies plugin execution in terms of stages. The pre-operation stage is typically represented by stage codes 20 and 40. Stage 20 is the “PreValidation” stage, where you primarily focus on validating the input data. Stage 40 is the “PreOperation” stage, where you can modify the input data or cancel the operation based on your custom logic. Choosing the correct stage is critical for optimal plugin performance. For example, if you only need to validate data, using stage 20 is more efficient than using stage 40, as it avoids unnecessary processing.
Example 1: Preventing Account Creation Without a Primary Contact
Let’s consider a scenario where you want to prevent the creation of a new account record if a primary contact is not specified. You can achieve this using a pre-operation plugin triggered on the ‘Create’ message of the ‘account’ entity.
using Microsoft.Xrm.Sdk;
using System;
namespace PreOperationPlugins
{
public class PreventAccountCreation : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
// Check if the target entity is an account and the message is 'Create'.
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "account")
{
// Check if the primarycontactid is present.
if (!entity.Attributes.Contains("primarycontactid"))
{
throw new InvalidPluginExecutionException("Cannot create an account without a primary contact. Please specify a primary contact.");
}
}
}
}
}
}
In this example, the plugin checks if the primarycontactid attribute is present in the account entity. If it’s missing, an InvalidPluginExecutionException is thrown, preventing the account creation. This exception will show a user-friendly error message to the user in Dynamics 365.
Example 2: Validating Account Name Length
This example demonstrates how to validate the length of the account name before creation or update. It will throw an exception if the account name exceeds a certain number of characters.
using Microsoft.Xrm.Sdk;
using System;
namespace PreOperationPlugins
{
public class ValidateAccountNameLength : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "account")
{
if (entity.Attributes.Contains("name"))
{
string accountName = entity.GetAttributeValue<string>("name");
if (accountName.Length > 100)
{
throw new InvalidPluginExecutionException("Account name cannot exceed 100 characters.");
}
}
}
}
}
}
}
This plugin retrieves the account name from the entity and checks its length. If the length exceeds 100 characters, an InvalidPluginExecutionException is thrown, preventing the operation. This helps enforce data quality rules.
Expert Tip: Always use early-bound types (generated classes representing your Dynamics 365 entities) when working with entities in your plugins. This improves code readability and reduces the risk of errors due to typos in attribute names. You can generate these classes using the CrmSvcUtil tool.
Setting Up Your Development Environment
Before you can start writing plugins, you need to set up your development environment. This involves installing the necessary tools and referencing the Dynamics 365 SDK assemblies in your project. The primary tool you’ll need is Visual Studio. You can use Visual Studio 2017 or later. Make sure you have the .NET Framework development workload installed.
- Install Visual Studio: Download and install Visual Studio from the Microsoft website. Choose a version that supports .NET Framework 4.6.2 or later.
- Create a new Class Library (.NET Framework) project: In Visual Studio, create a new project. Select “Class Library (.NET Framework)” as the project template. Choose a meaningful name for your project, such as “Dynamics365.Plugins”. Ensure you select the correct .NET Framework version (4.6.2 or later).
- Add References to Dynamics 365 SDK Assemblies: The Dynamics 365 SDK provides the necessary assemblies for interacting with the Dynamics 365 platform. You can obtain these assemblies through NuGet or by downloading the SDK from Microsoft. The most crucial assemblies are:
Microsoft.Crm.Sdk.Proxy.dllMicrosoft.Xrm.Sdk.dll
- Sign your Assembly: Plugins must be signed with a strong name to be deployed to Dynamics 365. In Solution Explorer, right-click on your project, select “Properties”, go to the “Signing” tab, and check the “Sign the assembly” checkbox. Create a new strong name key file.
Example 1: Adding References Manually (Alternative to NuGet)
If you prefer to add the references manually instead of using NuGet, you can download the Dynamics 365 SDK from the Microsoft website. After extracting the SDK, you can find the necessary assemblies in the \bin\coretools folder. To add the references, right-click on your project in Solution Explorer, select “Add”, then “Reference”. Browse to the location of the assemblies and select them.
Specifically, to add the Microsoft.Xrm.Sdk.dll manually, perform these steps:
- Right-click on your project in Solution Explorer.
- Select “Add” -> “Reference…”.
- In the Reference Manager dialog, click the “Browse” button.
- Navigate to the directory where you extracted the Dynamics 365 SDK (e.g.,
C:\SDK\bin\coretools). - Select the
Microsoft.Xrm.Sdk.dllfile. - Click “Add”.
- Click “OK” to close the Reference Manager dialog.
Example 2: Setting the Target Framework
Ensure your project targets a compatible .NET Framework version. Dynamics 365 typically supports .NET Framework 4.6.2 or later. To check and modify the target framework:
- Right-click on your project in Solution Explorer and select “Properties”.
- In the project properties window, go to the “Application” tab.
- In the “Target framework” dropdown, select “.NET Framework 4.6.2” (or a later compatible version).
Choosing the correct .NET Framework version is crucial for compatibility with the Dynamics 365 platform. Using an incompatible version can lead to deployment errors or unexpected behavior.
Expert Quote: “The key to successful plugin development is a well-configured development environment. Ensure you have the correct SDK assemblies referenced and that your project is signed with a strong name.” – Dynamics 365 Expert
Writing a Basic Pre-operation Plugin
Now that your development environment is set up, let’s write a basic pre-operation plugin. This plugin will demonstrate the fundamental structure and components of a Dynamics 365 plugin. Every plugin must implement the IPlugin interface, which defines a single method: Execute. This method is the entry point for your plugin’s logic.
Here’s a step-by-step guide to creating a basic pre-operation plugin:
- Create a new class: In your Visual Studio project, create a new class that implements the
IPlugininterface. Name the class something descriptive, such as “PreOperationAccountPlugin”. - Implement the
IPlugininterface: Add the necessaryusingstatements (Microsoft.Xrm.SdkandSystem), and implement theExecutemethod. - Obtain the execution context: Inside the
Executemethod, obtain theIPluginExecutionContextfrom theIServiceProvider. The execution context provides information about the current operation, such as the entity being processed, the message being executed, and the user who initiated the operation. - Implement your business logic: Add your custom logic to the
Executemethod. This could involve validating data, modifying attributes, or preventing the operation from occurring. - Register the plugin: After writing the plugin, you need to register it with Dynamics 365. This involves uploading the plugin assembly to Dynamics 365 and configuring a step that specifies when and how the plugin should execute. We’ll cover registration in more detail later.
Example 1: A Simple Pre-Operation Plugin that Logs a Message
This example demonstrates the basic structure of a pre-operation plugin. It simply logs a message to the tracing service, which can be helpful for debugging.
using Microsoft.Xrm.Sdk;
using System;
namespace PreOperationPlugins
{
public class SimplePreOperationPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the tracing service
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
// Obtain the execution context
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
tracingService.Trace("SimplePreOperationPlugin: Plugin execution started.");
// Add your business logic here
tracingService.Trace("SimplePreOperationPlugin: Plugin execution completed.");
}
}
}
In this example, the ITracingService is used to log messages. This is useful for debugging and troubleshooting your plugin. Remember to register this plugin for a specific entity and message (e.g., Create of Account) to see the trace messages when that event occurs in Dynamics 365.
Example 2: Accessing Input Parameters in a Pre-Operation Plugin
This example demonstrates how to access the input parameters of a pre-operation event. Input parameters contain the data that is being passed to the Dynamics 365 platform for processing. The most common input parameter is the “Target” parameter, which contains the entity being created or updated.
using Microsoft.Xrm.Sdk;
using System;
namespace PreOperationPlugins
{
public class AccessInputParametersPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
tracingService.Trace($"Entity Logical Name: {entity.LogicalName}");
if (entity.Attributes.Contains("name"))
{
string accountName = entity.GetAttributeValue<string>("name");
tracingService.Trace($"Account Name: {accountName}");
}
}
else
{
tracingService.Trace("No Target entity found in InputParameters.");
}
}
}
}
This plugin checks if the “Target” parameter exists and if it’s an entity. If so, it retrieves the entity and logs its logical name and the account name (if it exists). This demonstrates how to access and use the data that is being passed to the plugin.
Advanced Pre-operation Techniques: Modifying Data and Preventing Execution
Beyond basic validation, pre-operation plugins can be used for more advanced scenarios, such as modifying data before it’s saved or preventing an operation from occurring altogether. Modifying data can be useful for standardizing data entry, automatically calculating values, or enriching data with information from external sources. Preventing execution is crucial for enforcing business rules and ensuring data integrity.
Modifying Data in a Pre-Operation Plugin
To modify data in a pre-operation plugin, you need to update the attributes of the entity contained in the “Target” input parameter. These changes will be reflected in the database when the operation is completed. However, it’s crucial to be mindful of potential side effects and ensure that your modifications don’t introduce inconsistencies or break other business rules.
Preventing Execution in a Pre-Operation Plugin
To prevent an operation from occurring, you can throw an InvalidPluginExecutionException. This exception will halt the execution pipeline and display an error message to the user. The error message should be clear and informative, explaining why the operation was prevented and what the user can do to resolve the issue.
Example 1: Automatically Setting the Account Number
This example demonstrates how to automatically set the account number when a new account is created. It assumes that you have a custom field called “new_accountnumber” on the account entity.
using Microsoft.Xrm.Sdk;
using System;
namespace PreOperationPlugins
{
public class SetAccountNumberPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "account" && context.MessageName == "Create")
{
// Generate a unique account number
string accountNumber = "ACC-" + Guid.NewGuid().ToString().Substring(0, 8).ToUpper();
// Set the account number on the entity
entity["new_accountnumber"] = accountNumber;
tracingService.Trace($"Account Number set to: {accountNumber}");
}
}
}
}
}
This plugin generates a unique account number and sets it on the “new_accountnumber” attribute of the account entity. The account number is generated using a combination of a prefix (“ACC-“) and a random GUID. This ensures that each account has a unique identifier. Note: this logic assumes you’ve created the field `new_accountnumber` yourself.
Example 2: Preventing Account Deletion if it Has Open Opportunities
This example demonstrates how to prevent the deletion of an account if it has any open opportunities. It uses the Organization Service to query for open opportunities associated with the account.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
namespace PreOperationPlugins
{
public class PreventAccountDeletion : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is EntityReference)
{
EntityReference entityReference = (EntityReference)context.InputParameters["Target"];
if (entityReference.LogicalName == "account" && context.MessageName == "Delete")
{
// Query for open opportunities associated with the account
QueryExpression query = new QueryExpression("opportunity");
query.ColumnSet = new ColumnSet(false); // Return no attributes
query.Criteria.AddCondition("accountid", ConditionOperator.Equal, entityReference.Id);
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, 0); // 0 = Open
EntityCollection opportunities = service.RetrieveMultiple(query);
if (opportunities.Entities.Count > 0)
{
throw new InvalidPluginExecutionException("Cannot delete account with open opportunities. Please close the opportunities first.");
}
}
}
}
}
}
This plugin queries for open opportunities associated with the account being deleted. If any open opportunities are found, an InvalidPluginExecutionException is thrown, preventing the deletion. The IOrganizationService is used to interact with the Dynamics 365 platform and retrieve data. This demonstrates how to use the Organization Service to perform more complex validations in your plugins.
Comparison of Pre-Validation and Pre-Operation Stages
| Feature | Pre-Validation (Stage 20) | Pre-Operation (Stage 40) |
|---|---|---|
| Primary Purpose | Data Validation | Data Modification and Complex Business Logic |
| Data Modification | Typically not allowed | Allowed |
| Performance Impact | Lower (focused on validation) | Potentially higher (due to more complex logic) |
| Use Cases | Simple data validation rules, such as checking for required fields or invalid characters | Complex business rules, data enrichment, preventing operations based on external data |
Expert Tip: When throwing an InvalidPluginExecutionException, always provide a clear and user-friendly error message. This will help users understand why the operation failed and what they can do to resolve the issue. A good error message can significantly improve the user experience.
Debugging and Deploying Your Plugin
After writing your plugin, you need to debug and deploy it to Dynamics 365. Debugging allows you to test your plugin and identify any issues before deploying it to a production environment. Deployment involves registering the plugin with Dynamics 365 and configuring the steps that trigger its execution.
Debugging Your Plugin
Debugging Dynamics 365 plugins can be challenging because the plugin executes within the Dynamics 365 server process. However, there are several techniques you can use to debug your plugins effectively:
- Tracing Service: Use the
ITracingServiceto log messages to the trace log. This allows you to track the execution flow of your plugin and identify any errors or unexpected behavior. You can view the trace log in Dynamics 365 under Settings -> Customization -> Developer Resources. - Plugin Registration Tool (PRT) Profiler: The Plugin Registration Tool includes a profiler that allows you to capture the execution context of a plugin. This can be helpful for debugging complex plugins or plugins that interact with external systems. You can start a profile session using the Plugin Registration Tool, then execute the operation that triggers your plugin. The profiler will capture the execution context, which you can then use to debug your plugin in Visual Studio.
- Remote Debugging: In some scenarios, you might be able to use remote debugging to attach a debugger to the Dynamics 365 server process. However, this requires access to the server and is typically only used in development or test environments.
Deploying Your Plugin
To deploy your plugin, you need to register it with Dynamics 365. This involves the following steps:
- Register the Assembly: Use the Plugin Registration Tool to register the plugin assembly with Dynamics 365. This involves uploading the assembly file to Dynamics 365.
- Register a Step: Create a step that specifies when and how the plugin should execute. This includes specifying the entity, message, stage, and any filtering attributes. The step defines the conditions under which your plugin will be triggered.
- Configure Security: Ensure that the plugin has the necessary security permissions to access the data and perform the operations it needs to. This might involve impersonating a specific user or granting the plugin specific privileges.
Example 1: Debugging with the Tracing Service
To use the tracing service, you need to obtain an instance of the ITracingService from the IServiceProvider and use the Trace method to log messages. For example:
using Microsoft.Xrm.Sdk;
using System;
namespace PreOperationPlugins
{
public class DebuggingExample : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
try
{
tracingService.Trace("DebuggingExample: Plugin execution started.");
// Your plugin logic here
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
tracingService.Trace($"Entity Logical Name: {entity.LogicalName}");
}
tracingService.Trace("DebuggingExample: Plugin execution completed.");
}
catch (Exception ex)
{
tracingService.Trace($"DebuggingExample: An error occurred: {ex.ToString()}");
throw;
}
}
}
}
This example demonstrates how to use the tracing service to log messages at the beginning and end of the plugin execution, as well as to log any exceptions that occur. These messages will be written to the trace log, which you can then view in Dynamics 365. Always wrap your code in try-catch blocks and log the exception details, as this can be invaluable for debugging.
Example 2: Registering a Plugin Step Using the Plugin Registration Tool
To register a plugin step using the Plugin Registration Tool:
- Connect to your Dynamics 365 organization using the Plugin Registration Tool.
- Select your plugin assembly in the list of registered assemblies.
- Right-click on the plugin and select “Register New Step”.
- In the “Register New Step” dialog, configure the following settings:
- Message: Select the message that will trigger the plugin (e.g., Create, Update, Delete).
- Entity: Select the entity that the plugin will operate on (e.g., Account, Contact, Opportunity).
- Stage of execution pipeline: Select the appropriate stage (e.g., PreValidation, PreOperation).
- Event Handler Mode: Select “Synchronous” for pre-operation plugins.
- Filtering Attributes: (Optional) Specify any attributes that should be filtered to trigger the plugin only when those attributes are modified.
- Click “Register New Step” to save the step.
Ensure that you select the correct message, entity, and stage for your plugin. Incorrectly configured steps can lead to unexpected behavior or performance issues. Also, be mindful of the execution order of your plugins. If you have multiple plugins registered for the same entity and message, the order in which they execute can be crucial.
By understanding how to debug and deploy your plugins effectively, you can ensure that your custom business logic is reliable and performs as expected. The tracing service and Plugin Registration Tool are invaluable resources for plugin development and deployment.
Article Monster
Email marketing expert sharing insights about cold outreach, deliverability, and sales growth strategies.