Below is a step-by-step guide to creating plugins in Microsoft Dynamics 365 (Dataverse) using C# that trigger on the Associate and Disassociate messages. These messages are used when creating or removing relationships (e.g., N:N or 1:N) between entities, such as associating/disassociating a contact with an account. The plugins will log these actions to a custom entity (new_relationshipauditlog) and include optional validation to prevent certain associations. This guide assumes familiarity with C# and Dynamics 365 plugin development.


Step-by-Step Guide to Creating Associate and Disassociate Message Plugins in Dynamics 365

Step 1: Set Up Your Development Environment

  1. Install Required Tools:
    • Visual Studio (2019 or later) with .NET Framework 4.6.2 or higher.
    • Install the Microsoft.CrmSdk.CoreAssemblies NuGet package for Dynamics 365 SDK references.
    • Install the Plugin Registration Tool (PRT) from NuGet (Microsoft.CrmSdk.XrmTooling.PluginRegistrationTool) or use XrmToolBox.
  2. Create a New Project:
    • In Visual Studio, create a Class Library (.NET Framework) project (e.g., Dynamics365RelationshipPlugins).
    • Add the NuGet package Microsoft.CrmSdk.CoreAssemblies for plugin development.
  3. Sign the Assembly:
    • Go to Project > Properties > Signing > Check Sign the assembly.
    • Create or select a strong name key file (e.g., mykey.snk). This is required for Dynamics 365 plugins.

Step 2: Create the Custom Entity for Logging (Optional)

  1. Create the Custom Entity:
    • In Dynamics 365, navigate to Settings > Solutions > [Your Solution] > Entities > New.
    • Create a custom entity (e.g., new_relationshipauditlog) with fields:
      • new_name (Single Line of Text, for log description).
      • new_action (Single Line of Text, for action type: “Associate” or “Disassociate”).
      • new_entityname (Single Line of Text, for relationship entity name).
      • new_recordid (Single Line of Text, for related record ID).
      • new_timestamp (Date and Time, for action timestamp).
    • Save and publish the entity.
  2. Add to Solution:
    • Include the custom entity in your unmanaged solution for deployment.

Step 3: Write the Plugin Code

Below are two separate plugin classes: one for the Associate message and one for the Disassociate message. Both plugins log the action to the new_relationshipauditlog entity and include validation to prevent association if a condition is met (e.g., contact has a specific attribute).

Associate Message Plugin

AssociateMessagePlugin.cs

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;

namespace Dynamics365RelationshipPlugins
{
    public class AssociateMessagePlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Obtain the execution context
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            // Check if the plugin is triggered by the Associate message
            if (context.MessageName.ToLower() != "associate" || context.Stage != 20) // Pre-Operation stage
            {
                return;
            }

            // Get the organization service
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // Get the relationship and related entities
            Relationship relationship = (Relationship)context.InputParameters["Relationship"];
            EntityReference target = (EntityReference)context.InputParameters["Target"];
            EntityReferenceCollection relatedEntities = (EntityReferenceCollection)context.InputParameters["RelatedEntities"];

            // Example: Handle Contact-Account N:N relationship (e.g., contact_account)
            if (relationship.SchemaName != "contact_account")
            {
                return;
            }

            try
            {
                foreach (EntityReference relatedEntity in relatedEntities)
                {
                    // Example validation: Prevent association if contact has a specific attribute (e.g., email is empty)
                    Entity contact = service.Retrieve("contact", relatedEntity.Id, new ColumnSet("emailaddress1"));
                    if (string.IsNullOrEmpty(contact.GetAttributeValue<string>("emailaddress1")))
                    {
                        throw new InvalidPluginExecutionException("Cannot associate contact without an email address.");
                    }

                    // Log the association to new_relationshipauditlog
                    Entity auditLog = new Entity("new_relationshipauditlog");
                    auditLog["new_name"] = $"Contact-Account Association: {relatedEntity.Id}";
                    auditLog["new_action"] = "Associate";
                    auditLog["new_entityname"] = relationship.SchemaName;
                    auditLog["new_recordid"] = relatedEntity.Id.ToString();
                    auditLog["new_timestamp"] = DateTime.Now;

                    service.Create(auditLog);
                }
            }
            catch (Exception ex)
            {
                throw new InvalidPluginExecutionException($"Error in AssociateMessagePlugin: {ex.Message}", ex);
            }
        }
    }
}

