Using REST to upload a File to Document library (Including different site collection)


The code I’m going to show you today will show you how you can upload a file to a document library using REST. This solution could be used within a Sandbox application.

To upload files, it is much better to use REST than the SharePoint API. The main reason is that SharePoint JSOM/CSOM can only upload files a maximum size of 1.5MB, where REST is good up to 2GB.

My example here has a simple GUI, it is an Upload form, which allows the user to pick from a list the site they wish to upload the file to, a file upload box, and an Upload button.

HTML Code.

<SharePoint:FormDigest runat="server"/>
<div>
   <label id="lblInfo" class="label" for="ProposalSiteUrl">Please enter your Site Collection URL or leave blank to upload file to this Site Collection document library:</label>
</div>
<div>
   <select id="ProposalSiteUrl" class="dropdown" title="Select a site">
                    <option value="http://intranet.cannonfodder.local">This Site</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite1">Team Site 1</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite2">Team Site 2</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite3">Team Site 3</option>
      </select>
</div>
<div class="div">
        <input id="AttachmentUploadField" type="file" size="98"/>
        <input id="AttachAttachmentButton" class="button" onclick="CannonFodder.Utilities.SaveAttachment();" type="button" value="Upload"/>
</div>
<div class="div">
         <label id="lblResult" class="result"></label>
</div>

The user can select from the dropdown a site to send the file to. Select the file using the file picker and click Upload. You can see in this example I have hard coded the option values for the dropdown list. Any results pass or failed will be displayed to the user using the lblResult label.

JavaScript

This code will require JQuery for grabbing the element values from the page and to perform JQuery Ajax request. Ensure that your page has JQuery loaded. In this example I grabbing JQuery from Google.

<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Personally, JavaScript isn’t my preferred programming language and I’m trying to write better JavaScript. I’m ensuring I’m not making it part of the Global namespace, and using boxing methods etc. Therefore in this example here you should find this a good practice writing JavaScript.
First I’m going to namespace my JavaScript Methods.

var CannonFodder = CannonFodder || {};
CannonFodder.Utilities = function(){
var currentDlg;
 //Code to write in here.
 return {
      SaveAttachment:saveAttachment
 };
}();

The above piece of code, will create a namespace called CannonFodder.Utilities with one public method called SaveAttachment which calls the private method saveAttachement. We haven’t written saveAttachment yet. We will write a bunch of functions inside the section where I have commented ‘//Code to write in here‘. The first one will be saveAttachement. (Notice small ‘s’ for save) This method will be the only method in this section that can be called from outside of CannonFodder.Utilities. The variable currentDlg
is a global variable for the scope of CannonFodder.Utilities. It is so I can display a waiting dialog screen to the user. This isn’t necessary for this code to work, it just gives the demo a nicer user experience.

The saveAttachment method

var saveAttachment = function(){
    //Validate if the upload file has a file selected.
    if(!validated()){
            jQuery('#lblResult').text("Please choose a file!");
            return;
    }
    
    //Dialog variables
    var dlgTitle;
    var dlgMsg;
    var dlgWidth;
    var dlgHeight;
    dlgTitle = "Uploading Your File...";
    dlgMsg = "<br />Please wait whilst your file is being uploaded<br /><br />Please do not close your browser window.";
    dlgHeight = 300;
    dlgWidth = 500;
        
    //Create Dialog box.
   if (currentDlg == null) {
           currentDlg = SP.UI.ModalDialog.showWaitScreenWithNoClose(dlgTitle, dlgMsg, dlgHeight, dlgWidth);
   }

   //Get File        
   var file = jQuery("#AttachmentUploadField")[0].files[0];

   //Upload the file
   uploadFile(file);
};

The code above doesn’t do much, it first calls a validation method (yet to write) to check if the user has selected a file to upload. If not, it updates the lblresult text to inform the user they must upload a file. If validation passes, we set up a dialog box to inform the user that the upload is taking place (it will take a long time for a 2GB file). Next we display the dialog to the user, grab the file and passes it to the uploadFile method.

The validated method

var validated = function(){
   var file = jQuery("#AttachmentUploadField")[0].files[0];
   if(file == null){        
          return false;
  }
  else{
    return true;
  }
};

The validated method grabs the Upload control and checks if there is a file available. If it is there then the function returns true, else it returns false.

The uploadFile method

var uploadFile = function (file) {
    proposalSiteUrl = jQuery('#ProposalSiteUrl option:selected').val();        
        
    if (proposalSiteUrl == _spPageContextInfo.webAbsoluteUrl) {
        uploadFileLocal(file);
    }
    else{
        uploadFileCrossSite(file, proposalSiteUrl);
    }
};

Our upload file still isn’t the actual code that uploads the file to the given URL. Here I’m checking if the site I’m uploading to is the same Site Collection I’m running my upload page from or not. If it is, then I can do a very simple upload (uploadFIleLocal). If I need to upload to a different Site Collection, then I will be required to perform a cross site call (uploadFileCrossSite).

The uploadFileLocal method

To explain this, I am going to show this method in bits and explain each section.

