RSS

Tag Archives: Web Service

Batch Updates with SharePoint Web Services

Applies To: SharePoint

One of the biggest irritations to me coming from a SQL background is trying to do SQL like things in SharePoint 2007. One of those irritations is performing a simple update through the web services.  In SQL, I could write something like:

UPDATE ListTable
    SET FieldName = FieldValue
    WHERE QueryFieldName = QueryFieldValue

In the generic T-SQL above, any row where the QueryFieldName column had a value equal to the QueryFieldValue would have it’s FieldName Column set to FieldValue. Pretty straightforward for anyone with a database background.

With the SharePoint Web Services, however, there isn’t a simple UPDATE command like this. Instead of the one simple command above, we have to make 2 calls to the Web Service and provide some complicated XML parameters. This can be a big hassle the first time you do this, so to hopefully save you some headaches, I’ll provide most of the code required below.

I won’t be showing you how to setup whatever project you are using or how to connect to the web service. The code is in C# and I will assume your service reference (Lists) is called myService.  So let’s get started!

Helper Functions:

I will be using a few helper functions to make generating the XML parameters easier, I’ll go ahead and list these here:

private void AddAttribute(XmlDocument x, ref XmlNode node,
          string attributename, string attributevalue,
          string AttributeNameSpace = "")
{
    XmlNode attnode =
              x.CreateNode(XmlNodeType.Attribute, attributename,
              AttributeNameSpace);
    attnode.Value = attributevalue;
    node.Attributes.Append(attnode);
}

private XmlNode CreateUpdateNode(XmlDocument x, int ID, string RowID)
{
    XmlNode node = x.CreateNode(XmlNodeType.Element, "Method", null);
    AddAttribute(x, node, "ID", ID);
    AddAttribute(x, node, "Cmd", "Update");
    node.AppendChild(CreateFieldNode(x, "ID", RowID));
    return node;
}

private XmlNode CreateFieldNode(XmlDocument x, string Name, string Value)
{
    XmlNode node = x.CreateNode(XmlNodeType.Element, "Field", null);
    AddAttribute(x, node, "Name", Name);
    node.InnerText = Value;
    return node;
}

Retrieve the Items to be Updated:

This is equivalent to the WHERE clause of the example SQL query. You will need to use the GetListItems method of the Lists service.

This method uses the List GUID to reference the target list, I’ll leave it to you to retrieve it and will assume it in the variable ListGUID. You will also need to replace the QueryFieldName, QueryFieldType, and QueryFieldValue variables with appropriate values.

XmlDocument doc = new XmlDocument();
XmlNode queryNode = doc.CreateNode(XmlNodeType.Element, "Query", null);
queryNode.InnerXml =
          string.Format("<Where><Eq><FieldRef Name='{0}' />
                         <Value Type='{1}'>{2}</Value></Eq></Where>",
          QueryFieldName, QueryFieldType, QueryFieldValue);

XmlNode viewNode = doc.CreateNode(XmlNodeType.Element, "ViewFields", null);
viewNode.InnerXml = "<FieldRef Name='ID'/>";

XmlNode resultsNode = myService.GetListItems(ListGUID, null, queryNode,
          viewNode, null,
          doc.CreateNode(XmlNodeType.Element, "QueryOptions", null), null);

 Parse the IDs from the Results:

XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("z", "#RowsetSchema");
XmlNodeList rowNodes = resultsNode.SelectNodes(".//z:row", nsmgr);
List<int> IDs = new List<int>();
foreach (XmlNode rowNode in rowNodes) {
    XmlAttribute result = rowNode.Attributes("ows_ID");
    IDs.Add(int.Parse(result.Value));
}

Perform the Update:

This is equivalent to the SET portion of the example SQL query. You will need to use the UpdateListItems method of the Lists service.

Again, this method uses the List GUID to reference the target list, I’ll leave it to you to retrieve it and will assume it in the variable ListGUID. You will also need to replace the FieldName and FieldValue variables with appropriate values.

<pre>XmlDocument doc2 = new XmlDocument();
XmlNode node = doc2.CreateNode(XmlNodeType.Element, "Batch", null);
AddAttribute(doc2, node, "OnError", "Continue");

int itemCount = 1;
foreach (int i in IDs) {
    XmlNode updateNode = CreateUpdateNode(doc2, itemCount, i);
    updateNode.AppendChild(CreateFieldNode(doc2, FieldName, FieldValue));
    //Append More children here to update multiple fields
    node.AppendChild(updateNode);
    itemCount += 1;
}

myService.UpdateListItems(ListGUID, node);

That’s it. To review, we created a CAML query and retrieved the IDs of the matching rows using the GetListItems method. We parsed the XML result and put all of those IDs in a List<int>. We then added each row ID to a batch update XML parameter which we used in the UpdateListItems method.

As you can see, this is way more complicated and not nearly as obvious as a SQL UPDATE command, but you can take the above code and create a pretty generic wrapper to make your web service updates nearly as easy. Let me know how it works out for you or if you have any questions!

Originally Published on WireBear.com/blog on February 11, 2011
 
1 Comment

Posted by on February 22, 2012 in SharePoint, Web Services

 

Tags: , , , , ,

Multiple Submit Buttons in LiquidOffice

Applies To: LiquidOffice

LiquidOffice is an enterprise forms processing and workflow suite from Cardiff (Now Autonomy).  It does some things very well (like routing forms), but it can be an incredibly frustrating piece of software for developers and designers.

Recently a client wanted to use LiquidOffice (v6.2.1) alongside a web service to provide a list of locations using a zip code and mile radius.  The requirements included the ability to return to a previous form and to change the zip code and/or mile radius.  Doing something like this in ASP.NET would be very easy and there are a ton of examples out there.  Doing this in LiquidOffice, however, presents quite a few challenges:

  • The LiquidOffice Web Service task is just awful
  • Populating a table from the process isn’t straightforward
  • By default you can have only one submit button on a form (although there is a list of actions which can have various values)
  • Navigating between forms isn’t intuitive either

The good news is that we got it working.  To try and help others that may be struggling with how to do some of these things we will present our solutions.

The LiquidOffice Web Service Task

We could probably write an entire post just about this alone, but instead we’ll show you how to work around some of the difficulties and get it working.

A couple of warnings though:
(all of these issues have been confirmed with Autonomy technical support)

  • The WS Task does NOT do authentication of any kind
  • The WS Task will NOT create complex objects
  • Once you have set a WS location and saved it, you can NEVER change it without recreating the task.  You can change it, save it and it will be reflected in the task properties, but it will continue to point to the previous WSDL.

Fortunately, there are work arounds to all of the above.  We didn’t need authentication for this particular web service.  We’ve needed it before (like when connecting to SharePoint), and we hope to cover how we overcame that obstacle in a future post.

We did however need a complex object.  In this case our locations were being returned in an array of location objects.  To get around this we created our own intermediate web service sitting on the LiquidOffice Process Server.  Calling this web service called the location service and wrapped those objects into a single string (very similar to JSON).  Then using a Script Task we parse out the string (using the split command) into an array and use it as a complex object again.

Populating the table should be done on the Form task and accessed using thisTask.getTable(“TableName”);  If you use the thisProcess.getTable(“TableName”); command you will have to find the internal name of the table.  This name is actually your table’s name plus a random number (I’m sure it’s not random but tech support could not tell us where it came from or guarantee it wouldn’t change).

Multiple Submit Buttons

For our form we needed a button that would return the user to the previous form, a button that would reload the list of locations, and of course a submit button.  These could all be accomplished by using the default submitaction control and using the form task properties to customize the Submit Action List.  Unfortunately, the end result on the form is not very user friendly.  It puts a combo box with the submit actions next to a button and the user is expected to choose their action and then hit the button.

You can, however, hide this control and use javascript attached to other buttons to simulate selecting an action and pressing the submitaction button.  To do this you still need to setup your Submit Actions.  For this form our Submit Actions look like this:

On the form we create three buttons (Back, Reload, and Submit) and use client side code to hide the submitaction control.  Here is our Javascript:

function CSForm_OnLoad()
{
  document.getElementById("DFS__ActionList").style.display = 'none';
  document.getElementById("DFS__GO").style.display = 'none';
}

function ReloadButton_OnButtonClick()
{
  CSForm.getField("SearchZipCode").setRequired(true);
  SubmitForm("Reload Locations");
}

function LocationSelected() {
  var locations = CSForm.getTable("Locations");
  for (var i=0;i<locations.getNumberOfRows();i++) {
    if (locations.getAt(i,"Selected").getValue()=="1") {
      return true;
    }
  }
  return false;
}

function SubmitButton_OnButtonClick()
{
  CSForm.getField("SearchZipCode").setRequired(false);
  if (LocationSelected()) {
    SubmitForm("Submit Choices");
  } else {
    CSClient.alert("You must choose at least 1 location!",0,1);
  }
}

function BackButton_OnButtonClick()
{
  SubmitForm("Back");
}

function SubmitForm(result) {
  document.body.style.cursor = 'wait';
  var choices = CSForm.getField("DFS__ActionList").getChoices();
  for(var i=0;i<choices.getCount();i++) {
    if (choices.getAt(i,true)==result) {
      document.getElementById("DFS__ActionList").selectedIndex = i;
      document.getElementById("DFS__GO").onclick();
      break;
    }
  }
}

Then in our process we add two loop objects so that it looks like this:

In the loop tasks we edit the Loop properties to set the Follow Path Based On section toAnother Task.  The Location Search Loop task looks like this:

The Task Output should match the submit action value from above.  The Navigation Loop is exactly the same except it’s Task Output value is set to -1.

So, yes it’s possible to do some complicated navigation in your forms and, yes, you can have multiple submit buttons.  It just takes a little work!

Originally Published on WireBear.com/blog on October 25, 2010
 
1 Comment

Posted by on February 20, 2012 in Form Designer, LiquidOffice, Process Designer

 

Tags: , , , , ,

 
Follow

Get every new post delivered to your Inbox.

Join 31 other followers