It's time to get our elbows a little dirty. Oh sure, we can initate a conversation with SharePoint/Groove, we can add workspaces to SharePoint/Groove and we can even add tools to SharePoint/Groove. But this is all preliminary stuff. What have you done for me lately? How about adding a little data to our tools? And what's really nice, it further supports my claim that SharePoint and Groove could really have their web services unified and become SharePoint Groove, or Groove SharePoint, depending on your point of view.
I'll pick two tools that I've been working with extensively for the past two weeks. Yes, its taken me a couple weeks to figure out how best to present this information. This may be a bit longer than my earlier posts, but its chock full of information.
The tools we will examine are the standard Task List in SharePoint 2003 and the unstandard TeamDirection Task List in Groove 2007. Why the Task List? My company does Project Management software and I'm partial to tasks. Why an unstandard TeamDirection Task List? Because, as of right now, there is no standard Task List for Groove 2007. We're hoping to change that; whether Microsoft ends up using ours or creates their own, we believe Groove needs a task list (and Project Management, as you might expect :)). In any case, you can download the TeamDirection Task List
here, if you'd like to follow along.
The SharePoint Task List is really a SharePoint List and the TeamDirection Task List is really a Groove Form. Remember, I'm claiming SharePoint Lists and Groove Forms are equivalent. Although, in all honesty, I'm not the only one to see the similarities. Groove
did too, a couple of iterations ago. I'm sure that's why SharePoint Lists and Groove Forms line up so nicely.
Let's start with the SharePoint Task List. The first thing I'd like to do is add tasks. To accomplish this, you first have to take a detour and meet CAML. I'm not exactly sure what it stands for, and to be honest it doesn't really matter. All I will say is it is aptly named, since its ungainly and something only a mother could love. BUT, its what we have to work with, and in fact it can cross the sands of stateless data transfer. It's just that what makes this journey particulary daunting is the documentation on this kind of CAML is so minimal that its next to useless. Thankfully, we have the ultimate reference implementations in Microsoft Office, and the ultimate HTTP sniffer tool in
Fiddler. Fiddler Rocks and is a detour worth taking. If you are serious about WSS, GWS and any WS development, its worth its weight in gold to see what's really going on to make things work. Note: There is a server side object model available from Microsoft
here; but it won't help you do smart clients, which is why I'm writing this.
Where were we? Right, CAML. You can add a task with CAML following these three easy steps. We will lift the curtain and start with Step 3 first:
<?xml version="1.0" encoding="UTF-8"?>
<ows:Batch OnError="Continue" xmlns:ows="http://tempuri.org/">
<SetVar Name="DatesInGregorian">TRUE</SetVar>
<SetVar Name="TimesInUTC">TRUE</SetVar>
<Method ID="New">
<SetList Scope="Request">{DF1CC702-9C1F-4C05-8805-BFEF41F5CC28}</SetList>
<SetVar Name="Cmd">Save</SetVar>
<SetVar Name="ID">New</SetVar>
<SetVar Name="urn:schemas-microsoft-com:office:office#Title">My New Task</SetVar>
<SetVar Name=\"urn:schemas-microsoft-com:office:office#AssignedTo\">0</SetVar>
<SetVar Name=\"urn:schemas-microsoft-com:office:office#Status\">Not Started</SetVar>
<SetVar Name=\"urn:schemas-microsoft-com:office:office#Priority\">(2) Normal</SetVar>
<SetVar Name=\"urn:schemas-microsoft-com:office:office#PercentComplete\">0</SetVar>
<SetVar Name=\"urn:schemas-microsoft-com:office:office#StartDate\">2006-08-14T15:00:00Z</SetVar>
<SetVar Name=\"urn:schemas-microsoft-com:office:office#DueDate\">2006-08-16T00:00:00Z</SetVar>
<SetVar Name=\"urn:schemas-microsoft-com:office:office#Body\"></SetVar>
</Method>
</ows:Batch>
So what does this all mean? For one, like a camel, its very finicky. If any of your data types are wrong, for instance your StartDate isn't in Zulu time, the web service request fails. If you mistakenly place the 'DatesInGregorian' element after the 'Method ID=' element, the web service request fails. This is where fiddler shines in that you can set up the exact scenario your smart cilent is trying to produce in MS Word or Excel and watch how they do it.
Some other points of interest are the Method element and the SetVar Name='Cmd' element. Because we are creating a new task, the Method element's value is 'New' and the SetVar Name='Cmd' value is New. As you might expect, when updating, these values change to Update and a real task id, respectively.
A word of caution, though. Don't rely purely on the MS Office applications, for they may not tell you the whole story. For example, if you watch Word create a task, you'd never know you can set the start date. It only sets the due date. How can you find all the available values for your tasks? In other words, how do you find the schema for the list? Ah, that was Step 2:
SharePointProxies.Lists ls = new SharePointProxies.Lists();
... set up the credentials and Url ...
XmlNode result = ls.GetList( listID );
The result XML divulges the schema of a particular list. I put a sample of the SharePoint Task List schema
here. This schema gives you important information on what's available, either purely for reading (like creation and modification stamps) or updating (like our StartDate).
So how do you find that list ID? That is Step 1, Finding your List ID:
SharePointProxies.Lists ls = new SharePointProxies.Lists();
... set up the credentials and Url ...
XmlNode result = ls.GetListCollection();
The result XML divulges all you lists, descriptions and identifiers. I put a sample of the SharePoint list collection XML
here.
So let's review the SharePoint steps to add data in order:
Step 1 Get a listing of all the lists in your SharePoint Workspace and associate ids with lists you want to work with.
Step 2 Get familiar with what the schema for your list has to offer. Ideally you will have an application you can sniff that works with your list.
Step 3 Start adding data. Here's a fuller example for creating a task. Remember that big CAML up above? Here's how you send it (Note that it goes to a Document Workspace Service instead of a Lists service!):
SharePointProxies.Dws dws = new SharePointProxies.Dws();
dws.Credentials = this.Credentials;
dws.Url = m_uri + "_vti_bin/dws.asmx";
string result = null;
try {
result = dws.UpdateDwsData( camlString, null );
}
The resulting XML of this call is a list of new ids for all the tasks you have created. We will leave this as an exercise for the reader to match new identifiers with tasks (hint: its returned in order of creation).
Are you ready for a bit of Groove Form task creation? Let's get started on that.
If you go to the TeamDirection SharePoint Groove Solutions
page, you see a TDWorkspace. Download and double click on this to install it in Groove 2007 if you'd like to apply these samples to something real.
Like the SharePoint Task creation, it takes 3 general steps to perform a Groove Task creation, but we will cover the steps in more detail.
Step 1 Get a listing of all Forms within your Groove Workspace
We covered this a bit in
Part III of the series with the acquireTelespaceTools example code.
Step 2 Get familiar with what the schema for your form has to offer.
Where SharePoint uses CAML to manipulate list data via WSS, Groove 2007 made the (good) decision to use System.Data.DataSets to manipulate form data via GWS. I have to bone up on SharePoint 2007 and their support of DataSets, but I like the idea in general as DataSets represent a nice abstraction. You can still work with XML if you really want to, and in fact I'll show you a Groove XML schema for our Task List, but DataSets will make your life easier. And don't forget about Hugh Pyle's
blog and his samples.
If we've completed step 1 successfully, we get the forms schema by querying for it. SharePoint lists also support querying, of course, but unlike Groove provides a distinct schema acquisition method. Groove supplies the schema by query flags. The code looks like this:
protected System.Data.DataSet getSchema() {
GrooveProxies.Forms2 svc = new GrooveProxies.Forms2();
buildRequestHeader( svc );
GrooveProxies.RecordQuery recordQuery = new GrooveProxies.RecordQuery();
recordQuery.FormURI = "";
recordQuery.IncludeFileAttachmentContent = false;
recordQuery.QueryMetadataOnly = true;
recordQuery.UnreadRecordsOnly = false;
recordQuery.ViewURI = "";
recordQuery.WhereClause = "";
// The query is really a schema retrieval, but this is the mechanism to retrieve
WSDLProxies.Forms2RecordDataSet fds;
fds = svc.QueryRecords(recordQuery);
// Make a DataSet to contain the records
System.Data.DataSet recordDataSet = new System.Data.DataSet("RecordDataSet");
// Load the schema into the DataSet
if (fds.Schema is System.Array) {
System.Xml.XmlNode[] schemaNodes = (System.Xml.XmlNode[])fds.Schema;
foreach (System.Xml.XmlNode schemaNode in schemaNodes) {
System.IO.StringReader schemaReader = new System.IO.StringReader("<?xml version=\"1.0\"?>" + schemaNode.OuterXml);
recordDataSet.ReadXmlSchema(schemaReader);
}
}
recordDataSet.AcceptChanges();
recordDataSet.EnforceConstraints = false;
return recordDataSet;
}
If your curious what a Groove Form schema looks like,
here is the xml for our Task List.
What immediately stands out in this schema as opposed to the SharePoint Task List schema is the presence of multiple Tables. Groove Forms, and DataSets, can contain multiple tables, whereas the SharePoint 2003 (at least) lists contain only a single table. This makes Groove Forms not only able to handle SharePoint list schemas, but even richer tools like InfoPath schemas as well (which in fact Groove 2007 does). By the way, the table defining our task is named "_451_465619523540032702E_43034". I'm going to ask our developer if we can name it 'Task' instead.
GOTCHA NOTE: Its always the little things that eat up time, and here's one that I found. When you build your request headers, the Url value you use changes depending on the context of your web service call. Let's revisit a sample web service request Url.
/GWS/Groove/2.0/Spaces/grooveTelespace/8ecctni3u4hzrvpxhhymbmqcdd8q8itexbttk7s
Which is fine if I send requests to the Spaces proxy, but I need to change Spaces to Tools like so:
/GWS/Groove/2.0/Tools/grooveTelespace/8ecctni3u4hzrvpxhhymbmqcdd8q8itexbttk7sAnd, when you talk to specific tools, like the Forms, you need two changes: no only from Tools to Forms, but you need to append DataModelDelegate. DataModelDelegate is a Groove API construct from way back and represents the Data controller of a Model View Controller pattern. So talking to a form requires an url like:
/GWS/Groove/2.0/Forms2/grooveTelespace/8ecctni3u4hzrvpxhhymbmqcdd8q8itexbttk7s/DataModelDelegate
Finally, when dealing with records within a form, you append the record id like so:
/GWS/Groove/2.0/Forms2/grooveTelespace/8ecctni3u4hzrvpxhhymbmqcdd8q8itexbttk7s/DataModelDelegate/12321.212352233
Step 3 Start adding data.
Here's the easy part with the Groove Forms interface. Since it works so well with System.Data.DataSets, adding a task to our task form is as easy as adding it to our data set. In fact, Hugh has a nice example of exactly
this.
For our task specific application, it might look like this:
DataTable dt = ds.Tables ["_451_465619523540032702E_43034"];
DataRow dr = dt.NewRow();
dr["Title"] = "Task 1";
dr["description"] = "I am the first task";
dr["startDate"] = "2006-08-14T08:00:00";
dr["dueDate"] = "2006-08-15T17:00:00";
dr["Priority"] = "(2) Normal";
dr["Status"] = "In Progress";
dr["PercentComplete"] = 0.5;
dr["AssignedTo"] = "grooveIdentity://asdfawtadfglksa";
dt.Rows.Add(dr);
tool.Insert(ds);
Both SharePoint and Groove provide a means to create, update and delete data. We haven't looked at deleting data, but its there. The other thing we might have noticed is query capabilities for Groove. Rest assured SharePoint has query capabilities as well. And rest assured we will cover querying in the near future.
Now if I had a company, and I knew a little something about SharePoint and a little something about Groove-- possibly even enough something to make me dangerous-- do you think I'd be looking into a way to seamlessly transfer data to and from SharePoint Lists and Groove Forms? Yeah, I think so.
But what you may not know is Groove Forms must be designed by the Groove Forms Designer. Unlike SharePoint, which allows me to modify list schema via WSS (that's a whole nother topic!), Groove can only inject 'well formed' forms into a workspace. So we can't quite achieve total data mobility, however, what we can do is move data between well understood workspaces. An example would be data between a default SharePoint Document Workspace and a richer Groove Workspace. I'm partial to the TD Workspace myself, and soon I may just point you to a tool that does exactly what I've described.