I have been working with uploading larger files lately with REST, and in an attempt to use concurrency control I stumbled on an annoying gotchya.
I received an error along the lines of
POST https://mysite.sharepoint.com/sites/tw-dev1/_api/Web/GetFileByServerRelativeUrl('/sites/tw-dev1/Documents/a04c54c1-bd17-4a58-b506-6dd31f94b623.txt')/ListItemAllFields 412 (Precondition Failed)
There are 2 issues I found:
- When creating a file using REST, the GUID included in the ETag response is in Upper Case but when you POST you need to use lower case
- The GUID may also contain braces { } which also need to be removed
Note: At some point in the past I remember the ETag just being a number, but now in SharePoint Online includes a format like "[GUID],[Int]".
Using fiddler, you can see the response which has the clue:
<?xml version="1.0" encoding="utf-8"?><m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><m:code>-1, Microsoft.SharePoint.Client.ClientServiceException</m:code><m:message xml:lang="en-US">The request ETag value '"{e9619514-4ebc-424f-8505-79a8ae02319c},1"' does not match the object's ETag value '"e9619514-4ebc-424f-8505-79a8ae02319c,1"'.</m:message></m:error>
Cause
The IF-Match header uses the eTag to maintain concurrency control. You will receive a 412 if the value of that header does not match the SharePoint value exactly.
Unfortunately, the eTag given by the SharePoint REST API when you initially create a file is not in the same format that is used when you update.
Solution
Remove the braces and lower case the eTag.
etag = etag.replace(/{|}/g, "").toLowerCase();
Alternate solution
If you want to turn off concurrency altogether (not ideal), you can use "*" as the IF-MATCH header value.
Source
For reference, here is my code to update the files list item fields using REST.
TypeScript:
public updateListItem(siteCollectionUrl:string, serverRelativeUrl: string, properties: any, etag:string = "*") {
if (etag) {
etag = etag.replace(/{|}/g, "").toLowerCase();
}
if (typeof (properties.__metadata) == typeof (undefined)) {
properties.__metadata = { 'type': 'SP.ListItem' };
}
var url = `${siteCollectionUrl}/_api/Web/GetFileByServerRelativeUrl('${serverRelativeUrl}')/ListItemAllFields `;
return $.ajax({
url: url,
type: "POST",
data: JSON.stringify(properties),
headers: {
"X-RequestDigest": $("#__REQUESTDIGEST").val(),
"content-type": "application/json;odata=verbose",
"IF-MATCH": etag,
"X-HTTP-Method": "MERGE"
}
});
}