In the previous post I looked at Plugin Deployment Options, which looked at the various different deployment options Database, Disk or GAC. The article looked at the async architecture and effects of ISSResets and CRM service restarts.
This post I am going to look at a couple of the parameters in the PluginExecutionContext which I needed to help me resolve a problem.
What is PluginExecutionContext
Unit testing plugins is difficult due to a plugins dependency on
The serviceProvider has these variables inside
- you use the IOrganizationServiceFactory to create IOrganizationService
Mocking the IServiceProvider and IPluginExecutionContext can be tricky because they contain a lot of variables you would need to setup.
I prefer to put the code logic into separate classes which take IOrganisationService, ITracingService, Entity being changed and other variables I need. I focus on testing the plugin logic and not get bogged down creating variables
The IPluginExecutionContext holds contextual information passed into a plugin. This information is different for each type of plugin because it contains things like
- InputParameters (e.g. which holds the Target)
To get the plugin runtime variables you need to get the PluginExecutionContect in your plugin, I’m going to assume you are using the CRM Developer toolkit
LocalPluginContext localContext IPluginExecutionContext context = localContext.PluginExecutionContext;
You now have access to the plugin runtime variables, here are a few
context.Depth localContext.PluginExecutionContext.Depth; IOrganizationService service = localContext.OrganizationService; ITracingService tracingService = localContext.TracingService; context.InputParameters.Contains("Target") context.ParentContext
This is how CRM SDK describes it (remember always start with the CRM SDK)
Defines the contextual information passed to a plug-in at run-time. Contains information that describes the run-time environment that the plug-in is executing in, information related to the execution pipeline, and entity business information.
A list of all the available parameters is listed on PluginExecutionContext properties page.
PluginExecutionContext is a fancy way of saying the plugin variables at run time.
Depth and ParentContext are two of the many properties passed into plugins in the PluginExecutionContext. I shall start with depth first
What does Depth do?
CRM SDK Definition of depth at the ready
Int32 – The the current depth of execution in the call stack.
Depth is the number of times a plugin/custom workflow has been called in one transaction
What is depth used for?
Depth is often used to stop infinite loops where a plugin updates a value which triggers the plugin to run again
A common cause of this is bad programming practise where a plugin retrieves the target entity and at the end of the plugin updates the entity.
A post plugin triggered on update of account name.
Plugin retrieves target entity
plugin updates name field on target entity field to name + unique number
plugin does CrmService.Update (target entity);
Plugin has updated field value which triggers plugin again
The way around this is to put code in a pre plugin. It’s possible to check the depth of the plugin
IPluginExecutionContext context = localContext.PluginExecutionContext; if (context.Depth > 1) return;
The code above is checking to see if the context.Depth is greater than one. If a plugin is triggered from a record being saved then the context.Depth = 1 (it has been run once). If the plugin called itself again the depth would then be incremented to 2.
I would put the context checking code as bad programming practise, it’s fixing the problem of stopping the infinite plugin but it’s working around the plugin being triggered in the wrong time (e.g. post instead of pre).
There are some cases when a plugin might need to update itself but for the majority of the time avoid doing because it’s confusing/complex and there is usually a better way.
Good practice when updating entity
When you want to update fields on a record, it’s good practice to create a new entity or early bound type of the record and only add the fields you want to update.
By only updating the fields you are changing you reduce triggering other plugins running needlessly.
Do not update the retrieve Target entity because it will update all fields included in the target entity.
How does depth work?
When a plugin/custom workflow triggers another plugin/trigger then the depth property is incremented by one.
The depth value has a maximum value called WorkflowSettings.MaxDepth. The CRM SDK (Depth)states this is set to maximum depth of 8 within one hour.
You can adjust this setting by changing WorkflowSettings.MaxDepth via powershell. My advice is to not change this setting because it usually means you are trying to work round bad practice rather than resolve the bad practice.
e.g. fix the problem not react to the problem
If a plugin/workflow hits the depth of 8 or greater then it will not run and I think throw an error.
The maximum depth is not such a problem for plugins because if it reaches 8 it’s usually because there is a loop and it happens quickly.
Workflows are different, it’s easily possible for a workflow to get triggered plenty of times by one record. I have seen workflows which have been created to keep polling/checking a value hit the depth max.
What is ParentContext?
PluginExecutionContext.ParentContext will return a IPluginExecutionContext context object of the triggering plugin/custom workflow if there is one, otherwise ParentContext will be null (if it was called from the Form)
Plugins don’t just trigger themselves in a loop, often the CRM developer will trigger another plugin by doing a CRMService.update() or an assign using the CrmService(IOrganisationService)
When you trigger a plugin in the same transaction then you will increment the context.Depth by 1.
Not only does the depth get incremented, so the depth = 2 in the triggered plugin. The context.ParentContext will now contain a value.
The CRM SDK describes ParentContext
The execution context from the parent pipeline operation.
This property may be populated for a plug-in registered in stage 20 or 40 when a Create, Update, Delete, or RetrieveExchangeRate request is processed by the execution pipeline.
This can be useful if you want to know if your plugin was triggered by a CRM form update or triggered from another plugin.
If a plugin is triggered from another plugin then the context.ParentContext will not be null and the localContext.PluginExecutionContext.Depth will have a count of 2 (or higher if it has been called more than once).
The ParentContext can be useful because it can give you more information about the calling plugin which you might want to use/use for filtering.
Depth checking and stopping infinite loops in plugins
The common method to avoid a recurring plugin is to check if a plugins depth > 1. This would stop the plugin from being run if was triggered from any other plugin. The plugin would only run if triggered from the CRM form.
This can resolve the problem of plugins firing more than once but it stops plugins being triggered from other plugins, this might not be the functionality you require.
This article below shows you how test for infinite loops using depth but it’s basically this
if (context.Depth > 1) return;
or you could do this
if (context.Depth > 1 || context.ParentContext != null) return;
An alternative solution is to use SharedVariables, talked about on this page Pass data between plug-ins. You can use ShareVariables to pass information between plugins registered on different stages but in the same transaction e.g a pre plugin can pass information to a post plugin.
It’s possible to set a shared Variable to a value and then check to see if the share variable exists, if it doesn’t have a value you know the plugin has not been run before, if it does have a value the plugin has ran in the transaction.
This blog gives you an example of a shared variables
These two forum discussion shows an interesting example of trying a parent record updating child records of the same type.