Monday, October 29, 2018

Dynamics CRM - Auto generate plugin steps.

I believe that you are experienced with the plugin registration and adding steps using plugin registration tool. But sometimes you may need to add/remove plugin steps programmatically.

Adding a plugin step means, creating a SdkMessageProcessingStep record in CRM.

Before creating a SdkMessageProcessingStep record you need to retrieve following.
  1. The message id.
    /// <summary>
    /// Gets the message identifier.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="sdkMessageName">Name of the SDK message.</param>
    /// <returns>
    /// The message id.
    /// </returns>
    /// <exception cref="InvalidPluginExecutionException">No sdk messages found with the sdk message name.</exception>
    private static Guid GetMessageId(ServiceContext context, string sdkMessageName)
    {
        using (context)
        {
            // Gets the sdk message
            var sdkMessage = context.SdkMessageSet.Where(s => s.Name == sdkMessageName).FirstOrDefault();
                    
            // Throws an error if no sdk messages found.
            if (sdkMessage == null)
            {
                throw new InvalidPluginExecutionException(string.Format("No sdk messages found with the sdk message name '{0}'.", sdkMessageName));
            }
    
            return sdkMessage.Id;
        }
    }
  2.  The plugin type id.
    /// <summary>
    /// Gets the plugin type identifier.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="assemblyName">Name of the assembly.</param>
    /// <param name="pluginTypeName">Name of the plugin type.</param>
    /// <returns>
    /// The plugin type id.
    /// </returns>
    /// <exception cref="InvalidPluginExecutionException">No plugin assemblies found with the assmbly name
    /// or
    /// No plugin types found with the plugin type and the assmbly name</exception>
    private static Guid GetPluginTypeId(ServiceContext context, string assemblyName, string pluginTypeName)
    {
        using (context)
        {
            // Gets the plugin assembly.
            var pluginAssembly = context.PluginAssemblySet.Where(p => p.Name == assemblyName).FirstOrDefault();
                    
            // Throws an error if no plugin assembly found with the name.
            if (pluginAssembly == null)
            {
                throw new InvalidPluginExecutionException(string.Format("No plugin assemblies found with the assmbly name '{0}'", assemblyName));
            }
    
            var assemblyId = pluginAssembly.Id;
                    
            // Gets the plugin type
            var pluginType = context.PluginTypeSet.Where(p => p.PluginAssemblyId.Id == assemblyId && p.TypeName == pluginTypeName).FirstOrDefault();
                    
            // Throws an error if no plugin types found.
            if (pluginType == null)
            {
                throw new InvalidPluginExecutionException(string.Format("No plugin types found with the plugin type '{0}' and the assmbly name '{1}'", pluginType.ToString(), assemblyName));
            }
    
            return pluginType.Id;
        }
    }
  3. The sdk message filter id.
    /// <summary>
    /// Gets the SDK message filter identifier.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="entityName">Name of the entity.</param>
    /// <returns>
    /// The message filter id.
    /// </returns>
    /// <exception cref="InvalidPluginExecutionException">No sdk message filters found for the entity.</exception>
    private static Guid GetSdkMessageFilterId(ServiceContext context, string entityName)
    {
        using (context)
        {
            // Gets the message id.
            Guid messageId = GetMessageId(context, ContextMessageName.Create);
                    
            // Get the message filter.
            var sdkMessageFilter = context.SdkMessageFilterSet.Where(s => s.PrimaryObjectTypeCode == entityName && s.SdkMessageId.Id == messageId).FirstOrDefault();
                    
            // Throws an error if no message filters found.
            if (sdkMessageFilter == null)
            {
                throw new InvalidPluginExecutionException( string.Format("No sdk message filters found for the entity '{0}'", entityName));
            }
    
            return sdkMessageFilter.Id;
        }
    }

Now we have the message id,  plugin type id and the sdk message filter id.

We can define the assembly name and the plugin type name as properties.
/// <summary>
/// The assembly name
/// </summary>
private static string AssemblyName = "PluginSample.Plugins";

