OnTaskCreated activity is trouble.

I meant to write this for quite a while and kept forgetting about it...
 
We've had numerous situations while developing Windows Workflow Foundation workflows in which either the OnTaskCreated or the OnTaskChanged activity would not be called ("fire"). The behaviour was non-deterministic, whereas sometimes it would work and then it wouldn't. E.g. I'd add an arbitrary line of code and then it would work.
 
Well, long story short, after following all the paths that could normally go wrong, like checking whether the taskID and the taskProperties are properly set, and also to check that all objects could serialize ok, and installing all relevant Hotfixes and Updates etc., I resorted to a recommendation to simply leave out the OnTaskCreated activity and have not had any problems ever since.

Published: Aug-27-09 | 2 Comments | Link to this post

Task Links for Custom SharePoint Workflow Notifications

If you're building a workflow solution using Windows Workflow Foundation and Windows SharePoint Services 3.0, you have to implement aspx forms. What's more, you also have to design your own task (or other) notification forms.
 
I guess everyone would want to have the convenience of having links back to the task, the task item and the workflow status. Even the ability to "Connect Outlook" to the Workflow Tasklist would be nice, so that all tasks are not only available on the web, but also within Outlook.
 
Of course these links need to be constructed at runtime, as IDs and names depend on the specific lists and task items being used.
 
The Html Template
The resource that is used to dynamically build these links is rather simple:
 

 <DIV>

    <P style="MARGIN-BOTTOM: 8px; BACKGROUND: blue">

        <B><SPAN style="COLOR: white">&nbsp; Workflow Links:</SPAN></B>

    </P>

    <DIV style="MARGIN-LEFT: 8px">

        1. <A href="{ListItemUrl}"> Link to the List Item the workflow was started on</A> <BR>

        2. <A href="{TaskUrl}"> Link to the Task</A> <BR>

        3. <A href="{WorkflowStatusUrl}"> Link to the Workflow Status</A> <BR>

        4. <A href="{ConnectOutlookUrl}"> Connect Outlook to Workflow Task List</A> <BR>

    </DIV>

 </DIV>

 

The essential parts are marked in red. These are the placeholders we have to change at runtime.

 

ListItemUrl & TaskUrl

The code to exchange the ListItemUrl and the TaskUrl is quite simple too. As you can see the "workflowProperties" and the "activity" have all the data we need to concatenate the pieces for the links:

 

String webUrl = workflowProperties.WebUrl;

bodyStr = bodyStr.Replace("{ListItemUrl}", webUrl

                           + workflowProperties.ListUrl + "/DispForm.aspx?ID="

                           + workflowProperties.ItemId.ToString());

bodyStr = bodyStr.Replace("{TaskUrl}", webUrl + workflowProperties.TaskListUrl

                           + "/DispForm.aspx?ID=" + activity.ListItemId.ToString());

 

WorkflowStatusUrl

For the WorkflowStatusUrl we need to get the GUIDs for the List that holds the item the workflow was started on, as well as the Workflow Instance and then add the curly braces and escape the "-". The following code section has the little helper method that does that, as well as the code to concatenate the link:

 

private String formatGuidString(String s)

{

    s = "%7b" + s;

    s = s + "%7d";

    s = s.Replace("-", "%2d");

    return s;

}

 

...

 

String strListID = formatGuidString(workflowProperties.Workflow.ParentList.ID.ToString());

String strInstanceID = formatGuidString(workflowProperties.Workflow.InstanceId.ToString());

bodyStr = bodyStr.Replace("{WorkflowStatusUrl}", webUrl + "/_layouts/WrkStat.aspx?List="

                          + strListID + "&WorkflowInstanceID=" + strInstanceID); 

 

ConnectOutlookUrl

Last not least, the link that I like the most. The Connect to Outlook menu command in SharePoint is already a great value, but with Workflows I find it quite cumbersome to send users to the Workflow Tasklist to have them manually connect to Outlook from there, especially when this list is usually hidden from the users.

 

