Saturday, June 30, 2012

Salesforce REST API Read File

In my last post I showed how to upload a file to Salesforce, now lets look at how to read it back. This requires two steps, one to read the document object and a second step to read the actual file data.
Reading the document object is just like reading any other type of object in Salesforce. First we need a class to hold the document object.

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; }
    public string Body { get; set; }
}

Here is the code to retrieve the document object and de-serialize it.

var uri = instanceURL + "/services/data/v24.0/sobjects/Document/015E0000000qhId";

var req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
req.Headers.Add("Authorization: OAuth " + accessToken);
req.ContentType = "application/json";
req.Method = "GET";
  
var resp = req.GetResponse();
var sr = new System.IO.StreamReader(resp.GetResponseStream());
var result = sr.ReadToEnd();

// Convert the JSON response into a token object
JavaScriptSerializer ser = new JavaScriptSerializer();
sfdcDocument doc;
 
doc = ser.Deserialize<sfdcDocument>(result);


First we create the REST URI needed to retrieve the document. We are using the simplest method of reading the document here, by just specifying the id of the document we want to read. There are ways to query by filename but I will cover that at another time. Now we make an HTTP get request, setting the accesstoken to the value we got during the authentication process, then read the response and finally de-serialize it.

The document object contains a field called Body which contains the URI used to retrieve the actual binary file data. Here is the piece of code needed to do that.

System.Net.WebClient client = new System.Net.WebClient();
client.Headers.Add("Authorization: OAuth " + accessToken);
client.DownloadFile(instanceURL + doc.Body, @"c:\temp2\" + doc.Name );


Here we use a WebClient to simplify reading the file. As always we add the access token, then execute DownloadFile using the instanceURL you got along with the access token, and the Body property of the document to specify the whole URI. We can also use the Name property of the document to get the original filename for the document and save it using the same document name.

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.