Geeks With Blogs
SOA & Integration Services - BizTalk, WCF, WF, AppFabric etc Why is it drug addicts and computer afficionados are both called users?

I recently took a look at using Windows Workflow Foundation to create a simple Human Workflow to manage a procurement process on SharePoint Portal Server. So I set about integrating SharePoint Portal Server (SPS), InfoPath and Windows Workflow Foundation to achieve this goal was it easy … well yes.

Here are the blog entries I can remember which really helped me out.

Which Style of Workflow When

Windows Workflow + SharePoint 2003 + BizTalk Scenario Built Out (Unfortunately SharePoint Portal Server is not .net 2.0 compatible whereas SharePoint Services are)

Adventures with Windows Workflow – Hosting a Workflow with Tracing and Persistence

Windows Workflow Foundation ASP.Net State Machine

Basic user experience:

The InfoPath form was vaguely similar to the Purchase Request form which comes with InfoPath as a template.

  1. Go to a SharePoint form library and enter a procurement request using an InfoPath form and submit it
  2. An approver would then receive an email with a link to the InfoPath form asking them to click the link and then approve or reject the procurement they open the form, approve, reject and then submit it
  3. Reject send email with form link back to requester
  4. Approve send email to a higher approver or a PO issuer with form links they open the form, enter the PO number and then submit it

Note:

Only an approver can approve, only a PO issuer can issue PO numbers

I didn’t need to write a single line of code to create the InfoPath form it was super easy.

How it’s done

SharePoint Portal Server Event Sinks

As SPS 2003 is not .net 2.0 compatible I hosted my Win WF State Machine in an ASP.net 2.0 Web Service (I am going to go into greater detail of how to do this below).

I registered an ASP.net 1.0 event handler class (see following code) in the advanced settings of my Procurement Form Library on SPS. Each time an InfoPath from is saved or submitted this event handler is called. This just calls the ASP.net 2.0 Web Service passing the SPS site URL and the InfoPath file url so the State Machine can access, read and update properties on the InfoPath form.

Note you could just as easily not use the SPS event sink and call the web service from an InfoPath form, using the event sink just gave me an easy way of passing the SPS site URL and the InfoPath file url (from the SPListEvent arguement) to my Win WF hosting Web Service so I could gain control of the InfoPath form inside the state machine and modify it’s xml etc.

using System;

using System.Runtime.Serialization;

using System.Runtime.Serialization.Formatters.Binary;

using System.IO;

using Microsoft.SharePoint;

using System.Security.Principal;

 

 

namespace Synergy.Procurement.Net1

{

       public class SPEventHandler : Microsoft.SharePoint.IListEventSink

       {

              void IListEventSink.OnEvent(Microsoft.SharePoint.SPListEvent spEvent)

              {

                     WindowsImpersonationContext wic = WindowsIdentity.GetCurrent().Impersonate();

                     try

                     {

                           //Pass the enough information to instanciate the current SharePoint Task

                           //and the Microsoft.SharePoint.SPListEventType through a web service to Win Workflow

                           //this way all the workflow process logic is handeled by Win Workflow

                           //NOTE: need to call through a Web Service as only SPS runs on .Net 1.1 and

                           //Win Workflow on .Net 2.0

 

                           //Get the following information so we can reinstanciate the list item in the workflow

                           //site url

                           //list file url

                           SPWeb spActiveSite = spEvent.Site.OpenWeb();

                           string spSiteUrl = spActiveSite.Url;

                           string spFileUrl = spEvent.UrlAfter;

 

                           WSToWinWF.Service wsWWF = new Synergy.Procurement.Net1.WSToWinWF.Service();

                           wsWWF.Credentials = System.Net.CredentialCache.DefaultCredentials;

                          

                            Boolean wsTestResult = wsWWF.Invoke(spSiteUrl, spFileUrl, System.Convert.ToByte(spEvent.Type));

                     }

                     catch (System.Exception e)

                     {     

                           System.Diagnostics.EventLog.WriteEntry(this.ToString(),e.ToString());

                     }

                     finally

                     {

                           wic.Undo();

                     }

              }

       }

}