/// <summary>
/// The plugin type name
/// </summary>
private static string PluginTypeName = "PluginSample.Plugins.TestHandler";


With all the above things in hand, now following method can be used to add plugin steps programatically.
/// <summary>
/// Adds the plugin step.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="entityLogicalName">The entity logical name.</param>
/// <param name="contextMessageName">Name of the context message.("Create", "Update", "Delete", "Associate", "Disassociate")</param>
public static void AddPluginStep(IOrganizationService service, string entityLogicalName, string contextMessageName)
{ 
    using (var context = new ServiceContext(service))
    {
        var stepName = string.Format("PluginSample.Plugins.TestHandler: {0} of {1}", contextMessageName, entityLogicalName);

        // Gets the message name.
        var messageId = GetMessageId(context, contextMessageName);

        // Gets the plugin type id.
        var pluginTypeId = GetPluginTypeId(context, AssemblyName, PluginTypeName);

        // Gets the sdk message filter id.
        var sdkMessageFilterId = GetSdkMessageFilterId(context, entityLogicalName);

        // Check the plugin step is already regiseterd and if not register the new step.
        var sdkMessageProcessingStep = context.SdkMessageProcessingStepSet.Where(s => s.SdkMessageFilterId.Id == sdkMessageFilterId && s.EventHandler.Id == pluginTypeId).FirstOrDefault();
        if (sdkMessageProcessingStep == null || sdkMessageProcessingStep.Id == Guid.Empty)
        {
            // Creates the sdk message processing step.
            SdkMessageProcessingStep step = new SdkMessageProcessingStep
            {
                AsyncAutoDelete = false,
                Mode = new OptionSetValue((int)SdkMessageProcessingStepMode.Synchronous),
                Name = stepName,
                EventHandler = new EntityReference(PluginType.EntityLogicalName, pluginTypeId),
                Rank = 1,
                SdkMessageId = new EntityReference(SdkMessage.EntityLogicalName, messageId),
                Stage = new OptionSetValue((int)PipelineStage.PreOperation),
                SupportedDeployment = new OptionSetValue((int)SdkMessageProcessingStepSupportedDeployment.ServerOnly),
                SdkMessageFilterId = new EntityReference(SdkMessageFilter.EntityLogicalName, sdkMessageFilterId)
            };

            service.Create(step);
        }
    }
}

In the same manner we can remove the plugin step by deleting the SdkMessageProcessingStep record.
/// <summary>
/// Removes the plugin step.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="entityLogicalName">The entity logical name.</param>
public static void RemovePluginStep(IOrganizationService service, string entityLogicalName)
{
    using (var context = new ServiceContext(service))
    {
        // Gets the plugin type id.
        var pluginTypeId = GetPluginTypeId(context, AssemblyName, PluginTypeName);

        // Gets the message filter id.
        var sdkMessageFilterId = GetSdkMessageFilterId(context, entityLogicalName);

        // Filter the message processing step.
        var sdkMessageProcessingStep = context.SdkMessageProcessingStepSet.Where(s => s.SdkMessageFilterId.Id == sdkMessageFilterId && s.EventHandler.Id == pluginTypeId).FirstOrDefault();

        if (sdkMessageProcessingStep != null)
        {
            // Delete sdk message processing step.
            service.Delete(SdkMessageProcessingStep.EntityLogicalName, sdkMessageProcessingStep.Id);
        }
    }
}

In above example I'm using the early bound classes. If you need late bound code examples, please let me know in comment section.


Friday, October 19, 2018

Deeper look in to IExecutionContext.Depth Property

In CRM customizations Depth is a very important property provided for context management, which most of the time CRM developers pay less attention to.

“Depth” property(Int32 ) is hold by the IPluginExecutionContext which holds contextual information passed into a plugin. It gets the current depth of execution in the call stack and used by the platform for infinite loop prevention.

