Custom Workflow in WSS
Met Sharepoint heeft Microsoft een platform gecreëerd waarmee op een snelle en betrouwbare manier met elkaar samengewerkt kan worden. De functionaliteiten die hiervoor nodig zijn, zijn gratis beschikbaar indien men beschikt over een Windows Server 2003 licentie. Deze versie van Sharepoint is bekend geworden onder de naam Windows Sharepoint Services (WSS) en hiervan is onlangs versie 3 beschikbaar gekomen. WSS is opgezet als een uitbreidbaar platform, waardoor men allerlei extra functies kan toevoegen. Microsoft zelf is hier een grote speler in met de Microsoft Office Sharepoint Server producten.
Één van de key selling points van Windows Sharepoint Services is de ondersteuning van workflow met behulp van Windows Workflow Foundation (WWF). Standaard krijg je bij de installatie van WSS een aantal kant en klare workflows maar het is ook mogelijk om eigen workflows te maken.
Helaas op dit moment is het maken van eigen workflows nog summier gedocumenteerd, men moet vaak uit de context van blogposts de informatie halen. In dit artikel wordt daarom in stappen een custom workflow gebouwd, waarbij voor elke stap zal worden toegelicht waarom dit nodig is en wat er mee bereikt wordt.
Architectuur
Windows Sharepoint Services v3 maakt gebruik van de functionaliteiten van het .NET framework 3.0. Boven op het .NET framework is een Core Workspace Services (CWS) layer gebouwd die alle functionaliteiten van WSS bevat. Deze layer bevat geen gebruikersinterface. De gebruikersinterface is als ‘solution’ boven op de services layer gebouwd.
In figuur 1 is te zien dat solutions uit meerdere functie-blokken kunnen bestaan. De ‘licht blauwe blokjes’ die in figuur 1 te zien zijn, bevatten de extra functionaliteiten waar een MOSS licentie recht op geeft. Deze onderdelen maken allemaal weer gebruik van de CWS layer.
Voor de ‘licht blauwe blokjes’ in figuur 1 heb je MOSS nodig

Fig. 1: Sharepoint architectuur
Er zijn twee tools om nieuwe workflows toe voegen: via de Sharepoint Designer en Visual Studio 2005. Een Information Worker kan met behulp van een wizard in de Sharepoint Designer een workflow maken. Visual Studio wordt door developers gebruikt die geavanceerde herbruikbare workflows willen maken. In dit artikel maken we gebruik van Visual Studio 2005.
Het scenario
Om een duidelijk beeld te geven hoe je een workflow kunt maken voor WSS, zullen we het volgende scenario verder uitwerken.
In Sharepoint bestaat tegenwoordig de mogelijkheid om je via RSS te abonneren op de inhoud van een lijst. Hiernaast kun je ook nog steeds gebruik maken van alerting via e-mail. Toch kan het zijn dat je niet uitkomt met deze twee mogelijkheden. Als je een alert mailtje krijgt, dan ontvang je alleen maar een notificatie dat er iets is gewijzigd en niet de wijziging zelf. Voor de RSS feed moet de site bereikbaar zijn vanaf de cliënt en dat is bijvoorbeeld niet het geval als je een RSS feed van het intranet vanaf het internet wil uitlezen.
Dit kun je oplossen door een workflow te maken die op het moment dat er een nieuw bericht in een lijst wordt aangemaakt dit bericht e-mailt naar geïnteresseerde gebruikers. Je kunt hier bijvoorbeeld denken aan een lijst met nieuwsberichten waarbij het toevoegen van een nieuwsbericht erin resulteert dat iedere geïnteresseerde gebruiker dit bericht in zijn inbox krijgt.
De lijst van geïnteresseerde gebruikers kunnen we maken met behulp van standaard SharePoint functionaliteit: Maak een lijst in SharePoint waar je een kolom aan toevoegt zodat iedere gebruiker zichzelf met zijn e-mail adres in deze lijst kan opvoeren. De workflow moet dan deze lijst benaderen om te bepalen wie er een e-mail moet krijgen. Schematisch is dit in figuur 2 weergegeven.