The connection to Outlook is achieved by calling a custom protocol (stssync). The following string (called "strConnectTo" in the following code) is a generalized version, for which we again will have to substitute the placeholders:

 

stssync://sts/?ver=1.1&type=tasks&cmd=add-folder&base-url={serverUrl}&list-url={taskListUrlPlusSlash}&guid=%7b{taskListGuid}%7d&site-name={siteName}&list-name={listName}

 

The 5 placeholders are actually also easily replaced:

 

strConnectTo = strConnectTo.Replace("{serverUrl}", workflowProperties.WebUrl);

strConnectTo = strConnectTo.Replace("{taskListUrlPlusSlash}", workflowProperties.TaskListUrl + "/");

strConnectTo = strConnectTo.Replace("{taskListGuid}", workflowProperties.TaskListId.ToString());

strConnectTo = strConnectTo.Replace("{siteName}", workflowProperties.Web.Title);

strConnectTo = strConnectTo.Replace("{listName}", workflowProperties.TaskList.Title);

bodyStr = bodyStr.Replace("{ConnectOutlookUrl}", strConnectTo); 

 

And that's it!


Published: Aug-24-09 | 0 Comments | Link to this post

Server 2008 R2 & Windows 7 VM on MacBook Pro ...what a great combo!

I just upgraded my Windows Server 2008 installation on my 2 year old MacBook Pro. I dared and did an inplace upgrade to R2, as I didn't want to go through all the hassle of reinstalling the different drivers for the MacBook Pro again. I was happy to find that the upgrade process was straight-forward with no major obstacles: only the error message that comes up to warn about the Hyper-V service, which as stated should be switched off for the upgrade ...except John Howard clarifies that it shouldn't!
 
Almost all my drivers survived the upgrade, except for the Bluetooth capability (it looks like the Bluetooth stack Spence Harbar created for Server 2008 no longer works with R2).
 
Bluetooth
 
I also had configured several "Vista-like" features for my Server 2008  (a great list of suggestions can be found here, here, and here). As one would expect, some of those settings were deactivated/overwritten by the new install, but that is no real problem either.
 
What made me really happy was to see the improved performance and responsiveness of the whole setup, across the board. One key fact stands out here: the memory requirements of the new "Windows 7 Ultimate" virtual machine I then installed are about 50% lower than the Vista virtual machine I had been running so far: it uses no more than 450MB, as can be seen here:
 
Windows 7 RAM Requirements
 
This is great news, as I like to run only my core applications such as Office off the Host, use a dedicated Server VM for SharePoint development, and have a separate VM to trial new software, so to avoid to screw up my host.
 
As my MacBook Pro model doesn't allow me to use more than 4GB of RAM I was  previously limited to running only 1 VM at a time. No I can keep 2-3 VMs running with no recognizable performance problems.
 
I was close to buying a new machine, which I usually do every two years, but with the new setup I'll probably be able to postpone that for quite a while :-)
 
Update: A friend of mine passed the Bootcamp 3.0 drivers on to me ...and I was very happy to find that FINALLY it is possible to "tap/click" using the MacBook's trackpad.

Published: Aug-21-09 | 0 Comments | Link to this post

Sudoku Solver (Source code and Console app)

My older son (12 years) just came back from a sailing trip. It wasn't just an "ordinary" sailing trip ...they went with a Math teacher who invited kids that are advanced on their Math skills.
 
Guess what! He comes back and immediately throws a "hard to solve" Sudoku at me, telling me that he did it in 1h40 and his teacher did it in 1h20! ...he was even blunt enough to challenge me, whether I could do better than that.
 
Not that I needed another job or such a challenge, as I've really lost interest in burning time with these puzzles early in my life, after I solved the Rubik's cube ...only then to be thrown at all the puzzles my friends had gotten as "presents", to add peace to their life by giving them the solution.
 
