Geeks With Blogs
Adrian Hara Working through the .NET maze

If you're developing workflows for SharePoint 2007, you are probably familiar with the Item property of the SPWorkflowActivationProperties class. Usually your workflow gets an instance of SPWorkflowActivationProperties when the OnWorkflowActivated activity executes, which is bound to a public field on the workflow class so you can hang on to it for the lifetime of the workflow. Perhaps one of the more useful properties of this class is Item, which returns an instance of SPListItem, representing the list item on which the workflow runs.

This is all fine, but maybe for more than two step workflows or for a better design/maintainability of the code, it makes sense (and I try to do it) to encapsulate the properties (access to) and whatever manipulating logic of the list item in a business class, representing the business concept you're dealing with (for example, I find that using classes like Contract or Customer to work with the item's metadata is nicer than working with SPListItem). A small problem with this approach is that you can't simply wrap the SPListItem instance in your business class, because it's not [Serializable], which means that you'll get some nasty error when SharePoint tries to dehydrate your workflow. In light of this, what I do is wrap a reference to the SPWorkflowActivationProperties instance and have a private property called ListItem which just calls the Item property, like so:

        private SPListItem ListItem
        {
              get { return workflowProperties.Item; }
        }

This worked very good for me until a few days ago, when I had a workflow that runs over items in a document library and had to replace the actual document file at some point (like replace a .doc with a generated .pdf from it). The replacement worked fine (I won't go into details, google has answers ;)), but after the replacement, if, during the same call (meaning before the workflow gets dehydrated again), I wanted to do something else with the file, or even call Update() on the SPListItem, exceptions got thrown, with messages like "the file has been modified by blah blah". After some investigations I realized that that although I was modifying the SPFile (which i got from SPListItem.File) and calling Update() on it, this didn't do anything (for example, looking at the SPListItem.File.Name, I'd still get "somepath/somename.doc" instead of "somepath/somename.pdf"). Also, the next logical solution, calling Update() or SystemUpdate() on the SPListItem itself would throw, as stated above. However, if, after modifying and/or replacing the file, I wouldn't do anything else and just let the workflow dehydrate, whenever it would get hydrated again, everything would work: this makes sense, since the SPListItem is not serialized and would be created again after rehydration.

So, the next step would be looking when is the SPListItem of SPWorkflowActivationProperties created and how can we refresh it "on the fly". Reflector shows the following:

ItemProperty

As you can see, the item is actually cached after the first call. This means that when updating the SPFile associated with the item and calling Update() on it, since this doesn't cause the item itself to update (or at least update its SPFile), we have to somehow refresh the whole SPListItem. While the SPWorkflowActivationProperties class doesn't expose some method to do this, it's easy to do with reflection: just set the m_item to null and on the next call to the property, it will be recreated. One thing to note here is that, as seen in the picture above, there are two cases where the item can be refreshed:

  • for non super user workflows, a call is made to the list directly for the item
  • for super user workflow, a call is made to SPWorkflow.ParentItem

Looking at the SPWorkflow.ParentItem with reflector, it turns out that the value backing the property is also cached:

ParentItemProperty

So, in order to be fully covered for all situations, the method to invalidate the list item and make sure it's refreshed should look like so:

        private void InvalidateWorkflowPropertiesItem()
        {
            FieldInfo parentItemField = workflowProperties.Workflow.GetType().GetField("m_createdParentItem", BindingFlags.NonPublic | BindingFlags.Instance);
            parentItemField.SetValue(workflowProperties.Workflow, null);

            FieldInfo itemField = workflowProperties.GetType().GetField("m_item", BindingFlags.NonPublic | BindingFlags.Instance);
            itemField.SetValue(workflowProperties, null);
        }

 

Normally, you should just call this method after doing something that causes the SPFile for an item to change, but I guess there are probably other situations where refreshing of the list item would come in handy.

ps: another approach to the basic problem that the SPListItem isn't serializable would be to keep it's unique id and retrieve it like that every time you need it, but this could mean implementing the caching and getting mechanisms yourself if you want to have some business class wrapper over it (meaning that you can't just GetByUniqueId() it every time you need to change a property, because you'd end up with a different instance every time and would have to be careful about updating it)

Posted on Friday, August 8, 2008 11:28 AM Sharepoint Gotchas | Back to top


Comments on this post: Refresh SPWorkflowActivationProperties Item

# re: Refresh SPWorkflowActivationProperties Item
Requesting Gravatar...
Today I've tried both of your suggestions and neither worked.
This item updating problem is really getting annoying.

Here is my code (It's so simple).

char[] delimiterChars = { '.' };
string[] arr = workflowProperties.Item.File.Name.Split(delimiterChars);

SPListItem wfi = WorkflowItem; //using "Jason's" 'get' method
if (arr.Length > 1) {
wfi["Field1"] = arr[0];
wfi["Field2"] = arr[1];
wfi["Original Creator"] = workflowProperties.Item["Created By"];

wfi.Update();
}


Any suggestions?
Left by Israel Smith on Feb 11, 2009 8:31 PM

# re: Refresh SPWorkflowActivationProperties Item
Requesting Gravatar...
Israel, what happens? Do you get an exception? Or just nothing happens and the item is not updated?
Left by Adrian Hara on Feb 12, 2009 8:58 AM

# re: Refresh SPWorkflowActivationProperties Item
Requesting Gravatar...
Hi,

Thanks for your efforts to track down that behaviour! I'm getting random errors in a long running workflow process that show up saying the workflowProperties.Item causes "Object not set to an instance". Also, whilst I don't know if its causing problems I'm seeing entries in the ULS saying that another thread is accessing the SPsite etc just after the workflow instance is kicked off. I've read Hristo Pavlov's excellent exploration into the SPRequest and concluded cacheing is involved all over the place.

(see htttp://hristopavlov.wordpress.com/2009/01/19/understanding-sharepoint-sprequest/)

I'm hoping the newly released SPDiag from Microsoft will help us all, there simply is not enough guidance and best practice for these update scenarios. http://blogs.msdn.com/rogerla/archive/2009/02/05/sharepoint-diagnostics-tool-v1-0-spdiag-released.aspx

Thanks again!
Left by Anthony on Feb 24, 2009 4:51 PM

# re: Refresh SPWorkflowActivationProperties Item
Requesting Gravatar...
I was sent this article today. I'm having the problem that is mentioned here with the update. It's not throwing an exception, it's just not updating at all. Here's my code with comments on the values at each stage:

SPWorkflowActivationProperties workflowProperties = new SPWorkflowActivationProperties();
SPListItem item = workflowProperties.Item;
// item["MBO Manager"] = "Bob Smith" at this point
item["MBO Manager"] = "BOOGEYMAN!!!";
// item["MBO Manager"] = "BOOGEYMAN!!!" at this point, but properties does not show "BOOGEYMAN!!!" anywhere.  It shows "Bob Smith" as a value for one of the properties, but the key is a guid
item.SystemUpdate(false);
// item["MBO Manager"] reverts back to "Bob Smith"

I have also tried item.Update(), item.UpdateOverWriteVersion() - neither of those seem to update it either.  I turned on versioning just to make sure that if it was trying to update the version anyway it could.

I'm at the end of my rope with this. Any help anyone can provide would be most appreciated.
Left by Brian on Dec 07, 2009 7:47 PM

Your comment:
 (will show your gravatar)


Copyright © Adrian Hara | Powered by: GeeksWithBlogs.net