Monday, July 27, 2009

New version of the SPListItem editor released

Here is a small video with the new features:

The latest release can be found here: http://splistitemeditor.codeplex.com/

Friday, July 17, 2009

Tips for using SPWeb.ProcessBatchData

There’re quite some samples about how to use the ProcessBatchData method of the SPWeb class on the internet but it turns out that the full capabilities of the method, i.e. the input XML that it uses are not that well documented. So here’re several examples that I managed to get to work while working on my SPListItem editor tool:

  • Create a folder in a SharePoint list/library:

<?xml version="1.0" encoding="utf-8"?>
<ows:Batch OnError="Continue">
  <Method ID="Test">
    <SetList Scope="Request">82d62a9a-55ba-49c8-a9b8-68ec965a5931</SetList>
    <SetVar Name="Cmd">Save</SetVar>
    <SetVar Name="ID">New</SetVar>
    <SetVar Name="Type">1</SetVar>
    <SetVar Name="owsfileref">/sites/1/docs/folder1</SetVar>
  </Method>
</ows:Batch>

The critical line here is the setting of the Type parameter – it is actually an alias of the system FSObjType field and without it the call won’t work.

This way you can create subfolders too, e.g. using:

    <SetVar Name="owsfileref">/sites/1/docs/folder1/sub1</SetVar>

The only requirement is that the parent folder exists, if it doesn’t – the call will fail.

  • Rename a file or a folder in a SharePoint list/library:

<?xml version="1.0" encoding="UTF-8"?>
<ows:Batch OnError="Continue"><Method ID="Test">
  <SetList Scope="Request">82d62a9a-55ba-49c8-a9b8-68ec965a5931</SetList>
  <SetVar Name="Cmd">Save</SetVar>
  <SetVar Name="ID">28</SetVar>
  <SetVar Name="owsfileref">/sites/1/docs/fld2</SetVar>
  <SetVar Name="owsnewfileref">fld1</SetVar>
</Method></ows:Batch>

So you need both the server relative URL of the file or folder object in the owsfileref parameter and the ID of the associated list item here. The new name is provided in the owsnewfileref parameter. One remark here – you can provide the new name without the extension part – the extension will be added automatically for files. In case you provide an extension (which differs from the original extension of the file) it will be ignored.

  • Using the owshiddenversion field:

Using this field is very important especially when you have versions enabled or mandatory check out for editing in document libraries. In order that you can use it, you first need to retrieve the list items having set the ViewFields property of the SPQuery object to contain the owshiddenversion field. And you need to provide the same value of the owshiddenversion field in the ProcessBatchData XML as you retrieved originally with the list item. A sample XML would look like:

<?xml version="1.0" encoding="UTF-8"?>
<ows:Batch OnError="Continue">
  <Method ID="M0">
    <SetList>82d62a9a-55ba-49c8-a9b8-68ec965a5931</SetList>
    <SetVar Name="Cmd">Save</SetVar>
    <SetVar Name="ID">23</SetVar>
    <SetVar Name="owsfileref">/sites/1/docs/codes.txt</SetVar>
    <SetVar Name="owshiddenversion">93</SetVar>
    <SetVar Name="urn:schemas-microsoft-com:office:office#Title">some title</SetVar>
  </Method>
</ows:Batch>

Basically this field is auto-incremented with every update of the list item. SharePoint checks the value of the field and compares it to the value of the current version of the list item. So if you have it in the update XML and another user has updated the item before you, you will receive the standard error message of saving conflict with a concurrent user. And in case the file in a document library that you try to update is not checked out, you’ll get again a standard error message. In both cases if you don’t include the owshiddenversion field in the update XML the update will actually succeed and you will end up with either overwriting the changes of another user or breaking the concurrence constraints that are normally enforced with the check-out mechanism.

And if you intend to do several updates in a row on a certain list item you should either re-fetch the item after each update to have the updated value of the owshiddenversion field for the next update (this is what actually happens when you update an item with SPListItem.Update) or be more economical and increment the value by one yourself.

  • Change the moderation status of a list item with the moderate command

OK, so it was known for some time that next to the Save command there is an undocumented Moderate command for updating the list item moderation status (setting an item to approved, rejected, draft, etc) using the ProcessBatchData method. Here is a sample XML:

