Archive
[Dynamics CE] – Strange Plug-in issue due to the usage of global variables
Other day, one of my colleagues reported a plug-in issue which could not be reproduced consistently. All the troubleshooting options (i.e., Plug-in Profiler, Tracing) had been used but was unable to found the root cause.
On close retrospection of code, we found out the usage global variables in Plug-in class file, which made the Plug-in ‘Stateful’.
Due to this, issue could not be reproduced when tested with Plug-in Profiler as it creates single Plug-in instance. Issue will only be reproduced when concurrent users instantiate plug-ins.
You have to make sure to create your Plug-in as ‘Stateless’ to avoid issues during concurrent Plug-in execution.
What is ‘Stateful’ Plug-in?
- If the Plug-in ‘Execute’ method refer any global variable(s), makes the Plug-in ‘Stateful’.
public class CustomersVirtual : IPlugin{
// ‘context’ variable declared as Global
IPluginExecutionContext context = null;
EntityCollection results = new EntityCollection();
public void Execute(IServiceProvider serviceProvider) {
context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));if (context.MessageName.ToLower() == “retrieve”){
Entity customer = new Entity(“raj_virtualcustomer”);
customer[“raj_customersid”] = Guid.NewGuid();
customer[“raj_name”] = “ABC Corporation”;//add it to the collection
results.Entities.Add(customer);
}
In the above Plug-in sample, variables ‘context’ and ‘results’ been defined as global and referred in ‘Execute’ method which makes it ‘Stateful’
What is ‘Stateless’ Plug-in:
- If the Plug-in ‘Execute’ method, has its own local variables and does not refer any global variables make the Plug-in ‘Stateless’.
public class CustomersVirtual : IPlugin{
public void Execute(IServiceProvider serviceProvider){// ‘context’ variable declared as Local variable
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));EntityCollection results = new EntityCollection();
if (context.MessageName.ToLower() == “retrieve”)
{
Entity customer = new Entity(“raj_virtualcustomer”);
customer[“raj_customersid”] = Guid.NewGuid();
customer[“raj_name”] = “ABC Corporation”;//add it to the collection
results.Entities.Add(customer);
}
In this sample, variables ‘context’ and ‘results’ been defined as Local in ‘Execute’ method which makes it ‘Stateless’
Why ‘Stateless’ Plug-in is recommended?
- As per the design, Dynamics platform caches plug-in class instances and hence the constructor is not called for every invocation of plug-in execution.
- IPlugins should be stateless is that multiple system threads could execute the same, shared, plug-in instance concurrently.
- This opens up members of classes that implement IPlugin to potential thread-safety issues which could lead to data inconsistency or performance problems.
- Read-only, static, and constant members are inherently thread-safe and can also be used reliably within a plug-in class.
How the Dynamics platform Caches Plug-in?
- For performance reasons, Dynamics DOES NOT dispose of the object after it completes execution.
- Dynamics caches the object and calls Execute on the same object instance on the next activation of the plug-in.
- Certain operations, such as changing a plug-in’s registration properties, will trigger a notification to the platform to refresh the cache. In these scenarios, the plug-in will be reinitialized..
- This means that you need to be very careful when defining class-level variables in the plug-in class as multiple threads can execute the code at one time
Bottom line:
- Don’t use Global variables in Plug-ins
Refer below articles for more insights:
🙂
How to Debug Plug-Ins in CRM
Hi,
Sometimes you might get wonder (even frustrated) why the debugger is not hitting the break point in your Plug-In code file.
Below are the Checklist to perform prior to start the debugging of your Plug-in assembly.
- Ensure that your plug-in assembly is signed (See)
- Rebuild the plug-in assembly
- Reset the IIS (i.e. Open command prompt and run ‘iisreset’ command)
- Restart CRM Asynchronous Service on CRM server (Only in case of asynchronous plug-in or Custom workflow )
- Copy the .pdb file and your .dll file to server’s assembly (i.e., ..\Program Files\Microsoft Dynamics CRM\Server\bin\assembly)
- Open the Plug-in Registration Tool (Download)
- Browse the dll from your Plug-Ins projects “bin\debug” folder
- In Plug-in Registration Tool , Choose “Specify the location where assembly should be stored” option as “Disk”
- Register the step
- Register the image(s) if any
- Attach the process by opening the Plug-in project in the Visual Studio and then
- From menu choose “Debug -> Attach to Process…” ( or Click ‘Alt + Ctrl + P’)
- Select w3wp.exe (i.e.,Worker Process), if plug-in is Synchronous
- Select CRMAsyncService.exe, if Plug-in is asynchronous or Custom workflow.
- You are all set by now and put a break point (Click F9) on relevant code line.
In case of Remote Debugger :-
- If your CRM server machine is different from your development machine
- Install “Visual Studio Remote Debugger” (Download) on CRM server machine
- Run it as “Administrator”
- Follow the same steps above except while attaching Process, in the “Attach to Process” window, set “Qualifier” as “CRM server machine”
Debug plug-in in outlook offline mode
- Clean and rebuild the plug-in solution on your machine using visual studio
- Register the plugin on the server
- Synchronize the organization with the outlook
- Go Offline
- Attach the debugger to the process “Microsoft.Crm.Application.Hoster.exe”
- Place a breakpoint in the code.
- Run the scenario
Hope it helps 🙂
Assign record (i.e., Change Owner) using CRM 2011 Plug-In
Hi,
In CRM, we cant update the “Owner” field like any other field, Assign or Changing the owner in CRM need’s an explicit service request (i.e., AssignRequest)
- This is the code to “Assign” a record to other user in Plug-In
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = null;
try
{
service = factory.CreateOrganizationService(context.UserId);
// Create the Request Object and Set the Request Object’s Properties
AssignRequest assign = new AssignRequest
{
//systemuser; i.e., User to whome you are assigning the entity to
Assignee = new EntityReference(“systemuser”, {new_owner_guid}),
//Current record which you are assigning to the user
Target = new EntityReference(context.PrimaryEntityName.ToString(), context.PrimaryEntityId)
};
// Execute the Request
service.Execute(assign);}
}
Hope it helps 🙂