Sunday, June 3, 2012

Salesforce REST API File Upload

The next part of the Salesforce.com REST API I want to talk about is how to work with documents. In this article I will explain how to upload documents to a folder in Salesforce. Creating a document is similar to creating any other type of Salesforce object so you may want to refer back to my original article on this topic, although I will cover all the code in detail here.
To create a document in Salesforce we will need to post a JSON or XML representation of the document meta-data just like we would with any other Salesforce object, but for a document we will also need to send the binary data from the file. To do this we will send a multi-part MIME document in the body of the HTTP post. The first thing we are going to need is a class to hold the document meta data.

public class sfdcDocument
{
    public string Description { get; set; }
    public string Keywords { get; set; }
    public string FolderId { get; set; }
    public string Name { get; set; }
    public string Type { get; set; }

}

Most of these are self explanatory, but we do need to talk I little about FolderId. When you upload a document to Salesforce you must provide the ID of the folder you want to upload it to. There are ways of querying for folder names, but for the purposes of this demo we will hard code the Id. To get a FolderID log into Salesforce and select the Documents tab. If the tab is not show, click on the + at the end of the tabs and pick Documents from the next screen. From the Folders drop down select the folder you want to upload to and click Go. The next screen will show the contents of you folder and you will be able to get the ID from the URL. For example in “na9.salesforce.com/015?fcf=005E0000000V7Ep” the folder ID is 005E0000000V7Ep.

Now lets look at the code to upload a document.

var doc = new sfdcDocument();
doc.Name = "TestDocument";
doc.FolderId = "005E0000000V7Ep";

First we create a new sfdcDocument object and populate the required fields. You will need to replace the FolderID with the one for the folder you will use.
string boundary = "----" + DateTime.Now.Ticks.ToString("x");
This line is used to create a boundary string to separate the parts of the multi-part MIME message we are going to build. This can be any value that we are sure will not appear somewhere else in the message.
var uri = instanceURL + "/services/data/v24.0/sobjects/Document/";
var req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
req.Headers.Add("Authorization: OAuth " + accessToken);
req.ContentType = "multipart/form-data; boundary=" + boundary;
req.Method = "POST";
var os = req.GetRequestStream();

Now we can build the HttpWebRequest object. In the first line we create the URI for the document resource. The instanceURL variable is the url of your specific Salesforce instance. You will get this when you request an access token during the authentication process. Next we create the request object and set the Authorization header. The variable accessToken in the token you received during the authentication process.  The next line sets the content type for the message. For normal Salesforce object this would be json or xml, but in this case we specify a multpart type and pass the boundary string we created earlier. Finally we set the http method to POST and create a variable for the request stream.

// Add header for JSON part
string body = "";
body += "\r\n--" + boundary + "\r\n"; ;
body += "Content-Disposition: form-data; name='entity_document'\r\n";
body += "Content-Type: application/json\r\n\r\n";

// Add document object data in JSON
var ser = new JavaScriptSerializer();
body += ser.Serialize(doc);

The next step is to add the JSON encoded meta data for the document. We start building the message body by included in the boundary string to mark the start of the first message part. The next two lines are the header for this part of the request. Since we are sending the meta data in JSON format we set the content type to “application/json”. Finally we serialize our document object and added it to the request body.

// Add header for binary part 

body += "\r\n--" + boundary + "\r\n"; ; body += "Content-Disposition: form-data; name='Body'; filename='test.pdf'\r\n"; body += "Content-Type: binary/octet-stream\r\n\r\n";
Now we are ready to setup the binary part of the message. It starts with a header that is similar to the one on the JSON part. In the second line we specify the filename of the document. This filename doesn’t appear to be used anywhere since the actual document in Salesforce uses the Document Name for it’s filename, but the filename attribute must be present and it must contain a value. The third line specifies the content (MIME) type of the file. It is important that this is set correctly so that the file will open in the right program when you open it from Salesforce. An easy way to determine the correct content type is to manually load a file into Salesforce and check the MIME type that it assigns.


// Add header data to request
byte[] data = System.Text.Encoding.ASCII.GetBytes(body);
os.Write(data, 0, data.Length);

// Add file to reqeust
FileStream fileStream = new FileStream(@"c:\temp\test.pdf", FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
    os.Write(buffer, 0, bytesRead);
}
fileStream.Close();

Now we need to add everything to the request stream. The first part of this code writes all the header information to the request stream, and the second part writes the actual binary contents of the file to the stream.

// Add trailer
byte[] trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
os.Write(trailer, 0, trailer.Length);
os.Close();

To complete the request we add a trailer string to it, and then close the stream.

// Do the post and get the response.
WebResponse resp;

try
{
    resp = req.GetResponse();
}
catch (WebException ex)
{
    resp = ex.Response;
}

if (resp == null) return null;
var sr = new System.IO.StreamReader(resp.GetResponseStream());

Finally we send the request and get the response. If an error occurs with the request an exception will be thrown. Here we catch that exception and get the actual response from the Exception.Response property. This will normally contain some useful error messages generated by Salesforce. If we have done everything right we should get a response like this:

{
    "id":"015E0000000qhJeIAI",
    "errors":[],
    "success":true
}

The success property will tell you if the insert succeeded and the id property will contain the a unique id for the document that was created.

One important thing to note about doing a document insert is that it will always create a new document even if you insert a document with the same name as one already in the folder. Salesforce will create a unique name for each document you insert.

4 comments:

Unknown said...

Hello Dan,
thank you - very interesting, example, helpful to me. I'm a beginner, trying to understand REST API using php.
Is it possible to translate this REST API File Upload Example into PHP?
(I'm not a experienced programmer.)

can you help me?
thanks in advance,
adi

Trevor Daniel said...

brilliant! thanks!

Unknown said...

Hello Dan,

I tried this and was able to succeed to some extent but all files sent to sales force are corrupt I tried both the content type for mime its a pdf file.

"Content-Type: binary/octet-stream"
"Content-Type: application/pdf"

none of the above works fine any help is highly appreicated

Dan Boris said...

You definitely want to use binary/octet-stream. I just tested this code with v37.0 of the API and it worked fine. Be sure to read my post about Salesforce exceptions, it can help with troubleshooting API problems.

http://danlb.blogspot.com/2013/09/salesforce-rest-exceptions.html