Hosting a State Machine in a Web Service

Once the .Net 2.0 Web Service is called its job is to:

  1. Open the infopath form on the SPS Forms Library and deserialize the InfoPath xml into a serializable class (generated by the xsd utility in .net 2.0)

        //Get the infopath file

        SPWeb activeSite = new SPSite(spSiteUrl).OpenWeb();

        SPFile spFile = activeSite.GetFile(spFileUrl);

 

        //Get the infopath xml from the byte array of the file and read it into a memory //stream

            

        Byte[] byteBuffer = spFile.OpenBinary();

        MemoryStream xmlStream = new MemoryStream(byteBuffer);

 

        //Now deserialise the infopath xml into a class

        XmlSerializer serializer = new XmlSerializer(typeof(procurementRequest));

 

        procurementRequest _pRequest;

 

        _pRequest = (procurementRequest)serializer.Deserialize(xmlStream);

  1. Start and stop the workflow runtime and add the ExternalDataExchangeService the in the Global.asax

void Application_Start(object sender, EventArgs e)

{

// NOTE:  This requires the configuration section to be named "WorkflowRuntime".

        System.Workflow.Runtime.WorkflowRuntime workflowRuntime = new System.Workflow.Runtime.WorkflowRuntime("WorkflowRuntime");

        Application["WorkflowRuntime"] = workflowRuntime;

       

        //Add the procurement service to the runtime

        System.Workflow.Activities.ExternalDataExchangeService dataService = workflowRuntime.GetService();

        Synergy.Procurement.Net2.LocalServices.ProcurementService procurementService = new Synergy.Procurement.Net2.LocalServices.ProcurementService();

        dataService.AddService(procurementService);

       

        //Start the runtime

        workflowRuntime.StartRuntime();

    }

 

    void Application_End(object sender, EventArgs e)

    {

        System.Workflow.Runtime.WorkflowRuntime workflowRuntime = Application["WorkflowRuntime"] as System.Workflow.Runtime.WorkflowRuntime;

        workflowRuntime.StopRuntime();

    }

  1. Create a new instance of the state machine or rehydrate an existing instance from SQL Server (I save the WorkFlow instance ID in the InfoPath Document) from the SqlWorkflowPersistenceService

    private Guid StartWorkflowInstance()

    {

        WorkflowRuntime workflowRuntime = (WorkflowRuntime)Application["WorkflowRuntime"];

 

        //Now get a reference to the ManualWorkflowSchedulerService

        _schedulerService = workflowRuntime.GetService();      

 

        //Get the current procurement service if haven't already

        if (_procurementService == null)

        {

            ExternalDataExchangeService dataService = workflowRuntime.GetService();

            _procurementService = (ProcurementService)dataService.GetService(typeof(ProcurementService));

        }

 

        //Get the instanceid from the infopath document

        System.Guid WorkflowInstanceId;

       

        //Start the instance if not started

        if (_spListEventType == SPListEventType.Insert)

        {

            WorkflowInstanceId = Guid.NewGuid();

            _workflowInstance = workflowRuntime.CreateWorkflow(typeof(MainStateMachine), null, WorkflowInstanceId);

            _workflowInstance.Start();

        }

 

        //Load an existing instance

        else

        {

            //Get the instanceid from the infopath document

            WorkflowInstanceId = new Guid(_pRequest.referenceNumber);

            _workflowInstance = workflowRuntime.GetWorkflow(WorkflowInstanceId);

            _workflowInstance.Load();

        }

 

        // Now run the workflow.  This is necessary when

        // ...using the ManualWorkflowSchedulerService

        _schedulerService.RunWorkflow(WorkflowInstanceId);

 

        return WorkflowInstanceId;

    }

  1. Decide upon which implemented ExternalDataExchange event to raise. This is based on the SPListEventType and the properties in the serializable class representing the InfoPath document (Windows Workflow Foundation ASP.Net State Machine). Create an ExternalDataExchange interface and class with which to call through to event handlers in the StateActivities on your state machine. I pass through the following event arguments:

o        WorkFlow instance ID (GUID)

o        SPS site URL

o        InfoPath file url

o        The serializable class representing the InfoPath document

Note:

o        You will need to impersonate the current windows identity before accessing the SharePoint site (call the OpenWeb method etc) and the users must have access to SharePoint

WindowsImpersonationContext wic = WindowsIdentity.GetCurrent().Impersonate();

o        It is important in your Service class to set the following ExternalDataEventArgs property before raising the event

e.WaitForIdle = true;

o        The Win WF Beta2 ManualWorkflowSchedulerService has a bug which may be fixed by calling it’s RunWorkflow method after each time you raise an ExternalDataExchange event in the web service.

The State Machine

It looks like this (simple aye):

The purpose of the state machine is to send emails to various managers requesting approval for procurement, notify procurement requesters of approval or rejection and email office admin staff to assign a PO number. It also updates values on the InfoPath form hosted on the SharePoint site for example the WorkFlow instance ID I talked about earlier. It decides what emails/updates to make based on what events are raised in which particular state. A procurement may need to be approved by several managers up the management chain thus many approval events may be raised (by different managers) once in the ProcurementApproved State Activity.

Below is the code to save back the InfoPath document to the SharePoint site once various updates have been made.

        private void SaveInfoPathDoc()

        {

            //Create an XmlTextWriter to add processing instructions and write the contents out to a memory stream

            MemoryStream memoryStream = new MemoryStream();

            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, System.Text.Encoding.UTF8);

 

            //FIX: Add the processing instructions to the top of the xml document this is

            //because they are lost in the serialisation\deserialisation process

            xmlTextWriter.WriteProcessingInstruction("mso-infoPathSolution",

                ConfigurationManager.AppSettings.Get("InfoPathPI_mso-infoPathSolution"));

            xmlTextWriter.WriteProcessingInstruction("mso-application",

                ConfigurationManager.AppSettings.Get("InfoPathPI_mso-application"));

            xmlTextWriter.WriteProcessingInstruction("mso-infoPath-file-attachment-present",

                ConfigurationManager.AppSettings.Get("InfoPathPI_mso-infoPath-file-attachment-present"));

 

            //Serialise the xml flush the writer and get the byte array _supportEmail the stream

            XmlSerializer serializer = new XmlSerializer(typeof(procurementRequest));

            serializer.Serialize(xmlTextWriter, _pRequest);

            xmlTextWriter.Flush();

            Byte[] byteBuffer = memoryStream.ToArray();

 

            //FIX: Sometimes sharepoint locks the infopath document even though it has been submitted

            //in the config file are two appSettings values: RetrySaveInterval & TimesToRetrySave

            //which are the retry parameters in case this occurs

 

            int timesToRetrySave = Convert.ToInt32(ConfigurationManager.AppSettings.Get("TimesToRetrySave"));

            //Convert seconds to milli seconds

            int retrySaveInterval = Convert.ToInt32(ConfigurationManager.AppSettings.Get("RetrySaveInterval")) * 1000;

 

            for (int i = 0; i < timesToRetrySave; i++)

            {

                try

                {

                    //Get hold of the active site and infopath file

                    SPWeb activeSite = new SPSite(_pse.SharePointSiteUrl).OpenWeb();

                    SPFile spFile = activeSite.GetFile(_pse.SharePointFileUrl);

 

                    //Overwrite the infopath xml back

                    spFile.SaveBinary(byteBuffer);

 

                    break;

                }

                catch (Microsoft.SharePoint.SPException ex)

                {

                    //If we've run out of retries then write an error to the event log

                    if (i >= timesToRetrySave - 1)

                    {

                        System.Diagnostics.EventLog.WriteEntry(this.ToString(), ex.ToString(), System.Diagnostics.EventLogEntryType.Error);

                    }

                    else

                    {

                        //Wait for the retry interval

                        System.Threading.Thread.Sleep(retrySaveInterval);

                    }

                }

                catch (Exception ex)

                {

                    System.Diagnostics.EventLog.WriteEntry(this.ToString(), ex.ToString(), System.Diagnostics.EventLogEntryType.Error);

                    break;

                }

            }

        }

