Assigning Case To Appropriate Team User Using Plugin In Dynamics 365/ CRM

In a recent project, I have worked on one interesting functionality, where I have written a plugin for a Case record to be triggered while assigning it to a Team, and it should fulfil these three requirements.
  1. If the Team has no users in it, Case should be assigned to the default queue of Team.

  2. If the Team has users, the Case should be assigned to the user with the least number of cases.

  3. If more than one user in the Team have the same number of cases, then the case should be randomly assigned to one of them.
Here, I will show you how to implement the above functionaliy. To simplify, I am not using any custom attribute or an entity; you can just create an online trial instance of Dynamics CRM/365 and try it out. 

Section 1
 
Code 

Step 1

Create a new project in Visual Studio of type Class Library and give the name. I have given the name as AssignPlugin and don't forget to set the framework version to 4.5.2.

Dynamics CRM

Step 2

Add the required references to project from CRM SDK.

Dynamics CRM 

Step 3

Remove the existing class from the project and add a new class to the project with some name like AssignTeamToUser.cs.

Open this class, make it public, add namespace “Microsoft.Xrm.Sdk” and inherit “IPlugin” interface, which is required for the plugin.

IPlugin requires Execute method to be implemented, so add it to our class.

Now, the code should look, as shown below.
 
  1. using Microsoft.Xrm.Sdk;  
  2. using System;  
  3.   
  4. namespace AssignPlugin  
  5. {  
  6.     public class AssignTeamToUser : IPlugin  
  7.     {  
  8.         public void Execute(IServiceProvider serviceProvider)  
  9.         {  
  10.              
  11.         }  
  12.     }  
  13. }  
Step 4

Now, we need to add some boilerplate code for some null checks and obtain plugin context and organization Service reference. It's a good idea to maintain business logic separately, so BusinessLogic method in it will contain our Business Logic.
  1. using Microsoft.Xrm.Sdk;  
  2. using System;  
  3.   
  4. namespace AssignPlugin  
  5. {  
  6.     public class AssignTeamToUser : IPlugin  
  7.     {  
  8.         public void Execute(IServiceProvider serviceProvider)  
  9.         {  
  10.             if (serviceProvider == nullreturn;  
  11.   
  12.             // Obtain the Plugin Execution Context  
  13.             var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));  
  14.   
  15.             // Obtain the organization service reference.  
  16.             var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));  
  17.             var service = serviceFactory.CreateOrganizationService(context.UserId);  
  18.   
  19.             // To check depth   
  20.             if (context.Depth > 2) return;  
  21.   
  22.             // The InputParameters collection contains all the data passed in the message request.  
  23.             if (context.InputParameters.Contains("Target") && context.InputParameters["Target"is EntityReference)  
  24.             {  
  25.                 // Business logic goes here  
  26.                 new AssignTeamToUser().BusinessLogic(service, context);  
  27.             }  
  28.         }  
  29.   
  30.         private void BusinessLogic(IOrganizationService service, IExecutionContext context)  
  31.         {  
  32.             // Business Logic  
  33.         }  
  34.     }  
  35. }  
Step 5