So, what do you do when you know how to program? No, you don't program right away. You look if there's a solution already out there, that just does it (I hate reinventing wheels!).
 
However, all I found were incomplete, strange (to say the least) or flawed solutions. Amongst the "highlights" I found were people that are using brute force algorithms (guys, are you using a sledge hammer to open your front door?), LINQ queries to check things that a couple of simple "for" loops could do, and people that were using highly mathematical algorithms that you'd have to read a whole book on first before you knew for sure, that what you were about to do would actually have a chance for success. Oh, almost forgot another good one: someone using an algorithm that could end up in an endless loop, so a perpetual hash generator was introduced to identify such a situation ...no wonder software applications are so convoluted these days!
 
The Approach
Oh well, what did I do? Yep, I invented a new wheel. How did I approach it? Well, Sudokus are practically a permutation problem, which means that one typically looks out for the easy choices (e.g. squares that have only one spot for a specific value), and if you are out of those you resort to the dual options (and then the triple etc.). Also, at least for me, it is a no-brainer to use recursion to address such a problem (and also to minimize the code ...less is more :-). The whole code is less than 200 lines. As an intro, here's the method that implements the approach:
 
The Code
 
public Boolean Solve(int leftToFill)

{

   if (leftToFill == 0) //if there is no more spot to fill we're done

   {

      Console.WriteLine(this.ToString()); //prints the solution matrix

      return true; //indicates that we found the solution

   }

   List<RowColumnValue> valuesFound = new List<RowColumnValue>();

   List<RowColumnValue> possibleValuesFound;

   for (int value = 1; value <= 9; value++) //we're looping over values 1..9

   {

      for (int square = 1; square <= 9; square++) // ...and squares 1..9

      {

         if (!DoesSquareContainValue(value, square))

         {

            possibleValuesFound  = (

List<RowColumnValue>)FindMinSquare(value, square);

            if ((valuesFound.Count == 0)

                     || (valuesFound.Count > possibleValuesFound.Count))

               valuesFound = possibleValuesFound; //first or so far best option

         }

      }

   }

   if (valuesFound.Count == 0)

      return false; //we're stuck and need to advance on a higher level

   for (int i = 0; i < valuesFound.Count; i++)

   { //we're setting a single value in the array

      RowColumnValue rcv = valuesFound[i];

      this[rcv.Row, rcv.Column] = rcv.Value;

      leftToFill--;

      if (Solve(leftToFill)) //recursion call on Solve()

      { //we've found the solution

return true;

      }

      else

      { //we need to undo the entry in the array

this[rcv.Row, rcv.Column] = 0;

      }

      leftToFill++;

   }

   return false;

}

As you can see it is pretty straight-forward and beyond the "FindMinSquare" method, that returns the possible value with the least options in a square the solution only has a few little helper methods, e.g. that check whether a specific value already exists in a row, column or square.
 
The Console application
This is what the Console application looks like:
 
FC.SudokuSolver
 
The Downloads
You can download the whole Solution as a zip  file (only 16KB), or simply get the exe for the Console app (rar file, only 4KB).
 
Of course I also did a bit of testing and for that I was happy to find some good Sudokus at this Wikipedia page (along with some entertaining info on Sudoku algorithms). None of these very hard ones took more than a couple of seconds on my MacBook Pro - come Windows Server - come Virtual machine (with less than 1 processor assigned) ...so I guess, after all, there's still nothing better for achieving performance than "keeping it simple"!
 
Have fun!
 
p.s. My son was pretty impressed that I did outperform him and his teacher by factor 150,000 and 120,000 respectively ...his Sudoku only took 0.04 seconds to solve (of course, one day he'll come to me and I won't be able to keep up with his challenge ...he is ambitious and quite a sharp cookie! :-)

Published: Aug-13-09 | 0 Comments | Link to this post