Tuesday, September 25, 2007

Maximum Number of Simultaneous Workflows

In a post here on SharePoint 2007 maximum limitations, Harshawardhan Chiplonkar - a member of the SharePoint Developer Support team - gave "workflows that can be run" as 15, clarifiying this by adding that this is the maximum number of simultaneous workflows that can be in memory executing code, not the number of workflow instances in progress in the database.

If 15 sounds like a small number for a "hard limit", you're not alone.

It looks like the 15 limit Harsh mentions might actually be the WorkflowEventDeliveryThrottle, which is 15 by default. This throttle can be modified (see below) - try setting it to a very low number, and you should see that workflow activation slows dramatically.



SPWebService spWebService = SPFarm.Local.Services.GetValue<SPWebService>();
spWebService.WorkflowEventDeliveryThrottle = 20; // default is 15
spWebService.Update();

Friday, September 14, 2007

Locked Workflow

I've periodically come across this SPException: "This task is currently locked by a running workflow and cannot be edited" when using the SPWorkflowTask.AlterTask method, even when it seems that the workflow is not in fact locked, and is instead patiently listening for an OnTaskChangedEvent. It turns out that this exception is thrown when the WorkflowVersion of the task list item is not equal to 1, which, if you believe the error message is the same thing as checking to see if the workflow is locked. Only it isn't - apparently sometimes at least, the Workflow version is non zero and the workflow is not locked (the InternalState flag of the workflow does not include the Locked flag bits). I'm not sure why this is occurring - maybe the error message is misleading - but the following code demonstrates a dodgy sort of a workaround that I've found useful. I've no idea if this is a good idea or not, so please treat with skepticism...

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using System.Collections;
using System.Threading;
 
namespace DevHoleDemo
{
    public class WorkflowTask
    {
        public static bool AlterTask(SPListItem task, Hashtable htData, bool fSynchronous, int attempts, int millisecondsTimeout)
        {
            if ((int)task[SPBuiltInFieldId.WorkflowVersion] != 1)
            {
                SPList parentList = task.ParentList.ParentWeb.Lists[new Guid(task[SPBuiltInFieldId.WorkflowListId].ToString())];
                SPListItem parentItem = parentList.Items.GetItemById((int)task[SPBuiltInFieldId.WorkflowItemId]);
                for (int i = 0; i < attempts; i++)
                {
                    SPWorkflow workflow = parentItem.Workflows[new Guid(task[SPBuiltInFieldId.WorkflowInstanceID].ToString())];
                    if (!workflow.IsLocked)
                    {
                        task[SPBuiltInFieldId.WorkflowVersion] = 1;
                        task.SystemUpdate();
                        break;
                    }
                    if (i != attempts - 1)
                        Thread.Sleep(millisecondsTimeout);
                }
            }
            return SPWorkflowTask.AlterTask(task, htData, fSynchronous);
        }
    }
}

Monday, September 10, 2007

Using a ControlClass to render an EditControlBlock menu

I found this usefull post on a CustomAction feature for list item context menus, but ran into a problem when I wanted to use ControlClass code rather than a UrlAction. This post details the problem, and it seems it can't be done because context menu items don't appear to be rendered on the server as WebControls. Html is generated that includes the UrlAction, which comes from the feature's xml definition.

If you're determined not to redirect to a different page, it is possible to trap for a postback event in another (2nd) custom action implemented as a WebControl on the StandardMenu. E.g. (in the feature definition):



<UrlAction Url="javascript:__doPostBack(&#39;MyEventTarget&#39;, &#39;{ItemId}&#39;);"/>


In the second menu CustomAction WebControl code:

        protected override void OnLoad(EventArgs e)
        {
            this.EnsureChildControls();
            base.OnLoad(e);
            if (this.Page.Request["__EVENTTARGET"] == "MyEventTarget")
            {
                int itemId = Convert.ToInt32(this.Page.Request["__EVENTARGUMENT"]);
            }
        }