<?xml version="1.0" encoding="UTF-8"?>
<ows:Batch OnError="Continue">
  <Method ID="M0">
    <SetList>82d62a9a-55ba-49c8-a9b8-68ec965a5931</SetList>
    <SetVar Name="Cmd">Moderate</SetVar>
    <SetVar Name="ID">26</SetVar>
    <SetVar Name="owsfileref">/sites/1/docs/a.txt</SetVar>
    <SetVar Name="owshiddenmodstatus">3</SetVar>
    <SetVar Name="urn:schemas-microsoft-com:office:office#_ModerationStatus">0</SetVar>
    <SetVar Name="owsitemlevel">2</SetVar>
    <SetVar Name="_Level">2</SetVar>
    <SetVar Name="owshiddenversion">12</SetVar>
  </Method>
</ows:Batch>

Note the usage of two more “hidden” fields here – the owshiddenmodstatus and owsitemlevel – they should contain the values of the original values of the _ModerationStatus and _Level fields when the list item was retrieved. The urn:schemas-microsoft-com:office:office#_ModerationStatus parameter should contain the new value of the moderation status. Actually these two are required only for document libraries – for lists you can provide just the urn:schemas-microsoft-com:office:office#_ModerationStatus parameter. Using this syntax you can quickly approve items in batches, and a clear difference with the Save command is that the Moderate command works on not checked out items (this holds for document libraries) and actually fails when the item is checked out.

Several tips for “hacking” the ProcessBatchData XML syntax

A good starting point is to check the standard SharePoint “Lists" web service – especially the UpdateListItems method. It uses a similar XML syntax for updating list items, and basically it’s easier to get things working with it – no fancy internal parameter names are needed. And the thing is that the web method internally uses SPWeb.ProcessBatchData and the input XML is being “translated” to the ProcessBatchData form. A nice way to check the work of the Lists.UpdateListItems is to open a list in datasheet view and check the requests sent to the web service with a web debugging proxy as fiddler. This way you’ll have real time XML samples – after all it’s obvious that the Lists.UpdateListItems was designed for use primarily by the standard list datasheet view.

As for the “translation” of the Lists.UpdateListItems XML to the ProcessBatchData form you can reflect the code of the standard SharePoint STSSOAP.DLL assembly – it’s located in the _app_bin subfolder of your SharePoint web applications. The “translation” is implemented in the ConstructCaml method of the Microsoft.SharePoint.SoapServer.ListDataImpl class. I actually got this method working in a small WinForm application, here is the sample code:

        private void TestConstructCaml()
        {
            System.Web.Services.WebService svc = new System.Web.Services.WebService();
            CreateHttpContext();
            test.ListDataImplProxy p = test.ListDataImplProxy.CreateUnderlyingInstance(svc);

            string listName = "docs";
            string xml = "<Batch OnError=\"Continue\"><Method ID=\"M0\" Cmd=\"Moderate\"><Field Name=\"ID\">22</Field><Field Name=\"FileRef\">/sites/1/docs/zterminal.csv</Field><Field Name=\"_ModerationStatus\">0</Field><Field Name=\"_Level\">2</Field><Field Name=\"owshiddenversion\">45</Field></Method></Batch>";

            string s = p.ConstructCaml(listName, xml);
            Console.WriteLine(s);

        }

        private void CreateHttpContext()
        {
            HttpContext.Current = null;
            string url = "http://racoon-vpc-hg:2909/sites/1";
            SPSite site = new SPSite(url);
            SPWeb web = site.OpenWeb();

            HttpRequest r = new HttpRequest("default.aspx", url, "");
            HttpResponse rs = new HttpResponse(null);
            HttpContext.Current = new HttpContext(r, rs);

            HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
            HttpContext.Current.Items["HttpHandlerSPSite"] = site;

        }

With this code I was able to check the translated ProcessBatchData XML from a Lists.UpdateListItems input XML. The ListDataImplProxy class is a reflection proxy class which calls the methods of the ListDataImpl class using reflection. It was generated with my reflection proxy generating utility, which can be downloaded from here: http://stefan-stanev-sharepoint-blog.blogspot.com/2009/05/how-to-access-non-public-class-members.html

Sunday, July 5, 2009

SPQuery Scope and ModerationType ViewAttributes

Basically there is some sporadic info here and there about these on the internet, for instance that the “Scope" attribute can take the values of “Recursive” and “RecursiveAll”.

So to wrap it up:

The possible values of the “Scope” attribute are:

  • Default
  • FilesOnly
  • Recursive
  • RecursiveAll

The possible values of the “ModerationType” attribute are:

  • HideUnapproved
  • Contributor
  • Moderator

It turns out that these two are analogous to the Scope and ModerationType properties of the SPView class, which are briefly explained here and here.