Upgrade Your Windows SharePoint Services 3 installation to Search Server Express 2008!

Why? Because Microsoft Search Server Express (MSSX) is a great product, as it includes many of the search elements and the core search engine of the commercial MOSS 2007 product.
 
Upgrading from WSS3 to MSSX is actually not that difficult (really!). In fact, I usually install WSS as a farm first anyway and then upgrade the servers to MSSX as a second step.
 
If you haven't got anything installed yet, Windows Server 2008 R2 x64 is a great platform. Make sure you install SQL Server Express separately upfront, so you can choose the proper "advanced" installation for WSS3. Anything from SQL Server 2005 SP3 and up will do, but I also really like the brand-new SQL Server 2008 R2 Express.
 
The upgrade process is really nothing more than running the MSSX installer and the usual wizard afterwards, whereas you should install on the Central Admin server first and configure the MSSX-specific settings on that machine after the wizard has finished. The upgrade procedure is well described in this document.
 
Of course, there's a catch (it would be too easy otherwise, wouldn't it? ;-) After the upgrade is completed you might be missing some vital MSSX components, and if you're not familiar with MSSX you might not even know what you're missing.
 
Here's a couple of screenshots that point out the short-comings of the upgrade results:
 
Site Administration has no search settings links
 
What is wrong here is that the links to the search administration pages are missing.
 
Search Box with Scopes Dropdown missing
 
Well, not only are we missing the great "All Sites" scope for searching but even when adding a custom scope manually it simply won't show here.
 
One might be tempted to configure the "SmallSearchInputBox" but all attempts to do so are doomed. Both problems actually stem from the same problem and are simple to solve: the upgrade didn't automatically turn on the Web Application-level features for Search, as can be seen here:
 
Missing Web App Feature Activation
 
Once you've activated these features the SmallSearchInputBox will display properly (and can also be configured nicely under "\12\TEMPLATE\FEATURES\OSearchEnhancedFeature\SearchArea.xml" and voila, we now have a proper Search Box and even search configuration links in the Site Administration page are now showing:
 
Search configuration links partially available
 
Well, unfortunately there are still two links missing (for the "Search queries" page and the "Search results" page), so I've written a little feature to add those missing links as well (the pages for this functionality are actually installed, just the links are missing), which you can download here.
 
Enjoy the MSSX product. Once you've configured the search I am sure you will. And it doesn't get much better than free!

Published: May-10-10 | 0 Comments | Link to this post

SPFeatureReceiver.FeatureUninstalling ...or is it solution retraction?

I just realized there isn't that much documentation available on SharePoint's FeatureUninstalling method, so I thought I should share my experience with it.
 
FeatureUninstalling will only be called when the feature is uninstalled on the last web application. Therefore it is somewhat like a "Solution is being Retracted" event.
 
The properties.Feature property will be null, so you need to access your feature elements via the SPFarm object (SPContext doesn't work here either, as has been documented a lot on the web).
 
A solution retraction does NOT necessarily call a FeatureDeactivating for all deployed feature instances, so you might want to iterate through your WebApplications and Sites to clean up and deactivate any remaining features.
 

Published: Apr-29-10 | 0 Comments | Link to this post

ChangedProperties Property - Documentation Error

We're getting ready to ship the 2.0 version of our "Metadata Extractor" product that extracts metadata out of images and copies it into the list columns. One of the cool new features is that it also extracts the metadata when a new version of a file is uploaded (previously it only did that on the initial upload, which was hard enough, given the problems around the asynchronous ItemAdded eventhandler ...but that's another story ;-).
 
The best way to check what exactly has been changed on an item is to use the ItemUpdating and ItemUpdated events. However when we looked into storing information that was available in the ItemUpdating event that occurs first, so that we could access it in the ItemUpdated afterwards we were a bit confused about the documentation, especially the meaning and usage of the ChangedProperties property. MSDN states that it "gets properties that have changed". Well, not only is that as minimalistic as can be, but also simply wrong:
 
The ChangedProperties property can actually be used to store values from the ItemUpdating event to access them in the following ItemUpdated eventhandler.
 
Example:
If you add the following statement in your ItemUpdating eventhandler:
properties.AfterProperties.ChangedProperties.Add("FILEVERSION", item.File.Versions.Count.ToString());
...then you can access this value in the ItemUpdated eventhandler via:
string previousVersionNumber = properties.AfterProperties["FILEVERSION"].ToString();
Btw, if you step through your code you'll see that the key/value pair is actually being added to the ChangedProperties property. SharePoint then copies it over to the AfterProperties collection before the ItemUpdated eventhandler is invoked.
 

Published: Nov-15-09 | 0 Comments | Link to this post

Custom List Forms with Code-behind

There are a lot of articles on the web about manually modifying the list forms and the ListFormWebPart in particular, but there isn't that much information about modifying these forms programmatically, and the problems that come with that.
 
To start with, there's a great article on SharePoint List Forms by Hristo Pavlov that I highly recommend.
 
Customizing the ListFormWebPart is a chapter by itself, but what we needed was to customize the EditForm.aspx. Actually, we needed to inject a control that would prevent the form to come up while the asynchronous "ItemAdded" event, triggered by a file upload to the library, was still busy retrieving metadata (also an adventure in itself, as I consider that clearly a conceptual bug in SharePoint), which would result in an exception and a failure of the upload and/or the metadata extraction.
 
Now, there are actually two levels of forms one could utilize: each list/library has a set of these forms (New, Display, Edit) and the content types that are associated with the list/library also have these forms.
 
We chose to replace the EditForm.aspx of the document-type content types with a custom aspx form with code-behind. Why code-behind? Well, there are actually a couple of things one needs to adjust on the fly to make this work. One example would be that a custom application page should reside in the "_layouts/" space and the default.master masterpage that is used by the list forms is not directly accessible from there. But the most important reason was that the ListFormWebPart requires the "ListName" (it's actually the Guid ID of the list) and the "ListItemId" (which is what it sounds like :-) to be set properly when the OnInit event fires.
 
ListName&ListItemId
 
So, where's the problem? The ListFormWebPart is an incredibly flexible piece of programming art that can render itself on the fly automatically, depending on what list, content type and columns are to be displayed, BUT it does not retrieve the ListName and ListItemId from the Page.REQUEST automatically. Alright, we can do that with the code-behind, but that's NOT the problem ;-) The problem is that the control isn't available during the OnPreInit event to set these properties, and before the OnInit event handler gets fired SharePoint already throws an exception stating that no item could be found for the list and itemId specified ...Duhh!
 
Well, the solution is actually (as most things in life) trivial: the OnInit event fires on the controls before it fires for the page. Therefore it is important to add a code-behind event handler not only at the page level but also for the Web Part itself:
 
WebPart OnInit event handler registration
 
And the rather simple code-behind for this looks like this:
 
        protected void Lfwp_OnInit(object sender, EventArgs e)
        {
            string listId = Page.Request.QueryString["List"];
            string itemId = Page.Request.QueryString["ID"];

            ListFormWebPart lfwp = (ListFormWebPart)sender;
            lfwp.ListName = listId;
            lfwp.ListItemId = Convert.ToInt32(itemId);
        }

 
The beauty of this solution is that the same custom EditForm.aspx can then be used not only for all Picture Libraries, but also for Document Libraries.

Published: Oct-12-09 | 8 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

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