I recently had to write code that would auto-register a plugin step & image for a given entity. The CRM SDK allows for this by exposing the required objects representing the Plugin itself, the Plugin Steps, and the Plugin Step Images. The structure of these objects was at first a little confusing to me, so I drew the relationships out and formed the diagram below.
PluginType
The PluginType object represents the actual plugin registered against an assembly. This object needs to be retrieved via the normal CRM web services, based on the textual name of the plugin. See the screenshot below to identify the name required. The example used here is a generic Count Mapping functionality, to count the number of ‘N’ records in a 1:N relationship, and store the result in a field in the ‘1’ entity.
SDKMessageProcessingStep
This object represents the step registered against a plugin, and is the equivalent of adding a new step in the Plugin Registration Tool. It has a number of attributes, equivalent to the ones you see in the Plugin Registration Tool. I was expecting this object to have text properties for MessageType and Entity, as they are text inputs in the Plugin Registration Tool, but it turns out it is more structured than that. The SDKMessageProcessingStep relies on the the SDKMessageFilter object to know what message type and entity the step is registered against.
SDKMessageFilter
This object represents a unique combination of entity and message. This object matches up directly with the SDKMessageFilterBase table in the SQL database, and contains an entry for each unique combination of entity and message type. For example, see the screenshot to the right, showing the SDKMessageFilter table just for the Email entity. There is a record for each message type. When an entity is created, this table must be populated with every combination to make this possible.
SDKMessage
This object matches up directly with the SDKMessageBase table in the SQL database. It contains an entry for every type of message possible. See the screenshot below for a sample of the contents of the table. The CRM web services can be used to retrieve these entries as SDKMessage objects, for use programmatically.
SDKMessageProcessingStepImage
This object represents images registered against plugin steps. There is nothing really complicated about this object, other than it just needs to reference a SDKMessageProcessingStep to know what plugin step to register against.
Creating the Plugin Step/Image
So, in order to create a step programmatically, the following steps need to be performed:
- Retrieve the PluginType object
- Retrieve the MessageType object (Create, Update, Delete, Associate, etc.)
- Retrieve the SDKMessageFilter object (eg. Create for Email, Update for Contact, etc.)
- Construct the SDKMessageProcessingStep object and update via the organisation service
- Construct the SDKMessageProcessingStepImage object and update via the organisation service
Below are code samples for how to achieve this. Note that I used Dynamic Entities and QueryExpression so there were no dependencies in this code. This means it can be lifted out of one CRM environment and put into another
/// <summary>
/// Retrieves the actual registered plugin type, that steps will be registered against
/// </summary>
/// <param name="service"></param>
/// <returns></returns>
public static Guid RetrievePluginType(IOrganizationService service, String pluginName)
{
try
{
QueryExpression query = new QueryExpression()
{
Distinct = false,
EntityName = "plugintype",
ColumnSet = new ColumnSet("plugintypeid"),
Criteria =
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("typename", ConditionOperator.Equal, pluginName)
}
}
}
}
};
EntityCollection entitiesRetrieved = service.RetrieveMultiple(query);
if (entitiesRetrieved != null && entitiesRetrieved.Entities != null && entitiesRetrieved.Entities.Count > 0)
return entitiesRetrieved.Entities[0].Id;
else
return Guid.Empty;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Retrieves a message type (Create, Delete, Associate, etc)
/// </summary>
/// <param name="service"></param>
/// <returns></returns>
public static Guid RetrieveMessageType(IOrganizationService service, string messageType)
{
try
{
QueryExpression query = new QueryExpression()
{
Distinct = false,
EntityName = "sdkmessage",
ColumnSet = new ColumnSet("sdkmessageid"),
Criteria =
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("name", ConditionOperator.Equal, messageType)
}
}
}
}
};
EntityCollection entitiesRetrieved = service.RetrieveMultiple(query);
if (entitiesRetrieved != null && entitiesRetrieved.Entities != null && entitiesRetrieved.Entities.Count > 0)
return entitiesRetrieved.Entities[0].Id;
else
return Guid.Empty;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Retrieves a SDK Message Filter, which is a record linking an entity to a message
/// (There is a record in the system for every combination of message type and entity)
/// </summary>
/// <param name="service"></param>
/// <returns></returns>
public static Guid RetrieveSDKMessageFilter(IOrganizationService service, Guid messageTypeId, string entityName)
{
try
{
QueryExpression query = new QueryExpression()
{
Distinct = false,
EntityName = "sdkmessagefilter",
ColumnSet = new ColumnSet("sdkmessagefilterid"),
Criteria =
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("primaryobjecttypecode", ConditionOperator.Equal, entityName),
new ConditionExpression("sdkmessageid", ConditionOperator.Equal, messageTypeId)
}
}
}
}
};
EntityCollection entitiesRetrieved = service.RetrieveMultiple(query);
if (entitiesRetrieved != null && entitiesRetrieved.Entities != null && entitiesRetrieved.Entities.Count > 0)
return entitiesRetrieved.Entities[0].Id;
else
return Guid.Empty;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Retrieves the registered plugin step for a given SDK Message Filter (combo of message type & entity name) and Plugin Type
/// </summary>
/// <param name="service"></param>
/// <returns></returns>
public static Guid RetrieveSdkMessageProcessingStep(IOrganizationService service, Guid pluginTypeId, Guid sdkMessageFilterId)
{
try
{
QueryExpression query = new QueryExpression()
{
Distinct = false,
EntityName = "sdkmessageprocessingstep",
ColumnSet = new ColumnSet("sdkmessageprocessingstepid"),
Criteria =
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("plugintypeid", ConditionOperator.Equal, pluginTypeId),
new ConditionExpression("sdkmessagefilterid", ConditionOperator.Equal, sdkMessageFilterId)
}
}
}
}
};
EntityCollection entitiesRetrieved = service.RetrieveMultiple(query);
if (entitiesRetrieved != null && entitiesRetrieved.Entities != null && entitiesRetrieved.Entities.Count > 0)
return entitiesRetrieved.Entities[0].Id;
else
return Guid.Empty;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Retrieves all registered plugin steps for a given Plugin Type
/// </summary>
/// <param name="service"></param>
/// <returns></returns>
public static EntityCollection RetrieveAllSdkMessageProcessingStepsForPlugin(IOrganizationService service, Guid pluginTypeId)
{
try
{
QueryExpression query = new QueryExpression()
{
Distinct = false,
EntityName = "sdkmessageprocessingstep",
ColumnSet = new ColumnSet("sdkmessageprocessingstepid"),
Criteria =
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("plugintypeid", ConditionOperator.Equal, pluginTypeId)
}
}
}
}
};
EntityCollection entitiesRetrieved = service.RetrieveMultiple(query);
return entitiesRetrieved;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Creates a plugin step
/// </summary>
/// <param name="service"></param>
/// <param name="messageName"></param>
/// <param name="messageId"></param>
/// <param name="entityName"></param>
/// <param name="pluginTypeId"></param>
/// <param name="pluginStepStage"></param>
/// <param name="pluginStepMode"></param>
/// <param name="pluginStepSupportedDeployment"></param>
/// <returns></returns>
public static Guid CreatePluginStep(IOrganizationService service, String messageName, Guid messageId, String entityName, Guid pluginTypeId, PluginStepStage pluginStepStage, PluginStepMode pluginStepMode, PluginStepSupportedDeployment pluginStepSupportedDeployment)
{
Entity step = new Entity();
step.LogicalName = "sdkmessageprocessingstep";
step.Attributes["name"] = "Cdss.Count.Plugins.CountPlugin: " + messageName + " of " + entityName;
step.Attributes["plugintypeid"] = new EntityReference("plugintype", pluginTypeId);
step.Attributes["sdkmessagefilterid"] = new EntityReference("sdkmessagefilter", RetrieveSDKMessageFilter(service, messageId, entityName));
step.Attributes["sdkmessageid"] = new EntityReference("sdkmessage", messageId);
step.Attributes["mode"] = new OptionSetValue((int)pluginStepMode);
step.Attributes["asyncautodelete"] = false;
step.Attributes["rank"] = 1;
step.Attributes["stage"] = new OptionSetValue((int)pluginStepStage);
step.Attributes["supporteddeployment"] = new OptionSetValue((int)pluginStepSupportedDeployment);
Guid stepId = service.Create(step);
return stepId;
}
/// <summary>
/// Creates a plugin step image
/// </summary>
/// <param name="service"></param>
/// <param name="pluginStep"></param>
/// <param name="pluginImageType"></param>
/// <returns></returns>
public static Guid CreatePluginStepImage(IOrganizationService service, Guid pluginStep, PluginImageType pluginImageType)
{
Entity image = new Entity();
image.LogicalName = "sdkmessageprocessingstepimage";
image.Attributes["sdkmessageprocessingstepid"] = new EntityReference("sdkmessageprocessingstep", pluginStep);
image.Attributes["name"] = Enum.GetName(typeof(PluginImageType), pluginImageType);
image.Attributes["entityalias"] = Enum.GetName(typeof(PluginImageType), pluginImageType);
image.Attributes["imagetype"] = new OptionSetValue((int)pluginImageType);
image.Attributes["messagepropertyname"] = "Target";
Guid imageId = service.Create(image);
return imageId;
}