Example Activity: Retrieve Most Available User

Now that you have a better understanding of some of the more advanced concepts in custom activity development, we can look at a real-world example that comes up frequently. Often, business users define a process that is used to assign leads to salespeople or cases to support staff. The process varies from organization to organization, but regardless of what it is, it can usually be accomplished using a custom workflow activity.

To demonstrate, we’ll create an activity called RetrieveMostAvailableUserActivity. Our implementation focuses on retrieving the most available user based on the user’s current case load. These concepts could just as easily be applied to the user with the fewest leads assigned or any other entity for that matter. In addition, we’ll allow the person designing the workflow to optionally specify a Team to constrain the list of users examined to a smaller set. To start, add a new class named RetrieveMostAvailableUserActivity to the ProgrammingWithDynamicsCrm4.Workflow project. Stub out the contents of this new class to match Example 6-11.

Example 6-11. Start of the RetrieveMostAvailableUserActivity class

using System;
using System.Collections.Generic;
using System.Workflow.ComponentModel;
using System.Xml;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Workflow;

namespace ProgrammingWithDynamicsCrm4.Workflow
{
    [CrmWorkflowActivity("Retrieve Most Available User",
        "Programming Microsoft CRM 4")]
    public class RetrieveMostAvailableUserActivity : Activity
    {

    }
}

By now you should be familiar with this starting point. We have a class derived from Activity that is decorated with the CrmWorkflowActivity attribute to define the human-readable name of the activity.

Next, we define the dependency properties we need. Insert the code shown in Example 6-12 into RetrieveMostAvailableUserActivity’s definition.

Example 6-12. RetrieveMostAvailableUserActivity’s dependency properties

public static DependencyProperty TeamProperty =
    DependencyProperty.Register("Team", typeof(Lookup),
    typeof(RetrieveMostAvailableUserActivity));

[CrmInput("Team")]
[CrmReferenceTarget("team")]
public Lookup Team
{
    get { return (Lookup)GetValue(TeamProperty); }
    set { SetValue(TeamProperty, value); }
}

public static DependencyProperty MostAvailableUserProperty =
    DependencyProperty.Register("MostAvailableUser", typeof(Lookup),
    typeof(RetrieveMostAvailableUserActivity));

[CrmOutput("Most Available User")]
[CrmReferenceTarget("systemuser")]
public Lookup MostAvailableUser
{
    get { return (Lookup)GetValue(MostAvailableUserProperty); }
    set { SetValue(MostAvailableUserProperty, value); }
}

Here we’re adding two properties, Team and MostAvailableUser. Team is an input property and is optionally used to limit the set of users evaluated. The CrmInput attribute specifies that the property is used for input and defines the human-readable name for it, while the CrmReferenceTarget attribute tells Microsoft Dynamics CRM that this Lookup property only accepts references to the team entity. Similarly, we specify that the MostAvailableUser property is used for output and that it only references systemuser entities.

With our dependency properties in place, we are ready to move on and define the Execute method. Place the code shown in Example 6-13 beneath the dependency properties, but still within the RetrieveMostAvailableUserActivity definition.

Example 6-13. RetrieveMostAvailableUserActivity’s Execute method

protected override ActivityExecutionStatus Execute(
    ActivityExecutionContext executionContext)
{
    IContextService contextService = executionContext.GetService<IContextService>();
    IWorkflowContext workflowContext = contextService.Context;
    ICrmService crmService = workflowContext.CreateCrmService(true);
    List<systemuser> users = RetrieveSystemUsers(crmService);
    systemuser mostAvailableUser = RetrieveMostAvailableUser(crmService, users);

    if (mostAvailableUser != null)
    {
        this.MostAvailableUser = new Lookup(
            EntityName.systemuser.ToString(),
            mostAvailableUser.systemuserid.Value);
    }

    return ActivityExecutionStatus.Closed;
}

Our Execute method starts by obtaining an IWorkflowContext using the technique shown earlier in the chapter. However, notice that it passes in true for the runAsAdmin argument. Because we are accessing records that the workflow user may not have access to in order to determine the most available user, we need to elevate the CrmService calls to run as the admin user. We then proceed to call two methods to first retrieve the list of potential users and then determine which of those users is the most available to take on an additional case. Finally, if a user is found, we assign that user to our MostAvailableUser output property.

Let’s continue by looking at the RetrieveSystemUsers method. Example 6-14 displays the implementation.

