Wednesday, October 31, 2012

Pin recently visited items for users with the SDK

It seemed like it would be a good idea to be able to manipulate the recently visited items for users, maybe by default have some frequently used records pre-pinned for easy access for new users.
1
A look through the organization database shows recently viewed information stored in the UserEntityUISettingsBase table in the RecentlyViewedXML field.
Something like this - one record for each user/entity type combination:
<RecentlyViewedEntityData etc="1">
    <RecentlyViewedItem>
        <Type>0</Type>
        <ObjectId>{669A5DCE-BD56-E111-9BBC-000C29336979}</ObjectId>
        <EntityTypeCode>1</EntityTypeCode>
        <DisplayName>Account</DisplayName>
        <Title>Unusual Store (sample)</Title>
        <Action/>
        <IconPath/>
        <PinStatus>false</PinStatus>
        <LastAccessed>06/02/2012 05:49:55</LastAccessed>
    </RecentlyViewedItem>
    <RecentlyViewedItem>
        <Type>0</Type>
        <ObjectId>{4E9A5DCE-BD56-E111-9BBC-000C29336979}</ObjectId>
        <EntityTypeCode>1</EntityTypeCode>
        <DisplayName>Account</DisplayName>
        <Title>A Store (sample)</Title>
        <Action/>
        <IconPath/>
        <PinStatus>false</PinStatus>
        <LastAccessed>06/08/2012 04:10:29</LastAccessed>
    </RecentlyViewedItem>
</RecentlyViewedEntityData>

Of course right away I saw the <PinStatus> element . I turned to the SDK and found the UserEntityUISettings class having a property called RecentlyViewedXml. Seemed pretty straight forward - retrieve the XML, set the PinStatus element to true, and update.

As a proof of concept I put together some code to update all the Account records related to my user and set them to be pinned. When I was done it didn't work (or so I thought). The code was correct, retrieving the XML again showed all the Accounts with a PinStatus of true but in the browser nothing showed up as pinned.

My first clue should have been the LastAccessed date - on the first example it is 06/02/2012 - surely I've accessed more than the 40 records kept on the recently viewed items list since then. After reviewing the other records, the dates were all over the place - many were several months old.

Another observation was that when you cleared your browser cache items disappeared from the list. After doing so I noticed that the Account records I had set to be pinned were now in fact present and pinned! A tip from a forum post indicated recently viewed items were stored locally on the user's machine.

<system drive>:\Users\<User>\AppData\Roaming\Microsoft\Internet Explorer\UserData

If you drill into the folders and files here (prior to deleting your browser cache) you'll see a representation of the recently viewed items. After clearing the cache these items were no longer there and the items in the database took over.

Not knowing exactly what triggers CRM to store some recently viewed items in the database and others not is still a bit of a mystery. But this does appear to validate that you can change the recently viewed items in the database by adding, removing, or pinning items. Provided you get the users to clear their browser cache after you make the change, any pinned items you add should show up. Theoretically items that are pinned should then stay pinned and not drop off as more records are viewed and having them in the database prevents them from being wiped out when the browser cache is cleared.

Sample code - this updates the PinStatus for all Account records already resent in the database for the provided user.
QueryExpression query = new QueryExpression
{
    EntityName = UserEntityUISettings.EntityLogicalName,
    ColumnSet = new ColumnSet(true),
    Criteria =
    {
        Filters = 
        {
            new FilterExpression
            {
                FilterOperator = LogicalOperator.And,
                Conditions = 
                {
                    new ConditionExpression("owninguser", 
                        ConditionOperator.Equal, myId),
                    new ConditionExpression("recentlyviewedxml", 
                        ConditionOperator.NotNull)
                }
            }
        }
    }
};

DataCollection<Entity> result =
    service.RetrieveMultiple(query).Entities;

foreach (Entity entity in result)
{
    string rvXML = entity.Attributes["recentlyviewedxml"].ToString();
    XElement xRVXML = XElement.Parse(rvXML);
    string type = xRVXML.FirstAttribute.Value;

    if (type == "1")
    {
        IEnumerable<XElement> rvItems = from rvElem in
                                        xRVXML.Descendants("RecentlyViewedItem")
                                        select rvElem;
        foreach (XElement rvItem in rvItems)
        {
            IEnumerable<XElement> pStatuses = from rviElem in
                                              rvItem.Descendants("PinStatus")
                                              select rviElem;
            foreach (XElement pinStatus in pStatuses)
            {
                pinStatus.Value = "true";
            }
        }
        entity.Attributes["recentlyviewedxml"] = xRVXML.ToString();
        service.Update(entity);
    }
}

Good Luck!