Extras

  1. Used the External RuleSet Demo to make decisions based on attributes in InfoPath doc instance like BusinessUnit, TotalCost on which manager should receive an email to approve\reject a procurement (I was surprised how easy it was to fit this in).
  2. InfoPath form access security: I made active directory lookups on email addresses got user logons and updated them in the InfoPath doc which allowed me in InfoPath to ensure only authorised managers/systems administrator’s had access to update approval information in controls on the InfoPath form.
  3. For debugging I found this invaluable Windows WorkFlow Tracing
Posted on Wednesday, July 5, 2006 12:22 AM Windows WorkFlow Foundation | Back to top


Comments on this post: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Dude this rocks!!!
Left by S Fish on Jul 05, 2006 12:54 AM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Hello,

I read your article on using a Web Service w/ Sharepoint 2003 to interface with Windows Workflow. The solution is exactly what I was looking for (my original thoughts were to use SOAP). In any case, you've got some great information in your blog entry but I was hoping you would be able to post or provide me with the project files? I tend to have a hard time unless I can look at how everything comes together in VS.

Thanks,
Brad Aswegan
Software Developer
Siemens Airfield Solutions
Left by Brad Aswegan on Jul 12, 2006 6:22 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Hi Brad,
Unfortunately I can't give out the complete code base as it's my employers IP I am happy to answer questions you may have.

However I was searching after I wrote this application and I found someone who had had the same idea (use a web service to get SPS 2003 to call .net 3.0) and he does provide the code as it's a demo. I haven't looked at it but it would probably help you http://www.kasev.net/blog/2006_06_01_kasev_archive.html.
Have a great day,
Rob
Left by rob Addis on Jul 12, 2006 6:31 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
I noticed a small problem with your code that you might want to fix.

The SPWeb calls unmanged code that requires you to use the IDisposable interface. Hence, you need to either call spActivateSite.Dispose() or using( SPWeb spActiveSite = spEvent.Site.OpenWeb() ).

If you don't, the object will not release resources because the unmanaged code will not collected by the .NET garbage collector. Over time you will run out of memory. I found out the hard way be having my server run out of memory every couple of days.

Here is a little known discussion about the issue from Microsoft:
http://msdn2.microsoft.com/en-us/library/ms778813.aspx#sharepointobjmodel_spwebobjects
Left by Peter Chapman on Jul 30, 2006 7:19 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Hey Peter,

Thank you for that information, I will add your comment to the Blog and fix the code. I am still waiting for some system testers to become avaliable before I deploy into prod, even so I can't be sure we would have picked up that problem! The other thing I need to research is whether it is necessary when you save the InfoPath document to check it out save and then and then back in the other sample I have provided a link to above does this.

Cheers

Rob
Left by Rob A on Jul 30, 2006 7:27 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Eric,
I'm keen on trying to help, what's your email address. I'm working to deadlines until I go on holiday this Wednesday, my girldriend is enforcing a computer ban over the holiday period (until next week)

Have you checked out this blog he has the whole source for similar functionality. I couldn't give the whole source for my code base as the work was for Synergy not myself. http://www.kasev.net/blog/2006_06_01_kasev_archive.html.

Good luck,
I think what you're trying to achieve is possible but with all new technology the first time you attempt something it may takes a bit longer than first expected.

Cheers
Rob
Left by Rob A on Oct 29, 2006 7:46 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Hello,

I am writing a SharePoint application that works with InfoPath forms. When

a user submits a completed InfoPath form to a forms library my code

processes the posted xml file by reading the data into a C# class object

that was generated by XSD.EXE. The class was generated from the

myschema.xsd file that is contained in the InfoPath solution with a command

line like:

xsd.exe myschema.xsd /c