Example 6-14. The RetrieveSystemUsers method

private List<systemuser> RetrieveSystemUsers(ICrmService crmService)
{
    QueryExpression query = new QueryExpression();

    ColumnSet cols = new ColumnSet();
    cols.AddColumns("systemuserid", "fullname");
    query.ColumnSet = cols;

    query.EntityName = EntityName.systemuser.ToString();

    if (this.Team != null)
    {
        LinkEntity teamMembershipLink = query.AddLink(
            "teammembership", "systemuserid", "systemuserid");

        teamMembershipLink.LinkCriteria.AddCondition(
            "teamid", ConditionOperator.Equal, this.Team.Value);
    }

    RetrieveMultipleRequest request = new RetrieveMultipleRequest();
    request.Query = query;

    RetrieveMultipleResponse response =
        (RetrieveMultipleResponse)crmService.Execute(request);
    List<systemuser> users = new List<systemuser>();
    foreach (systemuser user in response.BusinessEntityCollection.BusinessEntities)
    {
        users.Add(user);
    }

    return users;
}

RetrieveSystemUsers is fairly straightforward. It sets up a RetrieveMultipleRequest and executes it using the ICrmService passed in to it. It then extracts the systemuser entities from the response and puts them into a list, which is returned. The only part of this method that might warrant a little more explanation is the filtering by team. If the Team input property is defined, the query is joined to the teammembership intersect table, which has a teamid attribute that can be used to return only the users who belong to a particular team.

Now that we have our list of potential users, we need to determine which of them has the lightest case load. This is what the RetrieveMostAvailableUser method is designed to accomplish. Example 6-15 shows its definition.

Example 6-15. The RetrieveMostAvailableUser method

private systemuser RetrieveMostAvailableUser(
    ICrmService crmService, List<systemuser> users)
{
    systemuser mostAvailableUser = null;

    int leastCases = int.MaxValue;

    foreach (systemuser user in users)
    {
        int count = RetrieveCaseCountForUser(crmService, user.systemuserid.Value);

        if (count == 0)
        {
            mostAvailableUser = user;
            break;
        }
        else if (count < leastCases)
        {
            mostAvailableUser = user;
            leastCases = count;
        }

    }

    return mostAvailableUser;
}

RetrieveMostAvailableUser simply iterates through the list of potential users and calls RetrieveCaseCountForUser for each of them. As it loops through the users, it keeps track of the user with the lowest case count and returns that user at the end. If at any point a user with zero cases is found, the method stops looping through the users because there cannot be a user with fewer than zero cases.

The only method left to look at now is RetrieveCaseCountForUser. Example 6-16 shows the definition of RetrieveCaseCountForUser.

Example 6-16. The RetrieveCaseCountForUser method

private int RetrieveCaseCountForUser(ICrmService crmService, Guid userId)
{
    string fetchXml = String.Format(@"
            <fetch mapping='logical' aggregate='true'>
                <entity name='incident'>
                    <attribute name='incidentid' aggregate='count' alias='count' />
                    <filter type='and'>
                        <condition attribute='ownerid' operator='eq' value='{0}' />
                        <condition attribute='statecode' operator='eq' value='0' />
                    </filter>
                </entity>
            </fetch>",
        userId);

    XmlDocument resultsDoc = new XmlDocument();
    resultsDoc.LoadXml(crmService.Fetch(fetchXml));

    XmlNode countNode = resultsDoc.SelectSingleNode("//count/text()");
    int count = int.Parse(countNode.Value);
    return count;
}

The limited aggregate functionality supported by the CRM services is exposed through the Fetch method of ICrmService. We prepare our fetch statement by initializing a string with XML that filters cases based on the user’s ID and an active statecode attribute. By specifying aggregate=’true’ on the fetch node and aggregate=’count’ on the attribute node, we tell CRM to return the count of entities that match the given criteria. The Fetch method returns a string containing something like the following:

<resultset morerecords="0"><result><count>71</count></result></resultset>

We can now use a simple XPath statement to return the text of the count node, which is the number of active cases assigned to the specified user.

After compiling and deploying this example, you should be able to configure a workflow to automatically assign newly created cases to the user with the lightest case load. It might also be a good idea to mark the workflow as available to run on demand, so that a case could easily be reassigned using the workflow’s logic manually as well as automatically. Although the logic used to determine the user to assign a case or lead to may differ, by using the techniques shown in this example you can create a flexible activity that you can use in multiple workflows.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset