[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.
var restUserToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{"YOUR_API_KEY"}:{"YOUR_API_KEY_NAME"}"));
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).
private string InvestigateError(Exception ex) {
First confirm if this is indeed an exception generated by some error while processing the request on the server, or some other reason.
var errorResponse = (ex as WebException)?.Response as HttpWebResponse; if (errorResponse != null && errorResponse.StatusCode == HttpStatusCode.InternalServerError) {
If Telligent Community returns an Internal Server Error, we can dig deeper to find what messages were returned.
var response = errorResponse.GetResponseStream(); if (response != null) { string data; using (var reader = new StreamReader(response)) { data = reader.ReadToEnd(); } if (!string.IsNullOrEmpty(data)) {
Assuming the class structure is correctly populated, we can pull out the errors returned by Telligent Community.
// Deserialize the response and check for errors var serializer = new JavaScriptSerializer(); var resp = serializer.Deserialize<dynamic>(data); if (resp?["Errors"] != null) { return string.Join("\r\n", resp["Errors"]); }
Close out the method, and if no specific errors were found, you can return something else to help you find the problem.
} } } return ex.Message; }
Upload a File
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. Thus it is important to use a context id that will not accidentally overlap with other possible file uploads.
First, set up your variables, including generating a GUID to use as upload context id.
const int MAX_CHUNK_SIZE_BYTES = 15728640; var url = "YOUR_SITE_URL"; var uploadContextId = Guid.NewGuid();
The most important component here is the 'file' variable, from which we can calculate how many chunks we will need for the full operation.
// Name file variables 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);
Begin looping operation for each chunk.
// 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++) {
Generate the form data and other parameters, using the format for "multipart/form-data" requests.
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 form data and generate the required query string format. Use the same UploadContextId that you generated in the file upload request.
// Add necessary information to request var values = new Dictionary<string, string> { { "FileUploadContext", uploadContextId.ToString()}, { "FileName", fileName} }; var queryString = string.Join("&", values.Select(v => $"{v.Key}={v.Value}"));
Then create the web request and write the form data to the request stream.
// Create request 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"; request.Method = "POST"; request.Headers.Add("Rest-Method", "PUT"); try { // Add data to request var bytesToSend = Encoding.UTF8.GetBytes(queryString); request.ContentLength = bytesToSend.Length; using (var requestStream = request.GetRequestStream()) { requestStream.Write(bytesToSend, 0, bytesToSend.Length); requestStream.Close(); }
Send the request, and if desired pull data out of the response for reference.
// Send request and check response 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); return responseObject; } }
Catch and investigate any errors. (see above)
} 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 file context to embed a file in a post body using the Blog Post Create endpoint. Use the standard embed syntax along with the UploadContextId:
[View:UPLOAD_CONTEXT_ID]
As above, create the form data in query string format.
// Create parameters var values = new Dictionary<string, string> { {"Title", "POST_TITLE"}, {"Body", $"POST_TEXT [View:{uploadContext}] POST_TEXT"} }; var queryString = string.Join("&", values.Select(v => $"{v.Key}={v.Value}"));
Generate the web request and write the form data to the request. This also requires the id of the blog you want to post to, with the requirement that the user represented by the API key has permission to post to that blog.
// Create request var request = (HttpWebRequest)WebRequest.Create(url + $"/api.ashx/v2/blogs/{blogId}/posts.json"); request.Headers.Add("Rest-User-Token", restUserToken); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; try { // Add data to request var bytesToSend = Encoding.UTF8.GetBytes(queryString); using (var requestStream = request.GetRequestStream()) { requestStream.Write(bytesToSend, 0, bytesToSend.Length); requestStream.Close(); }
Send the request, and if desired pull out data for reference.
// 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); return responseObject?["BlogPost"]?["Url"]; } }
Catch and investigate any errors. (see above)
} catch (Exception ex) { return InvestigateErrors(ex); }
If the request was successful, the blog post with embedded image should now be visible on your Telligent Community site.
Note
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.