Fig. 2: Workflow scenario
De workflow
Om met behulp van Visual Studio workflows voor SharePoint te kunnen maken moet je de MOSS 2007 ECM Starter kit downloaden van de MSDN site.
(Op het moment dat dit artikel geschreven werd, was de ECM Starter kit nog in Beta. Er werd wel gebruik gemaakt van de MOSS RTM versie.)
Nadat je deze geïnstalleerd hebt, kun je een nieuw sequential workflow project starten (figuur 3).

Figuur 3: Start nieuw Sharepoint Sequential Workflow project
De verplichte ‘onWorkflowActivated’ activity wordt gebruikt om de workflow te koppelen aan het event waarop de workflow kan starten.
Er wordt nu een project voor je gemaakt met daarin een Workflow1.cs file. Als je deze opent, zie je een basis workflow in de designer. Deze workflow bevat initieel alleen de ‘onWorkflowActivated’ activity. Dit is een verplichte activity die gebruikt wordt om de workflow te kunnen koppelen aan het event waarop de workflow kan starten.
In ons scenario moet er nadat de workflow is gestart de lijst met de e-mail adressen worden geopend en voor elk adres een mailtje gecomponeerd en verzonden worden. Hiervoor hebben we een while loop nodig om alle e-mail adressen apart te benaderen.
Het is een goede gewoonte om de verschillende stappen van de workflow te loggen in de history list. Dit loggen kunnen we doen met de standaard LogToHistoryListActivity activity in de Microsoft.SharePoint.WorkflowActions namespace.
De uiteindelijke workflow in de designer is afgebeeld in figuur 4.

