Wednesday, March 12, 2008

Day 381: Some thoughts about MOSS, Workflows, InfoPath and WebServices

Hi. Lately I've been involved in a tricky project and would like to tell you the way I tackled it. While fighting with it I found lots of useful blogs ... so I thought now it's my turn to share some knowledge.

The main part of it consisted of a custom workflow that is attached to a SharePoint document library. When started the workflow creates several tasks that fire up InfoPath forms. InfoPath forms themselves query a web service to populate its drop down lists with data.

The first key moment was the use of Visual Studio 2008 and VSTO (Visual Studio Tools for Office). Now debugging the workflow is a breeze - right click your state machine or sequential workflow project, configure "SharePoint Debug Settings", place your breakpoints, click F5 and enjoy. That's all. VS 2008 will do everything for you, open a browser and navigate to the appropriate document library. No more manual IIS resets, GAC assembly deployment, detaching, attaching ...

Another interesting thing was where to deploy my web service. Knowing where most SharePoint web services are, I headed for the /_vti_bin folder of the moss web application, created a virtual directory to put the .asmx, web.config file and bin folder, configured the directory as application and pointed it to the the correct application pool. So far so good. Problems began when I started to receive this error message: "Unable to connect publishing custom string handler for output caching.". After googling for a while I read that the SharePoint page processing model may cause this because it overrides the way pages are parsed. I needed to modify the web service web.config, as described in this blog, to restore its standard handling. Ok, sounds pretty reasonably, except the fact that it doesn't work. I looked in disbelief for a few minutes when the idea to move the web service to another folder struck me - I put the web service files in the /_layouts folder and now everything was fine.

The next challenge was how to publish the InfoPath forms so that they could still find the web service when we move the application to test environment, for example the web service url in my dev environment looks like http://mossdev/_layouts/DemoWS/Service1.asmx but in the test environment it looks like http:// my.test.server/_layouts/DemoWS/Service1.asmx. I read the following MSDN article "InfoPath Support for Relative Paths to Web Services" but it was not appropriate for my case. It was time for Universal Data Connections. You can create Universal Data Connection File (.udcx) if you "convert" a data connection in InfoPath. What surprised me here was that though I wanted to centrally manage my connection, I had to upload it first to a data connection library, then download a copy and then upload it through Central Administration, InfoPath Forms Services section. The UDC file is a XML file that describes how to access a specific datasource. You can find the following tag inside the file: <udc:wsdlurl> http://mossdev/_layouts/DemoWS/Service1.asmx?wsdl</udc:wsdlurl>.
When you move to another environment you have to edit this file, for example replace the host header for the web service, and upload it again in central administration. The only thing your InfoPath form is interested in is to find the UDC file with certain name located under the IP section of CA. That's all. Very flexible solution!

"Houston, we have another problem!". One of the web methods needed an input parameter. How to supply this value at run time? I felt I could do this from the workflow ... When you define a data connection to a web method that needs parameter, InfoPath creates a Secondary Data Source and exposes this parameter as a field. You know that you can supply information to your form using that ItemMetadata.xml file and ows_field. Ok. I decided to create a rule "set team=ows_team" ("team" is the parameter) that applies everytime the form is loaded. The ows_team value I supply programmatically from the workflow when I create the task with a line like this: spWorkflowProperties.ExtendedProperties["ows_team"] = "team value"; One final problem: My web service was called before the parameter could be set from the workflow. To fix this you have to modify your data connection and uncheck the box "Automatically retrieve data when form is opened", place a control(for example a button) and create a rule saying "execute this data connection when button is clicked". Prima.

Tot zo.