var uploadFileLocal = function (file) {
      var digest = jQuery("#__REQUESTDIGEST").val();
      var webUrl = _spPageContextInfo.webAbsoluteUrl;
      var libraryName = "Documents";

      var reader = new FileReader();
      var arrayBuffer;

First we need to set all our variables. The digest request is important, it is a client side token to validate post backs to SharePoint to prevent attacks where the user might be tricked into posting data back to the server. It is unique to a user and a site. It is only valid for a limited time. The first line gets the current request digest from the page. This digest is required in our ajax post request when we upload the file. The other variables are pretty standard, web url, the library name to upload to, a file reader and array buffer.

        reader.onload = function (e) {
            arrayBuffer = reader.result;

            url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
               "@TargetLibrary='" + libraryName + "'" +
               "&@TargetFileName='" + file.name + "'";

            //JQuery Ajax call here

       };
       reader.readAsArrayBuffer(file);
  };

The next part of the uploadFileLocal function that continues straight after var arrayBuffer. Our reader needs to read in the file, and on the onload event, we need to prepare our REST url to past into the JQuery ajax request. (The JQuery Ajax call is shown in the next piece of code). The reader.onload function doesn’t fire until after reader.readAsArrayBuffer(file) is called. The REST url uses two parameters @TargetLibrary and @TargetFileName, the actual file data is put into arraryBuffer and used in the JQuery Ajax call.

        
jQuery.ajax({
        url: url,
       type: "POST",
       data: arrayBuffer,
    headers: {
            "Accept": "application/json; odata=verbose",
            "X-RequestDigest": digest
            },    
contentType: "application/json;odata=verbose",
processData: false,
    success: function () {
            jQuery('#lblResult').text("Successfully uploaded file locally.");
            if (currentDlg != null) {
                        currentDlg.close();
            }
         },
     error: function (arr, error) {
               jQuery('#lblResult').text("Error uploading file locally.");
               if (currentDlg != null) {
                      currentDlg.close();
               }
          }
});

Insert the above code in the previous snippet where the comment //JQuery Ajax call here is. This is a typical Ajax call using JQuery. http://api.jquery.com/jquery.ajax/ We pass in our REST url, state that it’s a POST request, and set the data to our file arrayBuffer. Our headers show that the call is a json call, and here we pass in the request digest we obtained in the very first variable of the uploadfileLocal function. The contentType and processData are standard for this type of call. Lastly I have created a success and error callbacks, with inline functions. The functions are similar, both updating the lblResult text with a success or fail message, and then close the dialog that is on show to the user. In the error function we could use the error variable to obtain the actual error message and display it to the user.

This concludes uploading a file to the same site. However if you try to use the same code to upload to a different site it will fail. The reason is all to do with the digest request. As I stated earlier, this is a client side token that is unique to the user and site. Therefore when you try to access a different site to what you are on, you will be passing an invalid digest request. You first need to obtain the digest request for the other site before you can upload/post data to it. Please note: The user will need contribute access to the site to be able to upload a file.

The uploadFileCrossSite method

var uploadFileCrossSite = function (file, webUrl) {
        url = webUrl + "/_api/contextinfo";
        jQuery.ajax({
            url: url,
            type: "POST",
            headers: {
                "Accept": "application/json; odata=verbose"
            },
            contentType: "application/json;odata=verbose",
            success: function (data) {
               var digest = data.d.GetContextWebInformation.FormDigestValue;
                var libraryName = "Documents";

                var reader = new FileReader();
                var arrayBuffer;

                reader.onload = function (e) {
                    arrayBuffer = reader.result;

                    url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
                       "@TargetLibrary='" + libraryName + "'" +
                       "&@TargetFileName='" + file.name + "'";

                    jQuery.ajax({
                        url: url,
                        type: "POST",
                        data: arrayBuffer,
                        headers: {
                            "Accept": "application/json; odata=verbose",
                            "X-RequestDigest": digest
                        },
                        contentType: "application/json;odata=verbose",
                        processData: false,
                        success: function () {
                        jQuery('#lblResult').text("Successfully uploaded file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }
                        },
                        error: function () {
                          jQuery('#lblResult').text("Error uploading file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }

                        }
                    });
                };

                reader.readAsArrayBuffer(file);

            },
       error: function () {
            jQuery('#lblResult').text("Error accessing other site.");
               if (currentDlg != null) {
                   currentDlg.close();
              }
          }
      });
 };

The uploadFileCrossSite code I have shown as whole, mainly because I have explained most of it in the uploadFileLocal function above. The lines I’ve highlighted 1-11 is a JQuery Ajax call into the target site. By requesting the context info, we can obtain the request digest of the target site in the success handler, data.d.GetContextWebInformation.FormDigestValue. We can then use this value to pass in as the digest to upload the file to the target site. The code to upload the file now is identical as before, obtaining the reader, calling the onload event, then on the success of the Ajax request we update the lblResult with a success message and close the dialog displayed.

The final error:function() call lines 53-59 highlighted at the bottom of the code is the error callback if there is an error contacting the target site for the request digest.

I can now test the form, I’ve selected my second team site, and selected a document DemoWordDoc3.docx to upload. I’ve then clicked the Upload button.

The file dialog appears in the centre of the screen so the user knows something is happening.

When the file has been uploaded, the user gets the message that it has successfully uploaded.

If I check out my Team Site 2, you can see that the DemoWordDoc3 is in the Documents library.

You can obtain my upload page from my OneDrive. To get into your SharePoint site, it is easiest just to copy it in using SharePoint Designer.

http://1drv.ms/1tcIDIQ

Advertisements