Disassociate Message Plugin

DisassociateMessagePlugin.cs

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;

namespace Dynamics365RelationshipPlugins
{
    public class DisassociateMessagePlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Obtain the execution context
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            // Check if the plugin is triggered by the Disassociate message
            if (context.MessageName != "disassociate" || context.Stage != 20) // Pre-Operation stage
            {
                return;
            }

            // Get the organization service
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // Get the relationship and related entities
            Relationship relationship = (Relationship)context.InputParameters["Relationship"];
            EntityReference target = (EntityReference)context.InputParameters["Target"];
            EntityReferenceCollection relatedEntities = (EntityReferenceCollection)context.InputParameters["RelatedEntities"];

            // Example: Handle Contact-Account N:N relationship (e.g., contact_account)
            if (relationship.SchemaName != "contact_account")
            {
                return;
            }

            try
            {
                foreach (EntityReference relatedEntity in relatedEntities)
                {
                    // Optional validation: Prevent disassociation if a condition is met
                    Entity contact = service.Retrieve("contact", relatedEntity.Id, new ColumnSet("statuscode"));
                    OptionSetValue status = contact.GetAttributeValue<OptionSetValue>("statuscode");
                    if (status != null && status.Value == 1) // Example: Active status
                    {
                        throw new InvalidPluginExecutionException("Cannot disassociate active contacts.");
                    }

                    // Log the disassociation to new_relationshipauditlog
                    Entity auditLog = new Entity("new_relationshipauditlog");
                    auditLog["new_name"] = $"Contact-Account Disassociation: {relatedEntity.Id}";
                    auditLog["new_action"] = "Disassociate";
                    auditLog["new_entityname"] = relationship.SchemaName;
                    auditLog["new_recordid"] = relatedEntity.Id.ToString();
                    auditLog["new_timestamp"] = DateTime.Now;

                    service.Create(auditLog);
                }
            }
            catch (Exception ex)
            {
                throw new InvalidPluginExecutionException($"Error in DisassociateMessagePlugin: {ex.Message}", ex);
            }
        }
    }
}

Step 4: Understand the Plugin Code

  1. AssociateMessagePlugin.cs:
    • Message: Triggers on the Associate message in the Pre-Operation stage (context.Stage == 20).
    • Purpose: Logs the association of a contact to an account (N:N relationship contact_account) and validates that the contact has an email address.
    • Input Parameters:
      • Relationship: The schema name of the relationship (e.g., contact_account).
      • Target: The primary entity reference (e.g., account).
      • RelatedEntities: The collection of related entities (e.g., contacts).
    • Logic:
      • Checks Validates each contact has an email address.
      • Logs the association to the new_relationshipauditlog entity.
  2. DisassociateMessagePlugin.cs:
    • Message: Triggers on the Disassociate message in the Pre-Operation stage.
    • Purpose: Logs the disassociation of a contact from an account and prevents disassociation for active contacts.
    • Logic:
      • Checks if the contact is active (statuscode == 1).
      • Logs the disassociation to the new_relationshipauditlog entity.
  3. Customization:
    • Modify the relationship.SchemaName to match your specific N:N relationship (e.g., new_customrelationship).
    • Adjust validation logic or remove it if not needed.
    • Update the new_relationshipauditlog fields to match your custom entity.
  4. Build the Solution:
    • Build the project in Visual Studio (Ctrl+Shift+B) to generate the DLL (e.g., bin\Debug\Dynamics365RelationshipPlugins.dll).