How Microsoft explains the behavior of the Depth:
“Every time a running plug-in or Workflow issues a message request to the Web services that triggers another plug-in or Workflow to execute, the Depth property of the execution context is increased. If the depth property increments to its maximum value within the configured time limit, the platform considers this behavior an infinite loop and further plug-in or Workflow execution is aborted.”

In simple words: 
Depth is the number of times a plugin/custom workflow has been called in one transaction



See following 3 examples.
  1. If Contact updates the account it self in post update, it makes an infinite loop and the first time the depth is 1, then in the second time the depth is 2 and so on…
  2. If Contact is updated form an account update plugin, and it triggers a plugin in contact entity, then the first plugin(in account) depth is 1 and the second plugin(in contact) depth is 2
  3. If a workflow(WF1) triggers in contact update, and the workflow triggers another child workflow(WF2), first workflow(WF1) depth is one and the second workflow(WF2) depth is 2


The default value in depth property is 1 and the maximum depth is 8.  And, the time limit is one hour. This means that after one hour of the first plugin/workflow execution, it reset the depth property to 1 again.

Knowingly or unknowingly you may have experienced this when you schedule a waiting workflow recursively. If the waiting time is less than 8min your workflow files in 8th execution. Mistry behind this is this is the above reason.

But maximum depth is configurable by the Microsoft Dynamics 365 administrator using the PowerShell command Set-CrmSetting. (The setting is WorkflowSettings.MaxDepth.)

In your plugins you can check the depth or parent context to prevent loops.

public void Execute(IServiceProvider serviceProvider)
{
 var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
 
    // Checking either depth or parent context is enough, but I have used both in this example.
 if (context.Depth > 1 || context.ParentContext != null)
 {
  return;
 }

 // Logic goes here.
}

But if your plugin should execute in second level (Previous Eg2 contact update and Eg3 WF3 update), no point of checking the parent context and need to check only the depth. The reason is the ParentContext is not null form the second level(2,3,4..).

public void Execute(IServiceProvider serviceProvider)
{
 var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
 if (context.Depth > 2)
 {
  return;
 }

 // Logic goes here.
}

When you use the depth property to prevent loops, use it wisely. Otherwise you might face in to trouble by missing the necessary plugin/workflow executions.




Friday, October 12, 2018

Dynamics CRM 365 App for Outlook permission error

Your client may complain that he is getting the following error when he opens the Dynamics 365 App for Outlook.

"We are unable to show Dynamics 365 App for Outlook because current user role does not have required permissions. Please contact your administrator to have required security role assigned to the Dynamics 365 App for Outlook solution."

The reason behind this is the client's Dynamics 365 security role does not have access to the app module used by the Dynamics 365 App for Outlook.

The fix is really simple.
  1. Go to Settings > Security > Security Roles and select the relevant security role for the user.
  2. Grant Create, Write permission for "App" entity.

Time to pay more attention to changes in the security roles with new app features...

Friday, October 5, 2018

Create auto-number attributes - Dynamics 365 (online), version 9.x

With the Dynamics 365 (online), version 9.0 release, you can add an auto-number attribute for any entity. Still a UI is not provided by Microsoft and you can do this programmatically. 
This is not the same Autonumber that is available for some of the out of the box entities like case, invoice, order, etc in Settings > Administration section. Here you can add any string field as the auto number field.


Ref : https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/create-auto-number-attributes

Auto Number Manager for XrmToolBox, provides you this facility with a nice and user firendly User Interface.

Ref : https://www.xrmtoolbox.com/plugins/Rappen.XrmToolBox.AutoNumManager/

But I found following two drawbacks in this new feature.

  1. After enabling the auto number for the field, there is no way to disable this at the moment. (Eventhough there is a checkbox in "Auto Number Manager" to disable this, it is not working at the moment. Delete attribute is not for deleting number format, it is for deleting the field.)
  2. The auto number will be increased even in failed attempt to create a record.Eg : Consider last sequence no is 100000. If next attempt of creating record fails and the successful record creation after that will not have the sequence number as 100001 and it will have the value 100002.