While registering plugin in
Assign step, it will have two Input parameters in context with the name Target & Assignee, we will pick them in the variable for further use. See the code given below.
  1. private void BusinessLogic(IOrganizationService service, IExecutionContext context)    
  2. {    
  3.     // We are hitting Assign button on a Case record so Target will be a Case record    
  4.     var caseEntityReference = (EntityReference)context.InputParameters["Target"];    
  5.     
  6.     // Assignee could be a User or Team    
  7.     var teamEntityReference = (EntityReference)context.InputParameters["Assignee"];    
  8.     
  9.     // In our requirement it should be a Team, otherwise return    
  10.     if (teamEntityReference.LogicalName != "team"return;    
  11.   
  12. }   

Step 6

Now, let’s jump into the main game. We will retrieve all the users in the given team, using fetchXml. Add namespace Microsoft.Xrm.Sdk.Query in our class, so FetchExpression will be available for the user.
  1. private void BusinessLogic(IOrganizationService service, IExecutionContext context)  
  2.       {  
  3.           // We are hitting Assign button on a Case record so Target will be a Case record  
  4.           var caseEntityReference = (EntityReference)context.InputParameters["Target"];  
  5.   
  6.           // Assignee could be a User or Team  
  7.           var teamEntityReference = (EntityReference)context.InputParameters["Assignee"];  
  8.   
  9.           // In our requirement it should be a Team, if user it should return  
  10.           if (teamEntityReference.LogicalName != "team"return;  
  11.   
  12.           // fetchXml to retrieve all the users in a given Team  
  13.           var fetchXmlLoggedUserInTeam = @"  
  14.           <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>  
  15.             <entity name='systemuser'>  
  16.               <attribute name='systemuserid' />  
  17.               <link-entity name='teammembership' from='systemuserid' to='systemuserid' visible='false' intersect='true'>  
  18.                 <link-entity name='team' from='teamid' to='teamid' alias='ac'>  
  19.                   <filter type='and'>  
  20.                     <condition attribute='teamid' operator='eq' uitype='team' value='{0}' />  
  21.                   </filter>  
  22.                 </link-entity>  
  23.               </link-entity>  
  24.             </entity>  
  25.           </fetch>";  
  26.   
  27.           // Passing current Team is fetchXml and retrieving Team's user  
  28.           var users = service.RetrieveMultiple(new FetchExpression(string.Format(  
  29.               fetchXmlLoggedUserInTeam,  
  30.               teamEntityReference.Id))).Entities;  
  31.          
  32.       }  

Step 7

(Implement Condition 1)

If FetchExpression` in the last step returns no users, then we will assign case record for the default queue of the Team, using AddToQueueRequest, add namespace Microsoft.Crm.Sdk.Messages in order to use the same.

Here, we are retrieving Id of default Queue of Team, which is used in DestinationQueueId parameter of AddToQueueRequest and in Target parameter, our case record is given.

Now, complete the code for the given condition and it should look, as shown below.
  1. using Microsoft.Crm.Sdk.Messages;  
  2. using Microsoft.Xrm.Sdk;  
  3. using Microsoft.Xrm.Sdk.Query;  
  4. using System;  
  5.   
  6. namespace AssignPlugin  
  7. {  
  8.     public class AssignTeamToUser : IPlugin  
  9.     {  
  10.         public void Execute(IServiceProvider serviceProvider)  
  11.         {  
  12.             if (serviceProvider == nullreturn;  
  13.   
  14.             // Obtain the Plugin Execution Context  
  15.             var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));  
  16.   
  17.             // Obtain the organization service reference.  
  18.             var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));  
  19.             var service = serviceFactory.CreateOrganizationService(context.UserId);  
  20.   
  21.             // To check depth   
  22.             if (context.Depth > 2) return;  
  23.   
  24.             // The InputParameters collection contains all the data passed in the message request.  
  25.             if (context.InputParameters.Contains("Target") && context.InputParameters["Target"is EntityReference)  
  26.             {  
  27.                 // Business logic goes here  
  28.                 new AssignTeamToUser().BusinessLogic(service, context);  
  29.             }  
  30.         }  
  31.   
  32.         private void BusinessLogic(IOrganizationService service, IExecutionContext context)  
  33.         {  
  34.             // We are hitting Assign button on a Case record so Target will be a Case record  
  35.             var caseEntityReference = (EntityReference)context.InputParameters["Target"];  
  36.   
  37.             // Assignee could be a User or Team  
  38.             var teamEntityReference = (EntityReference)context.InputParameters["Assignee"];  
  39.   
  40.             // In our requirement it should be a Team, if user it should return  
  41.             if (teamEntityReference.LogicalName != "team"return;  
  42.   
  43.             // fetchXml to retrieve all the users in a given Team  
  44.             var fetchXmlLoggedUserInTeam = @"  
  45.             <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>  
  46.               <entity name='systemuser'>  
  47.                 <attribute name='systemuserid' />  
  48.                 <link-entity name='teammembership' from='systemuserid' to='systemuserid' visible='false' intersect='true'>  
  49.                   <link-entity name='team' from='teamid' to='teamid' alias='ac'>  
  50.                     <filter type='and'>  
  51.                       <condition attribute='teamid' operator='eq' uitype='team' value='{0}' />  
  52.                     </filter>  
  53.                   </link-entity>  
  54.                 </link-entity>  
  55.               </entity>  
  56.             </fetch>";  
  57.   
  58.             // Passing current Team is fetchXml and retrieving Team's user  
  59.             var users = service.RetrieveMultiple(new FetchExpression(string.Format(  
  60.                 fetchXmlLoggedUserInTeam,  
  61.                 teamEntityReference.Id))).Entities;  
  62.   
  63.             // Condition 1  
  64.             // If user count is zero case should be assigned to Team's default Queue  
  65.             if (users.Count == 0)  
  66.             {  
  67.                 var team = service.Retrieve("team", teamEntityReference.Id, new ColumnSet("queueid"));  
  68.   
  69.                 var addToQueueRequest = new AddToQueueRequest  
  70.                 {  
  71.                     Target = caseEntityReference,  
  72.                     DestinationQueueId = team.GetAttributeValue<EntityReference>("queueid").Id  
  73.                 };  
  74.                 service.Execute(addToQueueRequest);  
  75.             }  
  76.   
  77.         }      }  
  78. }  
Our first condition is done. You can directly jump to further sections to quickly test it. Follow the steps further to implement the rest of the conditions.

Step 8

(Implement Condition 2)
 
If the Team has users available in it, then cases should be assigned to the user. With the least number of cases, follow the else part in the code given below. 
Namespaces System.Collections.Generic & System.Linq needs to be added. 
  1. // Condition 1  
  2. // If user count is zero case should be assigned to Team's default Queue  
  3. if (users.Count == 0)  
  4. {  
  5.     var team = service.Retrieve("team", teamEntityReference.Id, new ColumnSet("queueid"));  
  6.   
  7.     var addToQueueRequest = new AddToQueueRequest  
  8.     {  
  9.         Target = caseEntityReference,  
  10.         DestinationQueueId = team.GetAttributeValue<EntityReference>("queueid").Id  
  11.     };  
  12.     service.Execute(addToQueueRequest);  
  13. }  
  14. else  
  15. {  
  16.     var caseCountAssignedToUser = new Dictionary<Guid, int>();  
  17.   
  18.     users.ToList().ForEach(user =>  
  19.     {  
  20.         var fetchXmlCaseAssignedToUser = @"  
  21.         <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>  
  22.           <entity name='incident'>  
  23.             <attribute name='incidentid' />  
  24.             <link-entity name='systemuser' from='systemuserid' to='owninguser' alias='ab'>  
  25.               <filter type='and'>  
  26.                 <condition attribute='systemuserid' operator='eq' uitype='systemuser' value='{0}' />  
  27.               </filter>  
  28.             </link-entity>  
  29.           </entity>  
  30.         </fetch>";  
  31.         var cases = service.RetrieveMultiple(new FetchExpression(string.Format(  
  32.             fetchXmlCaseAssignedToUser,  
  33.             user.Id))).Entities.ToList();  
  34.         caseCountAssignedToUser.Add(user.Id, cases.Count);  
  35.     });  
  36.   
  37.     var sortedCaseCount = from entry in caseCountAssignedToUser  
  38.                           orderby entry.Value ascending  
  39.                           select entry;  
  40.     var allUserWithSameLeastNumberOfCases = sortedCaseCount  
  41.         .Where(w => w.Value == sortedCaseCount.First().Value).ToList();  
  42.   
  43.     var targetUser = new Guid();  
  44.   
  45.     // Condition 1  
  46.     // Assign the case to user with least number of case assigned  
  47.     if (allUserWithSameLeastNumberOfCases.Count() == 1)  
  48.     {  
  49.         targetUser = sortedCaseCount.First().Key;  
  50.     }  
  51.   
  52.     var assign = new AssignRequest  
  53.     {  
  54.         Assignee = new EntityReference("systemuser", targetUser),  
  55.         Target = caseEntityReference  
  56.     };  
  57.   
  58.     service.Execute(assign);  
  59. }  

Step 9

(Implement Condition 3)
 
If more than one user has the same number of cases, then it should be assigned randomly, else follow the part in the code given below.
  1. // Condition 1  
  2. // Assign case to user with least number of case assigned  
  3. if (allUserWithSameLeastNumberOfCases.Count() == 1)  
  4. {  
  5.     targetUser = sortedCaseCount.First().Key;  
  6. }  
  7. // Condition 2  
  8. // If more than one users are having same least number of users, then it be assigned randomly  
  9. else  
  10. {  
  11.     var randomUser = new Random().Next(0, allUserWithSameLeastNumberOfCases.Count() - 1);  
  12.     targetUser = allUserWithSameLeastNumberOfCases[randomUser].Key;  
  13. }  
  14.   
  15. var assign = new AssignRequest  
  16. {  
  17.     Assignee = new EntityReference("systemuser", targetUser),  
  18.     Target = caseEntityReference  
  19. };  
  20. service.Execute(assign);  
Step 10
 
(Sign the Assembly)

In order to use the plugin in Dynamics 365/CRM assembly has to be signed.

  • To sign, right click on the project and click Properties.

  • Click on signing on the left pane

  • Check Sign the assembly checkbox.

  • In Choose a strong name key file dropdown, select new.

  • In dialog box, give some name for the key.

  • Uncheck Protect my key file with a password (Optional).

  • Hit OK to save.

    Dynamics CRM 

 
Now, our final code looks, as shown below.
  1. using Microsoft.Crm.Sdk.Messages;  
  2. using Microsoft.Xrm.Sdk;  
  3. using Microsoft.Xrm.Sdk.Query;  
  4. using System;  
  5. using System.Collections.Generic;  
  6. using System.Linq;  
  7.   
  8. namespace AssignPlugin  
  9. {  
  10.     public class AssignTeamToUser : IPlugin  
  11.     {  
  12.         public void Execute(IServiceProvider serviceProvider)  
  13.         {  
  14.             if (serviceProvider == nullreturn;  
  15.   
  16.             // Obtain the Plugin Execution Context  
  17.             var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));  
  18.   
  19.             // Obtain the organization service reference.  
  20.             var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));  
  21.             var service = servicefonFactory.CreateOrganizationService(context.UserId);  
  22.   
  23.             // To check depth   
  24.             if (context.Depth > 2) return;  
  25.   
  26.             // The InputParameters collection contains all the data passed in the message request.  
  27.             if (context.InputParameters.Contains("Target") && context.InputParameters["Target"is EntityReference)  
  28.             {  
  29.                 // Business logic goes here  
  30.                 new AssignTeamToUser().BusinessLogic(service, context);  
  31.             }  
  32.         }  
  33.   
  34.         private void BusinessLogic(IOrganizationService service, IExecutionContext context)  
  35.         {  
  36.             // We are hitting Assign button on a Case record so Target will be a Case record  
  37.             var caseEntityReference = (EntityReference)context.InputParameters["Target"];  
  38.   
  39.             // Assignee could be a User or Team  
  40.             var teamEntityReference = (EntityReference)context.InputParameters["Assignee"];  
  41.   
  42.             // In our requirement it should be a Team, if user it should return  
  43.             if (teamEntityReference.LogicalName != "team"return;  
  44.   
  45.             // fetchXml to retrieve all the users in a given Team  
  46.             var fetchXmlLoggedUserInTeam = @"  
  47.             <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>  
  48.               <entity name='systemuser'>  
  49.                 <attribute name='systemuserid' />  
  50.                 <link-entity name='teammembership' from='systemuserid' to='systemuserid' visible='false' intersect='true'>  
  51.                   <link-entity name='team' from='teamid' to='teamid' alias='ac'>  
  52.                     <filter type='and'>  
  53.                       <condition attribute='teamid' operator='eq' uitype='team' value='{0}' />  
  54.                     </filter>  
  55.                   </link-entity>  
  56.                 </link-entity>  
  57.               </entity>  
  58.             </fetch>";  
  59.   
  60.             // Passing current Team is fetchXml and retrieving Team's user  
  61.             var users = service.RetrieveMultiple(new FetchExpression(string.Format(  
  62.                 fetchXmlLoggedUserInTeam,  
  63.                 teamEntityReference.Id))).Entities;  
  64.   
  65.             // Condition 1  
  66.             // If user count is zero case should be assigned to Team's default Queue  
  67.             if (users.Count == 0)  
  68.             {  
  69.                 // Retrieving Team's default Queue  
  70.                 var team = service.Retrieve("team", teamEntityReference.Id, new ColumnSet("queueid"));  
  71.   
  72.                 var addToQueueRequest = new AddToQueueRequest  
  73.                 {  
  74.                     // Case record  
  75.                     Target = caseEntityReference,  
  76.                     // Team's default Queue Id  
  77.                     DestinationQueueId = team.GetAttributeValue<EntityReference>("queueid").Id  
  78.                 };  
  79.                 service.Execute(addToQueueRequest);  
  80.             }  
  81.             else  
  82.             {  
  83.                 // Dictionary to save UserId and number of case assigned pair  
  84.                 var caseCountAssignedToUser = new Dictionary<Guid, int>();  
  85.   
  86.                 users.ToList().ForEach(user =>  
  87.                 {  
  88.                     // FetchXml query to retrieve number cases assigned to each user  
  89.                     var fetchXmlCaseAssignedToUser = @"  
  90.                     <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>  
  91.                       <entity name='incident'>  
  92.                         <attribute name='incidentid' />  
  93.                         <link-entity name='systemuser' from='systemuserid' to='owninguser' alias='ab'>  
  94.                           <filter type='and'>  
  95.                             <condition attribute='systemuserid' operator='eq' uitype='systemuser' value='{0}' />  
  96.                           </filter>  
  97.                         </link-entity>  
  98.                       </entity>  
  99.                     </fetch>";  
  100.   
  101.                     var cases = service.RetrieveMultiple(new FetchExpression(string.Format(  
  102.                         fetchXmlCaseAssignedToUser,  
  103.                         user.Id))).Entities.ToList();  
  104.   
  105.                     // Adding user id with number of cases assigned to Dictionay defined above  
  106.                     caseCountAssignedToUser.Add(user.Id, cases.Count);  
  107.                 });  
  108.   
  109.                 // Sorting in ascending order by number of cases  
  110.                 var sortedCaseCount = from entry in caseCountAssignedToUser  
  111.                                       orderby entry.Value ascending  
  112.                                       select entry;  
  113.   
  114.                 // Getting all the users with least sae number of cases  
  115.                 var allUserWithSameLeastNumberOfCases = sortedCaseCount  
  116.                     .Where(w => w.Value == sortedCaseCount.First().Value).ToList();  
  117.   
  118.                 var targetUser = new Guid();  
  119.   
  120.                 // Condition 1  
  121.                 // Assign case to user with least number of case assigned  
  122.                 if (allUserWithSameLeastNumberOfCases.Count() == 1)  
  123.                 {  
  124.                     targetUser = sortedCaseCount.First().Key;  
  125.                 }  
  126.                 // Condition 2  
  127.                 // If more than one users are having same least number of users, then it be assigned randomly  
  128.                 else  
  129.                 {  
  130.                     var randomUser = new Random().Next(0, allUserWithSameLeastNumberOfCases.Count() - 1);  
  131.                     targetUser = allUserWithSameLeastNumberOfCases[randomUser].Key;  
  132.                 }  
  133.   
  134.                 var assign = new AssignRequest  
  135.                 {  
  136.                     Assignee = new EntityReference("systemuser", targetUser),  
  137.                     Target = caseEntityReference  
  138.                 };  
  139.   
  140.                 service.Execute(assign);  
  141.             }  
  142.   
  143.         }  
  144.     }  
  145. }  
Section 2  
 
Deploying in Dynamics 365/CRM
 
 To deploy the plugin to CRM
  • Build the project.

  • Open Plugin Registration tool and connect it to your Dynamics CRM instance.

    Dynamics CRM
  • Click Register, followed by clicking Register new assembly.

  • In popup Step 1, select your DLL from the debug folder, which we have just created and the location will be similar to this

           "C:\Users\AshV\Documents\Visual Studio 2017\Projects\AssignPlugin\AssignPlugin\bin\Debug\AssignPlugin.dll"
  • In Step 2, check select all and hit Register Selected Plugin button.

    Dynamics CRM 
  • After success, you will see the message given below.

    Dynamics CRM
  • Now, a new step has to be resistered on Assign step of Case record, else this plugin will not be triggered. To regster, right click on assemly and click Register new step.

    Dynamics CRM
  • In dialogbox, select Message as Assign and Primary Entity as an incident (i.e. case). 

    Dynamics CRM 
  •  Hit Register
Section 3
 
Configuration in CRM & Verifying Functionality
  • Create Team with Administrator priviledges by following Settings -> Security -> Teams and create new Team. I have given Team name as Quick Service Team.

    Dynamics CRM
  •  Assign System Administrator Role to the newly created Team.

    Dynamics CRM
  • Now, this Team has no users. If any case is assigned to this Team it will go to the default queue, let's try it out.
  • Hit Assign, select Assign To as a User or Team, select Quick Service Team and hit Asssign.

    Dynamics CRM 
  •  After assigning hit Queue Item Details to verify whether it is assigned to the Queue or not.

    Dynamics CRM
  •  To verify the rest of two conditions, create a few more users in Team and try assigning the record to Team and see how records are getting assigned.
You can find this code in my GitHub repo as well https://github.com/AshishVishwakarma/AssignPluginDynamics365. For any query regarding this, feel free to get in touch with me.