SharePoint Document Libraries, Versioning and REST

Mike BerrymanOne of my more recent projects in SharePoint 2013 had the client asking for a pretty basic document management system.  This project was done primarily with Javascript using REST calls to the SharePoint server.  Part of what the users needed to accomplish regarding document management was:

  1. Upload documents from the application
  2. Fill in metadata about the document
  3. Allow for uploading a revised version of a document as a new version of that document

In SharePoint, there’s a versioning setting that can be turned on for document libraries.  This allows for maintaining a version history of a document within SharePoint.  Every time a change is made to the document (or its metadata), SharePoint sees it as a new version of the document and creates a new entry in the version history log.  This is important to know due to the way documents are uploaded via REST in SharePoint.

Uploading a file via REST in SharePoint is easy enough – there’s plenty of articles and examples out there showing exactly how to go it – so I won’t go into the details for that process.  The same goes for updating the metadata for a document with REST.  For the purposes of this blog post, the general process is as follows:  First, upload the document.  The response of a successful upload will contain some information regarding the ListItem that was created as part of the upload.  Then, use that information to update the ListItem (aka metadata) for the document.

As you can see, uploading a document with some metadata is a 2-step process.  When versioning is turned on for a document library, this 2-step process results in 2 versions being created – 1 when the document was uploaded, and 1 when the metadata was updated.  For my client, this was confusing and thus unacceptable.

To fix this issue I needed a way to ensure that only one version of a document was created during the upload/edit metadata process.  Ultimately I found a rather obscure REST API endpoint that allowed me to update the metadata without creating a new version of the document called ValidateUpdateListItem.  This endpoint accesses the ListItem of the document through the document itself, so it requires the server-relative URL of the document.  One of the parameters that can be POSTed to this endpoint is “bNewDocumentUpdate” and if set to true will perform the update on the ListItem without incrementing the version of the document.  The tricky part of this endpoint is the way you pass along the data on the ListItem you want to update.

function updateDocumentListItemNoVersion(serverRelativeFileUrl, projectId) {
    var body = {
        formValues: [{
            __metadata: { "type": "SP.ListItemFormUpdateValue" },
            FieldName: "Title",
            FieldValue: data.Title
        },
        {
            __metadata: { "type": "SP.ListItemFormUpdateValue" },
            FieldName: "Project",
            FieldValue: projectId + ""
        }],
        bNewDocumentUpdate: true
    };
    var headers = {
        "X-RequestDigest": $("#__REQUESTDIGEST").val(),
        "accept": "application/json; odata=verbose"
    };
    var url = _spPageContextInfo.webAbsoluteUrl + 
"/_api/web/lists/getbytitle('DocLibTitle')/rootfolder/files/getbyurl(url='" + 
serverRelativeFileUrl + "')/listitemallfields/validateupdatelistitem"
    $.ajax({
        url: url,
        type: "POST",
        data: body,
        headers: headers,
        complete: function(response) {
            //I use a promise structure here to resolve a promise with the
            //response data, but it can be handled however you wish
        },
        error: function(err) {
            //handle the error
        }
    });
}

The payload for this endpoint is the “formValues” parameter, which is an array of fields and the value you want them set to.  Note that the “FieldName” is the internal name of the field, and the “FieldValue” is expected to be the correct data type.  For example, one of the metadata fields I needed to update was a lookup field which in its rawest form is a string.  I had the ID of the lookup value I wanted the field set to, but it needed to be converted to a string in order to function properly.

Also of note is that unlike most other SharePoint REST update POSTs, the headers for this request don’t include the “X-HTTP-Method” = “Merge” or “If-Match” properties, effectively making it act like an insert POST instead of an update POST.  Just another quirk that tripped me up for a bit.

At this point my consumer code looked something like this:

function uploadDocument(file, metadata) {
    uploadDocument(file).then(function(response) {
        var serverRelativeFileUrl = response.data.d.File.ServerRelativelUrl;
        updateDocumentListItemNoVersion(serverRelativeFileUrl, 
metadata).then(function(successResponse) {
            //update the UI to let the user know the file was successfully 
            //updated
        }, function(error) {
            //alert the user about the error
        });
    });
}

With the process only creating one version of a document.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s