July 17, 2008 - 17:08, by Robin Meure
In this week I was busy with creating a webservice that automatically would create new sitecollections and subsites for a given webapplication and other parameters. Nothing fancy here right? Well.. building a webservice and hosting it in SharePoint (in my case in the LAYOUTS folder) wasn’t that easy, unless you figure out how to do it ;) The steps you have to go through, are the following :
- Add a reference to your custom assembly in the markup of the webservice page as follows
<%@ WebService Language="C#" CodeBehind="CustomWebService.asmx.cs" Class="zevenseas.SharePoint.WebServices.CustomWebService" %>
<%@ Assembly Name="zevenseas.SharePoint.WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=00000000000" %>
- Deploy the webservice to the LAYOUTS folder, download the tool WSSWebServicePackager and make sure you have the Disco tool nearby (I had to download the first VS2005 SDK to get it) and put the created WSDL and Disco .ASPX’s next to the webservice.
- Create a console application, add a web reference to your custom webservice and you can use the custom webservice!
- (optional : every time you modify the webservice you have to repeat step 2 to get it working)
Now, since we have the webservice setup properly we can build something beautiful! I created the following methods:
- CreateNewSitecollection
- CreateNewSite
- UpdateSite
- DeleteSite
During the creation of a site, I make use of a custom sitedefinition that contains some document libraries and a contact list. Now the requirement was to have a lookup column in each document library to the contacts list. Plus some additional columns were required as well. Doing this using a sitedefinition is quite hard so I decided to by it code and I was surprised how easy it was. Check the code below to see how it works :
private void UpdateDocumentLibrary(SPList documentLibrary, List<string> choices)
{
//Here we add the columns to the document library, the first one is a 'simple' choice column
documentLibrary.Fields.Add(fieldInternalNameSoort, SPFieldType.Choice, true);
SPFieldChoice soortField = (SPFieldChoice)documentLibrary.Fields[fieldInternalNameSoort];
foreach (string choice in choices)
{
soortField.Choices.Add(choice);
}
soortField.Update();
//This is the tricky bit, adding a lookup column to an existing list
SPList contactList = documentLibrary.ParentWeb.Lists["Contacts"];
documentLibrary.Fields.AddLookup("Contacts", contactList.ID, false);
SPFieldLookup contactLookUp = (SPFieldLookup)documentLibrary.Fields["Contacts"];
contactLookUp.LookupField = contactList.Fields["Last Name"].Title;
//We update the document library to make sure that the columns are added
documentLibrary.Update();
//Now to add the columns to the default view of the document library we do the following
SPView addSoortColumnTodefaultView = documentLibrary.DefaultView;
addSoortColumnTodefaultView.ViewFields.Add(fieldInternalNameSoort);
addSoortColumnTodefaultView.Update();
SPView addContactsColumnTodefaultView = documentLibrary.DefaultView;
addContactsColumnTodefaultView.ViewFields.Add(fieldInternalNameContact);
addContactsColumnTodefaultView.Update();
}
The thing that I was struggling with the longest was to add the newly created columns to the default view. Thanks to Bryan Friedman I managed to solve it, check out his post on how to do this. Thanks Bryan! ;)
Next thing I wanted to do was adding some custom webparts to the default.aspx page instead of using the sitedefinition. This also was fairly easy.. I did this thing back in the 2003 days but back then you had to add the whole xml definition of the webpart (correct me if I’m wrong btw). But now you can create a reference to a webpart and add this using the limited webpart manager as such :
private bool AddWebParts(SPWeb currentWeb)
{
bool webpartsInPlace = false;
try
{
SPLimitedWebPartManager wm = currentWeb.GetLimitedWebPartManager("default.aspx", System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);
//Creating a new instance of a custom navigation webpart
NavigationWebPart navWp = new NavigationWebPart();
navWp.Title = "Site Structure";
//Creating a new instance of a cusotm aggregation webpart
WhatsNewWebpart whatsnewWp = new WhatsNewWebpart();
whatsnewWp.Title = "What's new";
whatsnewWp.ListType = 101;
//Defining some properties of the webpart
if (currentWeb.WebTemplateId == 13000)
{
whatsnewWp.Scope = WhatsNewWebpart.ScopeEnum.Site;
whatsnewWp.ViewSiteColumn = true;
}
else
{
whatsnewWp.Scope = WhatsNewWebpart.ScopeEnum.Web;
}
whatsnewWp.Limit = 10;
whatsnewWp.ViewCreatedColumn = true;
whatsnewWp.ViewListColumn = true;
whatsnewWp.ViewModifiedColumn = true;
//Here we are actually adding the webparts to the site using the webpartzoneID's and the webpartOrder
currentWeb.AllowUnsafeUpdates = true;
wm.AddWebPart(navWp, "Left", 1);
wm.AddWebPart(whatsnewWp, "Left", 2);
currentWeb.Update();
currentWeb.AllowUnsafeUpdates = false;
webpartsInPlace = true;
}
catch (Exception e)
{
}
return webpartsInPlace;
}
Right.. so next on the requirements list was to have the MOSS Search active. Meaning that the searchscopes were not to be only “This site” or “This List” but also “All Sites” and having a redirect to the Searchcenter for the results. Now in the UI this is a simple setting (Site Actions –> Search Settings –> put in the url of SearchCenter –> Done) but doing this programmatically is a different story. In short, there is no method or function you can use that is in the SPWeb or SPSite object. Using reflector I found out that when clicking on the Ok button in the UI the following was happening :
this._rootWeb.AllProperties["SRCH_ENH_FTR_URL"] = this.urlTextBox.Text.Trim();
Not very pretty eh? But if it works.. it works! ;)
Next requirement please! Defining managed paths in code.. hmm.. well this one is pretty funny actually.. the first hit in Google was a blogpost by Hristo Pavlov that was published today describing exactly what I needed :) It seems that on the WebApplication class there is a “Prefixes” collection that holds all the managed paths. To add a managed path you have to do the following :
public bool ModifyWebApplication(SPWebApplication webApplication)
{
bool isModified = false;
try
{
webApplication.Prefixes.Add("blogs", SPPrefixType.WildcardInclusion);
webApplication.Update(true);
isModified = true;
}
catch (Exception error)
{ }
return isModified;
}
So there you go.. creating and configuring sites by only using code is pretty cool and not that hard to do!
July 16, 2008 - 22:54, by Robin Meure
I guess the picture is self explanatory.. it’s just like when SP1 came out. If the Product team announces something the whole community blogs about is as well but there is really no point in blogging about it again now is there? I know, I did in the past as well but I can’t imagine people not being subscribed to their blog these days right? ;)
Sorry if I upset someone by this post but I just had to write this of my chest!
July 14, 2008 - 13:40, by Robin Meure
As my mate Daniel posted, we are looking for people who are willing to make a change in their professional career :
…zevenseas is a new, small company, dedicated to SharePoint, and dedicated to making consulting the high value profession it once was.
In the last 6 months we have been crafting out a boutique organisation that allows us to do the things we think are important. Things like attending key international conferences and supporting the community via blogs and cool tools. We are an organisation that always works as a team, meeting each and every Friday through our Fridays@Sea concept, providing all of us with an opportunity to share experience, work collaboratively on problems, and most importantly, ensure we never feel we are out there on our own.
As SharePoint specialists we are able to focus on the product that excites us most, digging deep into the technology to ensure the best possible outcome for our customers. We bring a pragmatic, experienced and business focused approach to solving the problems our customers share with us, making the most of the strong platform SharePoint has become…
Now let me tell you how my experience has been since I decided to be a part of this lovely company. I’ve joined zevenseas at the beginning and I’ve seen it grow within 6 months as a great company with a very promising future. The chemistry between each of us is so great it’s barely describable but we seek more people who carry the same chemistry. So if you are looking for a kick-ass place to work, want to be involved in all the business decisions we make, don’t mind to be with a really good bunch of people every Friday and of course (if you aren’t already) be a very good SharePoint developer/consultant/architect then please mail me at robin@zevenseas.com and I’ll buy you a beer ;)
July 10, 2008 - 09:55, by Robin Meure
Well my mate Daniel is probably still wet behind his ears but he managed to built another lovely webpart. This webpart is called the “Choice Column Filter WebPart”. The question is of course.. what does it do?
1. First I added a new column called “CompanyName” to the standard “Contacts” list in SharePoint as follows:

2. Then I added in some sample data, and added a List View Web part for the list onto a new Web Part Page like so:

3. Everything is pretty simple so far, but his is where the Choice Filter web part comes in. The Choice Filter web part is attached to the “CompanyName” column, pulling out its values, creating a list of option boxes, and allowing you to sort the list at a single click. Here we add the Web Part, and the open the tool pane:

4. At the top of the tool pane we can select the list we want to attach to, and then any Choice fields that exist within the list, here we have changed it to point to our Contacts list:

5. When you have finished, click on “OK” to save the changes. The Choice Filter web part will now render with each choice appearing as an Option button:

6. All that remains is to connect up our new Choice Filter web part to the existing “Contacts” list view web part. First, make sure the page is in “Edit Mode”, then click on the “Choice Filter” edit drop down and select the following meni tree: “Connections” –> “Send Choice Filter To” –> “Contacts”, you will then have the following pop-up appear:

7. Match the “Choice Value” to the field you connected to earlier, in this case “CompanyName” and you are ready to go.


Pretty impressive eh? You can download it from our CodePlex community solution site, and please let us know what you think of it!
July 7, 2008 - 13:19, by Robin Meure
At my last project I was asked to develop a workflow that was quite complex. Now I have my fair experience of building workflow's using SharePoint Designer and custom workflow activities but I didn’t have any experience in building workflow's in VS. First thing to decide was whether to use a sequential or a state machine workflow. I decided to go for a state machine workflow so let me explain why I opted for that type of workflow and let me begin by briefly describe what the workflow is supposed to do :
- Automatically create sites
- Handle the user registration process
- If it’s the first user then do ‘code A’
- If it’s the second user .. eleventh user then do ‘code B’, etc
- If the first user didn’t respond within 2 months than do ‘code C’
- Automatically modify sites if there are an X number of users who have registered, after a X period of time modify the site again
So to me, this looked like a state machine. The first state is ‘creating the site’ , second state is ‘user registers’ and the final state is ‘modifying the site’. As always I googled for some (simple) tutorials who lead me the way and I started off with a console state machine workflow and within minutes I had a nice little workflow running :)
I decided to put this little workflow into our SharePoint environment which meant that the console workflow was running in an ASP.NET environment. This is where I got my first struggle with the Workflow Foundation. I wasn’t aware of the fact that the workflow was having it’s own scheduler and was running asynchronously. In order to run the workflow synchronously with the ASP.NET process you have to define the ManualWorkflowScheduler. The following articles pointed me in the right direction :
So after tackling that, the next ‘problem’ was facing me. I was very keen on using the persistency and tracking functionality since that gave the end-user/administrator the ability to quickly see in which state the workflow was running and what has happened in the past. Unfortunately this meant that the workflow could not be modified while running. Since the workflow could run for over a couple of months and having the requirement that a couple of variables could be changed (like the period of waiting or how many users are required) this was not an option. This was also not an option because when the workflow would wait for a couple of days it meant that an user could not register because the instance was waiting. So I had to drop that functionality and I had to modify the workflow so every time ‘something’ happened in the system which would fire the workflow, the workflow would create a new instance. Here are some articles which describe a bit more what I’m talking about :
So after a whole lot of iterations the workflow actually got more and more sequential.. And instead of a ‘site’ lifecycle it became an ‘user per site’ lifecycle. My lessons learned are the following :
- There is a huge difference between console applications and ASP.NET application that utilitize the workflow foundation.
- Spend more time in defining the workflow and make good choices between state machine and sequential. An process workflow that is defined in the functional phase could be very different in the technical phase
- Make a choice in having one instance or several instances of a workflow in terms of ‘waiting’ workflow’s. Next to that, running instances can not be versioned. So if you have long running workflows (eg. the lifecycle of a SharePoint site) and you want to make some changes to the workflow you simply cannot.
July 6, 2008 - 21:39, by Robin Meure
This week I was at a customer who was having some ‘pain’ in retrieving the status of a an approval workflow per submitted form. Their wish was to have a simple overview of all running workflow's that were waiting for a period longer than 14 days. With that overview they could see which approver was causing the delay and thus measurers could be taken.
My first hunch was to use a calculated column to determine when the form was last modified (since an approval caused a modification on the form) and combining that with the current date. But given the fact you cannot use the [Today] function in a calculated column this approach was not the answer. Next hunch was to use a dataview webpart, by using conditional formatting I could hide the forms that didn’t match the expression and showed the forms that did. Unfortunately I don’t have much experience using this webpart and creating an useful expression took too much time. Plus the fact that the customer wanted to have a little more information about the workflow itself I decided to do this by code ;)
Now every time something happens in the workflow, this event is registered in the workflow history list (which is a hidden list). Every list-item in that list has a reference to the formitem that it belongs to. So basically what I need to do is the following :
- Get the workflow instances per form-item
- Per instance, get the workflow history list
- Per form-item, get the last history item and get the workflow message/description
So in code it looks like this :
private List<WaitingWorkflowItem> GetWorklowHistoryForList(SPList formList)
{
List<WaitingWorkflowItem> waitingWorklowItems = new List<WaitingWorkflowItem>();
//Getting metadata from the form
SPWeb web = SPContext.GetContext(this.Context).Web;
SPQuery getRunningWorkflows = new SPQuery();
getRunningWorkflows.Query = "<Where><Eq><FieldRef Name=\"_ModerationStatus\" /><Value Type=\"ModStat\">Pending</Value></Eq></Where>";
SPListItemCollection coll = formList.GetItems(getRunningWorkflows);
foreach (SPListItem formItem in coll)
{
DateTime modified = (DateTime)formItem["Modified"];
//We are only interested in the forms that are waiting for more than 14 days
if (modified.AddDays(numberOfDays) == DateTime.Today)
{
//Storing the data about the form in our own class
WaitingWorkflowItem waitingWorkflowItem = new WaitingWorkflowItem();
waitingWorkflowItem.Title = formItem.Name;
waitingWorkflowItem.LinkToForm = formItem.Url;
//If a form is waiting for more then 14 days, we want to know how many days it is waiting
TimeSpan ts = new TimeSpan(DateTime.Today.Ticks - modified.AddDays(numberOfDays).Ticks);
waitingWorkflowItem.NumberOfDays = ts.Days.ToString();
//Here we are retrieving all running workflows on the item
Collection<Guid> instanceCollection = formItem.Workflows.GetInstanceIds();
foreach (Guid instance in instanceCollection)
{
//It could be that every workflow has it's own history list, so therefore we are retrieving
//the status using the workklow instance
SPWorkflow workflow = (SPWorkflow)formItem.Workflows[instance];
SPList historyList = workflow.HistoryList;
//Getting the workflowhistory for the current form item
SPQuery getSpecificWorkflowHistory = new SPQuery();
getSpecificWorkflowHistory.Query =
"<Where><Eq><FieldRef Name='Item' />" +
"<Value Type='Integer'>" + formItem.ID +
"</Value></Eq></Where>";
SPListItemCollection workflowItems = historyList.GetItems(getSpecificWorkflowHistory);
SPListItem workflowItem = workflowItems[workflowItems.Count - 1];
//Storing the last description of the workflow
waitingWorkflowItem.Status += workflowItem["Description"].ToString();
}
waitingWorklowItems.Add(waitingWorkflowItem);
}
}
return waitingWorklowItems;
}
With our own WaitingWorklflow class that looks like this :
public class WaitingWorkflowItem
{
private string title = string.Empty;
private string linkToForm = string.Empty;
private string numberOfDays = string.Empty;
private string status = string.Empty;
public string Title
{
get
{
return title;
}
set
{
title = value;
}
}
public string LinkToForm
{
get
{
return linkToForm;
}
set
{
linkToForm = value;
}
}
public string NumberOfDays
{
get
{
return numberOfDays;
}
set
{
numberOfDays = value;
}
}
public string Status
{
get
{
return status;
}
set
{
status = value;
}
}
}
So all we have to do is bind the Collection what we retrieve from the function to a DataGrid (or any other gridview-like control) and put it all in a simple WebPart and it will look like this :
Can you see the possibilities? :) Maybe some conditional formatting based on the amount of “NumberOfDays” that the workflow is waiting. Or putting in a email link so the person who must approve get’s another email, reminding the person that the approval is urgent. Another benefit of this type of reports is that you can see if workflow's are properly running or not. In this customer’s case some workflow's didn’t update the approval status of the form although the workflow was complete.
July 6, 2008 - 17:27, by Robin Meure
After not reading my RSS feed for a couple of days I was stumbled with all the good posts out there.. Here is a summary of the posts that immediately caught my attention :
- Best Practices for developing accessible web sites in Microsoft Office SharePoint Server 2007 by Waldek Mastykarz. Very interesting whitepaper, I learned a lot while reading this document.. So highly recommended!
- SharePoint 101- Managed Paths by Zach Rosenfield. An excellent explanation on Managed Paths.
- Designing browser-enabled forms for performance in InfoPath Forms Services (Part 1) , Part 2, Part 3, Part 4, Part 5, Part 6. If you are starting to build forms using InfoPath 2007 to be browser-enabled (or like me, you have some experience with it), please read these blogposts by the InfoPath Team.
- How to fix: Multipage Meeting Workspace will not display items copied / created by SharePoint Designer workflows, post from the SharePoint Designer Support blog (makes you wonder though.. there is an actual support blog for SharePoint Designer (even SharePoint itself does not have it’s own support blog) to fix the problem when you are utilizing the copy list item when copying a document from one library to another.
- How to fix: Custom List Forms will not insert or show up on the Design surface of SharePoint Designer, another post by the SPD Support blog when you come across this behavior.
- Adding Copy and Paste to SharePoint with the SmartTools, Jan ‘the man’ Tielens has it done it again! Adding the functionality what is used most in every environment and that is copy & pasting! Check it out here CopyPaste Wiki page on CodePlex :)