With the generated class I can read (Deserialize) InfoPath's xml file into a

strongly typed C# object. Manipulate the data using the C# object, and then

write (Serialize) the C# object back to an xml file with code like this:

// InfoPathData class generated by XSD.EXE

private InfoPathData formData;

// Fill InfoPathData object with data from the InfoPath form

formData = (InfoPathData)DeserializeFile(myFile, typeof(InfoPathData));

// Modify the data

formData.Field1 = "New Data";

//update the InfoPath form

myFile.SaveBinary(SerializeObject(projInfo, typeof(InfoPathData)));

The problem that I am having is that the serialized xml file does not have

the preamble that identifies the xml file as an InfoPath document as well as

it is missing a namespace declaration.

Are there Serialization attributes that I can add to the generated C# class

that would cause the XmlSerializer to output the correct InfoPath specific

xml?

Are there changes to the XSD.EXE command like that would cause the generated

C# class to have the correct Serialization attributes?

Is there a better way to Deserialize and Serialize InfoPath documents?

Thanks
Left by Karmele on Jan 04, 2007 2:11 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Hello from Spain,
I have a problem when I want to save back the Infopath document to the Sharepoint Form library.
After deserialize the InfoPath xml into a serializable class, I achieve to change some fields and see this changes in the Form library fields when I insert a new form in the library(I make the changes on the Insert event),but something is wrong. Now there is a xml file in the form library with the changes but I can´t open it with infopath, it is only a xml file,I think that the properties of the infopath documents have been lost.
In the article whe can read the following text:

/FIX: Add the processing instructions to the top of the xml document this is because they are lost in the serialisation\deserialisation process

you take some properties from the AppSettings:

xmlTextWriter.WriteProcessingInstruction("mso-infoPathSolution",

ConfigurationManager.AppSettings.Get("InfoPathPI_mso-infoPathSolution"));

xmlTextWriter.WriteProcessingInstruction("mso-application",

ConfigurationManager.AppSettings.Get("InfoPathPI_mso-application"));

xmlTextWriter.WriteProcessingInstruction("mso-infoPath-file-attachment-present",

ConfigurationManager.AppSettings.Get("InfoPathPI_mso-infoPath-file-attachment-present"));

Here is my problem, I dont how to buid the
AppSettings.
Can you show or send the web.config file where the Appsettings are defined?

Thanks!
Left by Gaizka on Jan 04, 2007 4:15 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
Ok this is a first both Karmele and Gaizka have written feedback at exactly the same time with pritty much the same problem. Karmele first read Gaizka mail he is half way there.

The problem is that in the serialisation \ deserialisation process the infopath Processing Instructions (PIs) are lost. These are used by sharepoint to correctly identify the xml file as an infopath document. I store the processing instructions in a config file so if a new version of infopath comes along I have some ability to change the PIs which are version specific.

Gaizka I don't have access to the PIs at the moment but if you save an infopath document to disk before you serialise\deserialise and open it in notepad the PIs will be at the top.

Hope this helps.
Left by Rob A on Jan 04, 2007 8:12 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
This was really very userful buddy...
Can u please help me up in this...

I want to Create an application where user can interact with the workflow from within the InfoPath Office client application? Where InfoPath form doesn’t open in a web browser. It opens on client MS InfoPath.

Provide some insight into this problem and if possible provide coding tips for the same.
Left by Manoj Iyer on May 28, 2007 4:42 PM

# re: Windows Workflow Foundation, InfoPath and SharePoint Portal Server 2003
Requesting Gravatar...
This was really very userful buddy...
Can u please help me up in this...

I want to Create an application where user can interact with the workflow from within the InfoPath Office client application? Where InfoPath form doesn’t open in a web browser. It opens on client MS InfoPath.

Provide some insight into this problem and if possible provide coding tips for the same.

-Thanks in Advance
Left by Manoj Iyer on May 28, 2007 4:49 PM

Your comment:
 (will show your gravatar)


Copyright © Rob Addis | Powered by: GeeksWithBlogs.net