Step 5: Register the Plugins in Dynamics 365

  1. Open the Plugin Registration Tool(PRT):
    • Launch the PRT and connect to your Dynamics 365 environment.
  2. Register the Plugin Assembly:
    • Click Register > Register New Assembly.
    • Browse to the compiled DLL.
    • Select Sandbox isolation mode and Database storage.
    • Click Register Selected Plugins.
  3. Register Steps for Associate Message:
    • Locate the AssociateMessagePlugin class in the PRT.
    • Right-click and select Register New Step:
      • Message: Associate
      • Primary Entity: none (for N:N relationships)
      • Event Pipeline Stage of Execution: PreOperation
      • Execution Mode: Synchronous
      • Filtering Attributes: Leave blank (not applicable for Associate).
    • Click Register New Step.
  4. Register Steps for Disassociate Message:
    • Repeat the process for DisassociateMessagePlugin:
      • Message: Disassociate
      • Primary Entity: none
      • Event Pipeline Stage of Execution: PreOperation
      • Execution Mode: Synchronous
    • Click Register New Step.

Step 6: Test the Plugins

  1. Deploy to a Sandbox Environment:
    • Export the solution (including the custom entity and plugin) as unmanaged and import it into a sandbox environment.
  2. Test the Associate Plugin:
    • In Dynamics 365, associate a contact with an account (e.g., via a subgrid or form).
    • Verify:
      • A record is created in new_relationshipauditlog with new_action set to “Associate”.
      • If the contact has no email, the association is blocked with the error message.
  3. Test the Disassociate Plugin:
    • Disassociate a contact from an account.
    • Verify:
      • A record is created in new_relationshipauditlog with new_action set to “Disassociate”.
      • If the contact is active, the disassociation is blocked with the error message.
  4. Check Plugin Trace Log:
    • Enable plugin tracing in Settings > Administration > System Settings > Customization.
    • Check Settings > Customization > Plug-in Trace Log for errors or logs.

Step 7: Debug and Troubleshoot

  1. Debugging:
    • Use the Plugin Profiler in PRT or XrmToolBox to step through the code in Visual Studio.
    • Add tracing with ITracingService (e.g., tracingService.Trace(“Associating {0}”, relatedEntity.Id);) for debugging.
  2. Common Issues:
    • “Relationship not found”: Ensure the relationship.SchemaName matches the actual N:N relationship name in Dynamics 365.
    • Permission errors: Verify the plugin’s user has create permissions for new_relationshipauditlog.
    • No execution: Check that the step is registered for the correct message and stage.

Step 8: Deploy to Production

  1. Export the Solution:
    • Export the solution as managed (for production) or unmanaged (for development).
    • Include the custom entity (new_relationshipauditlog) and plugin assembly.
  2. Import and Publish:
    • Import the solution into the production environment.
    • Publish all customizations.
  3. Verify in Production:
    • Test association and disassociation in production.
    • Monitor the Plug-in Trace Log for issues.

Step 9: Best Practices and Cleanup

  1. Best Practices:
    • Use Synchronous mode for validation to block operations when needed.
    • Handle exceptions with clear error messages using InvalidPluginExecutionException.
    • Test thoroughly in a sandbox environment.
    • Use meaningful names for plugins and steps (e.g., ContactAccountAssociatePlugin).
  2. Documentation:
    • Document the plugins’ purpose, messages, entities, and dependencies.
    • Note the relationship name (contact_account) and custom entity details.
  3. Backup:
    • Back up the solution and plugin code before production deployment.
    • Increment assembly versions for managed solutions (e.g., 1.0.0.1).

Additional Notes

  • Relationship Scope: The plugins target the contact_account N:N relationship. Modify the relationship.SchemaName to target other relationships (e.g., new_customrelationship).
  • Validation: The example validation checks for an email address (Associate) or status (Disassociate). Adjust based on your business rules.
  • Performance: Minimize queries in plugins to avoid performance issues. Use ColumnSet to retrieve only necessary attributes.
  • Alternative Tools: Use XrmToolBox or Power Platform CLI (pac plugin init) for faster development and registration.
  • Managed Solutions: For managed solutions, increment the solution version and use the Stage for Upgrade process to update plugins.

This guide provides a complete process for creating and deploying Associate and Disassociate message plugins in Dynamics 365. If you need specific customizations, additional validation logic, or troubleshooting help, let us know!

Leave a comment

Copyright © 2025 Dynamics Services Group