[toc]
Telligent Community makes available a dedicated REST endpoint for file uploads which then provides a context identifier that can be referenced to attach or embed files when making other REST requests.
Setup
To make REST requests against Telligent Community, you must authenticate using either an API key or an OAuth client as described here. For this example we will be using an API key. We can set up a REST token now that we will use for each request. For more information, see Using an API Key and username to authenticate REST requests.
string YOUR_API_KEY, YOUR_API_KEY_NAME; string restUserToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{YOUR_API_KEY}:{YOUR_API_KEY_NAME}"));
To simplify the samples below, here is a helper method that completes the generic web tasks of taking a request object and data, finalizing it, making the request, and receiving and parsing the response:
private dynamic SendPostRequest(HttpWebRequest request, byte[] requestData) { // Set up the request request.Method = "POST"; // Add data to request request.ContentLength = requestData.Length; using (var requestStream = request.GetRequestStream()) { requestStream.Write(requestData, 0, requestData.Length); } // Make request using (var response = (HttpWebResponse)request.GetResponse()) { // Read results var stream = response.GetResponseStream(); using (var reader = new StreamReader(stream)) { // Deserialize response var responseData = reader.ReadToEnd(); var serializer = new JavaScriptSerializer(); return serializer.Deserialize<dynamic>(responseData); } } }
It's also helpful to be able to read errors from the REST responses generated by Telligent Community for troubleshooting purposes. Here is a sample method that will take an exception and extract errors (if any). First confirm if this is indeed an Internal Server Error generated by Telligent Community while processing the request, then read and parse the errors from the response.
private string InvestigateError(Exception ex) { // Investigate for any further error information var errorResponse = (ex as WebException)?.Response as HttpWebResponse; // Ensure this is an internal error from Telligent Community and not from another source if (errorResponse == null || errorResponse.StatusCode != HttpStatusCode.InternalServerError) return ex.Message; // Read the response string data; using (var reader = new StreamReader(errorResponse.GetResponseStream())) { data = reader.ReadToEnd(); } // Deserialize the response and check for errors var serializer = new JavaScriptSerializer(); var resp = serializer.Deserialize<dynamic>(data); return string.Join("\r\n", resp?["Errors"] ?? ex.Message); }
Upload a File
We will be using the Multiple File Upload endpoint. Let's break down the required parameters:
UploadContextId: an identifier that is used throughout the file upload process. This is user generated and needs to be unique, so generate a GUID.
ChunkNumber, TotalChunks: We need to transmit the file in chunks to avoid both request timeouts and size limitations. Chunking simply means splitting the file data into smaller parts and sending each part separately. Telligent Community's REST endpoint will handle combining the chunks back into one, as long as the UploadContextId, ChunkNumber, and TotalChunks are specified with each of the requests.
FileName: used as the name of the file that is created or added to as more upload requests are made with the UploadContextId.
First, set up your variables, including generating a GUID to use as UploadContextId. We use 15MB as our chunk size to strike a balance between request size and number of requests needed. The most important component here is the 'file' variable, from which we can calculate how many chunks we will need for the full operation. For this file we are using an HttpPostedFile such as one uploaded via an ASP file upload control.
// Input variables const int MAX_CHUNK_SIZE_BYTES = 15728640; var url = "YOUR_SITE_URL"; var uploadContextId = Guid.NewGuid(); // File variables HttpPostedFile file; var fileName = file.FileName; var fileContentType = file.ContentType; var fileDataStream = file.InputStream; // Calculate chunk information int totalChunks = (int) Math.Ceiling((double) fileDataStream.Length/MAX_CHUNK_SIZE_BYTES);
We will need to loop through each chunk and upload it. Set up the loop here.
// Open the file reader to read chunks using (var rdr = new BinaryReader(fileDataStream)) { // Send each chunk in succession for (var currentChunk = 0; currentChunk < totalChunks; currentChunk++) { // Chunk uploading code will go here } }
Generate the form data and other parameters, using the format for multipart/form-data requests. We need to use this content type instead of the default (application/x-www-form-urlencoded) because we are passing a file in the request body. The boundary specifies to the receiving server when a parameter begins and ends.
var boundary = Guid.NewGuid().ToString("N"); var sb = new StringBuilder(); // Add other data to the multi-part form var formData = new Dictionary<string, string> { {"UploadContextId", uploadContextId.ToString()}, {"FileName", fileName}, {"CurrentChunk", currentChunk.ToString()}, {"TotalChunks", totalChunks.ToString()}, }; foreach (var item in formData) { sb.Append($"--{boundary}\r\nContent-Disposition: form-data; name=\"{item.Key}\"\r\n\r\n{item.Value}\r\n"); } // Add the file itself sb.Append($"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{fileName}\"\r\nContent-Type: {fileContentType}\r\n\r\n");
Generate the actual file data chunk using the file reader and form data.
// Prepare data chunk var startDataBytes = Encoding.UTF8.GetBytes(sb.ToString()); var chunk = rdr.ReadBytes(MAX_CHUNK_SIZE_BYTES); var endDataBytes = Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n"); var bytesToSend = new byte[startDataBytes.Length + chunk.Length + endDataBytes.Length]; startDataBytes.CopyTo(bytesToSend, 0); chunk.CopyTo(bytesToSend, startDataBytes.Length); endDataBytes.CopyTo(bytesToSend, startDataBytes.Length + chunk.Length);
Create the web request and write the chunk data to the request stream.
// Create request var request = (HttpWebRequest) WebRequest.Create(url + "/api.ashx/v2/cfs/temporary.json"); request.Headers.Add("Rest-User-Token", restUserToken); request.Method = "POST"; try { // Add file data to request request.ContentType = "multipart/form-data; boundary=" + boundary; request.ContentLength = bytesToSend.Length; using (var requestStream = request.GetRequestStream()) { requestStream.Write(bytesToSend, 0, bytesToSend.Length); requestStream.Close(); }
Now we are finally ready to send the request and read the response.
// Make request and read results using (var response = (HttpWebResponse) request.GetResponse()) { var stream = response.GetResponseStream(); using (var reader = new StreamReader(stream)) { var responseData = reader.ReadToEnd(); var serializer = new JavaScriptSerializer(); var responseObject = serializer.Deserialize<dynamic>(responseData); if (responseObject?["Errors"] != null) { return string.Join("\r\n", responseObject["Errors"]); } } }
If errors occur, catch and investigate. (see above)
} catch (Exception ex) { return InvestigateError(ex); }
Once you've transmitted all chunks successfully, you can use your upload context id in other requests.
} } return $"File successfully uploaded: {uploadContextId}";
Change User Avatar Image
Now that we have the reference to the uploaded file, we can use that file in another request like User Avatar Update. The UploadContextId will signal to Telligent Community what file is to be used in the request.
First, create the web request with necessary headers.
var request = (HttpWebRequest)WebRequest.Create(url + $"/api.ashx/v2/users/{userId}/avatar.json"); request.Headers.Add("Rest-User-Token", restUserToken); request.ContentType = "application/x-www-form-urlencoded"; // Extra specification needed on UPDATE requests to Telligent Community request.Headers.Add("Rest-Method", "PUT");
Then collect the necessary parameters and generate the required query string format. Use the same UploadContextId that you generated in the file upload request. Then write the form data to the request stream.
var values = new Dictionary<string, string> { {"FileUploadContext", uploadContextId.ToString()}, {"FileName", fileName} };
Send the request, and if desired pull data out of the response for reference. Catch and investigate any errors.
try { // Add data to request var queryString = string.Join("&", values.Select(v => $"{v.Key}={v.Value}")); var bytesToSend = Encoding.UTF8.GetBytes(queryString); // Send request and check response var response = SendPostRequest(request, bytesToSend); return response?["AvatarUrl"]; } catch (Exception ex) { return InvestigateError(ex); }
If the request was successful, the new avatar image should be immediately visible once you refresh your site.
Embed a File in a Post
We can also use the url generated from the upload to embed a file in any Content that supports file embedding in the body. In this example we will be using the Blog Post Create endpoint. Use the standard embed syntax:
[View:FILE_URL:WIDTH:HEIGHT]
As above, create the web request with necessary headers.
var request = (HttpWebRequest)WebRequest.Create(url + $"/api.ashx/v2/blogs/{blogId}/posts.json"); request.Headers.Add("Rest-User-Token", restUserToken); request.ContentType = "application/x-www-form-urlencoded";
Then collect the necessary parameters and generate the required query string format. Use the same UploadContextId that you generated in the file upload request. Then write the form data to the request stream.
var values = new Dictionary<string, string> { {"Title", $"POST_TITLE {Guid.NewGuid()}"}, {"Body", $"POST_TEXT [View:{fileUrl}:300:300] POST_TEXT"} };
Send the request, and if desired pull data out of the response for reference. Catch and investigate any errors.
try { // Add data to request var queryString = string.Join("&", values.Select(v => $"{v.Key}={v.Value}")); var bytesToSend = Encoding.UTF8.GetBytes(queryString); // Send request and check response var response = SendPostRequest(request, bytesToSend); return response?["BlogPost"]?["Url"]; } catch (Exception ex) { return InvestigateError(ex); }
If the request was successful, the blog post with embedded image should now be visible on your Telligent Community site.
Cleaning Up Temporary Files
Telligent Community does eventually clean up old file upload contexts by deleting the id; this means the files will stay in any locations it was used on the site, but the context id will no longer be usable in future requests. The timing of this cleanup is configurable in MultipleFileUploadCleanupJob.