Fig. 4: De MailAnnouncementsToRecipients workflow
De gegevens
Men kan geen aannames doen over de thread waar een workflow wordt afgehandeld. De connectie die een gebruiker maakt met Sharepoint is alleen geldig binnen zijn eigen thread en het is daarom niet mogelijk hier gebruik van te maken.
Daarom zullen we aan de workflow alle data mee moeten geven die nodig is om de lijst met e-mailadressen te benaderen. Hier betekent dit dat we de url van de huidige site, de naam van de lijst en de naam van de kolom met de e-mail adressen moeten opgeven. Er zal vanuit de workflow een nieuwe connectie naar Sharepoint moeten worden gemaakt om de e-mail adressen uit te lezen.
Workflow fases
Er zijn in Sharepoint minimaal vier verschillende fases waarin een workflow zich kan bevinden:
- Association fase:
Een beheerder zal de workflow gaan associëren met een bepaalde lijstinstantie. Hierbij betreedt de workflow de association fase. De beheerder kan hier data opgeven die voor alle toekomstige instanties van de workflow relevant zijn.
- Initiation fase:
Elke keer dat een workflow gestart wordt, betreedt de instantie van de workflow de initiation fase. In deze fase is de data die in de associatiefase door de beheerder is opgegeven beschikbaar en kan de gebruiker die de workflow start, eventueel nog data toevoegen of veranderen. Deze data is alleen geldig gedurende de looptijd van de workflowinstantie.
- Status fase:
Gedurende de looptijd van een instantie van de workflow kan deze verschillende statussen bereiken die gedefinieerd zijn in de workflow zelf, bijvoorbeeld: gestart, opzoeken adressen, versturen mail, enzovoort. De huidige status kun je opvragen door de status van de workflow uit te vragen.
- Task completion fase:
Elke workflow instantie is een keer klaar en komt dan in de task completion fase. Deze fase kan automatisch bereikt worden of door de gebruiker handmatig worden bepaald (afbreken van de workflow bijvoorbeeld)
Het invoerscherm
Omdat de data in ons scenario voor elke workflowinstantie hetzelfde zal zijn (de locatie van de lijst met e-mailadressen verandert niet), kunnen we deze in de association-fase opgeven als de workflow aan de lijst gekoppeld wordt. Er zijn op het internet heel wat voorbeelden te vinden over hoe je dit doet met behulp van een InfoPath formulier, maar in dit scenario gaan we voor een eigen ASPX pagina.
Voeg een project van het type class library toe aan de solution en maak daar een aspx pagina in aan. Hierdoor zijn we in staat om de aspx pagina te koppelen aan een code-behind page en deze te compileren in een assembly.
(Het is ook mogelijk om hiervoor een web project te gebruiken. Dit type project is als download gratis beschikbaar op de MSDN-site. Met behulp van dit project kan men nog steeds op de Visual Studio 2003 manier een webproject aanmaken.)
De html-code van de aspx pagina is afgebeeld in figuur 5.
/>
Fig. 5: Aspx code associatie pagina
In deze figuur kun je zien dat er een tabel getoond gaat worden die een TextBox bevat voor de naam van de lijst met e-mailadressen.
De gebruiker moet de naam van de lijst met e-mail adressen invullen. Na het invullen van de naam, drukt men op de knop Update in de derde rij van de tabel. De code achter het onclick event van de button vult een listbox met kolommen uit de e-mail adressenlijst. De gebruiker kiest in de listbox de kolom die de e-mail adressen bevat en drukt op de Ok-button.
In de code van de Ok-button wordt de associatie tussen de lijst en de instantie van de workflow gelegd.
Het object is een Sharepoint control dat er voor zorgt dat er vanuit deze pagina geschreven mag worden in de Sharepoint-context. Dit is noodzakelijk om de koppeling van de workflow aan de lijst te kunnen wegschrijven.
Er moeten nog een aantal hidden inputboxen worden toegevoegd aan de ASPX pagina om ervoor te zorgen dat alle relevante workflowdata mee wordt gestuurd.
(De benodigde hidden inputboxen worden gebruikt in de WFAssoc.aspx in de CollectFeedback sample uit de ECM starter kit.)
Code in het invoerscherm
Er moet code worden geschreven om de hier boven beschreven functionaliteit te bouwen.
De code in de btnUpdate_click methode gebruikt de huidige Sharepoint context, opent de lijst die is ingevuld in txtList en voegt alle kolommen toe aan de listbox (figuur 6).
protected void btnUpdate_Click(
object sender, EventArgs e)
{
if (txtList.Text != "")
{
SPList emailList = Web.Lists[txtList.Text];
if (emailList != null)
{
foreach (SPField field in emailList.Fields)
{
if (field.TypeAsString == "User")
lbPersons.Items.Add(
new ListItem(field.Title));
}
}
}
}
Fig. 6: Code btnUpdate_click
Het spannende stuk code begint in de btnOk_click methode (figuur 7): hoe maken we de associatie tussen de nieuwslijst en de workflow? Hiervoor maken we gebruik van de functie CreateListAssociation op de SPWorkflowAssociation klasse.
In figuur 7 is de code van de btnOk_click event handler weergegeven. Hierbij moet vermeld worden dat de m_baseTemplae, m_workflowName, m_tasklistName en m_historylistName objecten instance member variabelen zijn van het type String.
protected void btnOk_Click(object sender, EventArgs e)
{
//create a new task list
string description = string.Format(
"Task list for the {0} workflow.", m_workflowName);
Guid taskListId = Web.Lists.Add(m_taskListName,
description, SPListTemplateType.Tasks);
//create a new history list
description = string.Format(
"History list for the {0} workflow.",
m_workflowName);
Guid historyListId = Web.Lists.Add(
m_historyListName, description,
SPListTemplateType.WorkflowHistory);
SPList taskList = Web.Lists[taskListId];
SPList historyList = Web.Lists[historyListId];
// perform association
SPWorkflowAssociation assocTemplate =
SPWorkflowAssociation.CreateListAssociation(
m_baseTemplate, m_workflowName,
taskList, historyList);
// set up starup parameters in the template
assocTemplate.Name =
"Mail announcement items to recipients";
assocTemplate.AutoStartCreate = true;
assocTemplate.AutoStartChange = true;
// place data from form into the association template
assocTemplate.AssociationData = string.Format(
"{0}{1}"+
"{2}",
Request.Url.ToString(), txtList.Text,
lbPersons.SelectedItem.Text);
// add association
List.AddWorkflowAssociation(assocTemplate);
string strUrl = List.DefaultViewUrl;
Response.Redirect(strUrl);
}
Fig. 7: Code btnOk_Click
Allereerst maken we een lijst aan om eventuele tasks in op te slaan en een lijst om de history logs te bewaren. Deze lijsten gebruiken we daarna in de CreateListAssociation aanroep.
(Bij het aanmaken van een associatieobject is het noodzakelijk om een lijst op te geven voor de opslag van taken en voor het opslaan van de logmessages. Alhoewel we in ons voorbeeld geen specifieke taken in de tasklist op zullen slaan maken hem hier wel aan. De logmessages die door de log activities gegenereerd worden komen in de logging list terecht.)
Om een associatie te kunnen leggen tussen de workflow en de nieuwslijst hebben we een associatieobject nodig (assocTemplate). In dit object kunnen we aangeven wat de naam van de workflow is, wanneer de workflow moet starten en wat voor associatiedata we meegeven. De associatiedata kunnen we door een property (assocTemplate.AssociationData) van het type String aan het associatieobject mee te geven. In deze property kunnen we alle data zetten die tijdens de associatie van de workflow aan de nieuwslijst van toepassing is. In dit voorbeeld is gekozen om de data door middel van een xml-string door te geven.
Ten slotte moeten we het associatieobject registreren bij een lijstinstantie. Dit doen we met de methode AddWorkflowAssociation op het SPList object. Het object List is een instance member variabele dat een referentie bevat naar de nieuwslijst. We voegen aan de nieuwslijst de workflowassociatie toe.
Code in de workflow
We hebben nu een invoerscherm waar we de locatie van de lijst met e-mailadressen in kunnen voeren. De volgende stap is ervoor te zorgen dat we deze data in onze workflow kunnen uitlezen.
Wanneer je een Sharepoint SequentialWorkflow project start krijg je in de codefile van de workflow een member-variabele van het type SPWorkflowActivationProperties. In deze variabele bevindt zich ook de associatiedata.
Het is mogelijk om je eigen code toe te voegen aan de onWorkflowActivated activity. Hiervoor moet je een event handler implementeren voor het Invoked event. Dit event gaat af tijdens de initiation fase, wat er op neerkomt dat voor elke workflowinstantie die gestart wordt de lijst met e-mail adressen zal worden uitgelezen. (figuur 8)
private void onWorkflowActivated1_Invoked(
object sender, ExternalDataEventArgs e)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(workflowProperties.AssociationData);
//Get the values from the associationdata xml,
//we can reference the exact nodes here
// because we know the position of the
//values in the xml document.
string urlWeb =
xmlDoc.DocumentElement.ChildNodes[0].InnerText;
string listName =
xmlDoc.DocumentElement.ChildNodes[1].InnerText;
string columnName =
xmlDoc.DocumentElement.ChildNodes[2].InnerText;
SPWeb thisWeb = new SPSite(urlWeb).OpenWeb();
//Fill global variables
emailRecipients = thisWeb.Lists[listName];
numberRecipients = emailRecipients.ItemCount;
currentRecipient = 0;
}
Fig. 8: Haal de associatiedata weer op in de workflow code
Hierna hoeven we alleen nog de verschillende code-blokken achter de activities van figuur 4 te vullen.
De LogToHistoryList activities halen de logging data uit workflow-variabelen. In dit scenario heb ik twee properties aangemaakt in de workflow, één met de naam LogToHistoryListActivity_Description en een met de LogToHistoryListActivity_Outcome naam. Deze properties zijn daarna weer gekoppeld aan de LogToHistoryList activities.
In het method invoking event van de logging LogToHistoryList worden de juiste waarde weggeschreven in de twee properties en de activity voert zelf het wegschrijven van deze gegevens naar de historylist uit.
Voor de while loop wordt een een tellertje opgehoogd bij iedere iteratie. Indien dit tellertje gelijk is aan het aantal recipients, hebben we alle e-mails verstuurd en kunnen we ophouden. Dit codeblok is afgebeeld in figuur 9.
private void CheckRecipients(object sender,
ConditionalEventArgs e)
{
if (currentRecipient < numberRecipients)
{
sendEmail1_To = emailRecipients.Items
[currentRecipient][data.FieldName].ToString();
currentRecipient++;
e.Result = true;
}
else
e.Result = false;
}
Fig. 9: Code while loop
In figuur 9 wordt steeds het workflow member field ‘sendEmail_To’ gezet zodat de e-mail naar de juiste ontvanger gaat. In de sendEmail activity kunnen we de inhoud van het e-mail bericht opgeven. Vul hiervoor de properties Body en Subject van de activity met de waardes die je wilt e-mailen naar de gebruikers.
Koppelen van het aspx formulier met de workflow
De laatste stap is het deployen van de workflow met bijbehorende aspx-pagina naar Sharepoint. Dit wordt gedaan met behulp van het Feature Framework. Helaas is er in dit artikel te weinig ruimte om hier uitgebreid inhoudelijk in te gaan op dit Framework.
Een workflow is een type ‘feature’ dat we aan Sharepoint kunnen toevoegen met dezelfde technieken als ook webparts en lijst definities gedeployd wordt.
Als we in de solution explorer van Visual Studio 2005 kijken zien we dat er twee xml bestanden zijn aangemaakt bij het creëren van ons sequential workflow project, namelijk feature.xml en workflow.xml. Met behulp van het workflow.xml bestand beschrijf je hoe de workflow geregistreerd moet worden in Sharepoint. (figuur 10)
xmlns="http://schemas.microsoft.com/sharepoint/">
Name="SendAnnouncementsbyemail"
Description="Simple workflow that creates a review
task and waits for the user to complete it."
Id="{EE6F77A2-4610-400e-8374-260CA1ECA18C}"
CodeBesideClass="SendAnnouncementByEmail.Workflow1"
CodeBesideAssembly="SendAnnouncementByEmail,
Version=3.0.0.0, Culture=neutral,
PublicKeyToken=04b5f82a3375e64d"
AssociationUrl="/_layouts/wfforms/Association.aspx"
>
Fig. 10: Workflow.xml
De verschillende attributen van het Workflow element hebben ieder een functie:
- Name: naam van de workflow
- Description: omschrijving van de workflow
- Id: GUID die deze workflow uniek kan identificeren
- CodeBesideClass: klassenaam (inclusief namespaces) van de workflow
- CodeBesideAssembly: assembly die de workflow bevat
- AssociationUrl: locatie van het aspx formulier dat getoond moet worden in de association fase.
Er zijn nog meer attributen terug te vinden in de SDK, maar voor ons scenario zijn de bovenstaande voldoende.
In het bestand feature.xml moeten de attributen Id en Title gezet worden en daarna is het mogelijk om deze workflow te deployen.
Ten slotte
In dit artikel zijn verschillende ‘short-cuts’ toegepast om een en ander snel werkend te krijgen. Het is bijvoorbeeld mogelijk om je eigen aspx formulieren te integreren met de standaard layout van Sharepoint, workflows te koppelen aan content types en deze door middel van solution packages te deployen. In dit artikel is de minimale basis uitgelegd om een eigen workflow te maken met een associatiepagina.
Bronvermelding