Verint | Telligent Community
Verint | Telligent Community
  • Site
  • User
  • Site
  • Search
  • User
Telligent Community 9.x
  • Verint Community
Telligent Community 9.x
Developer Training Uploading Files
  • Ask the Community
  • User Documentation
  • API Documentation
  • Manager Training
  • Developer Training
  • Tags
  • More
  • Cancel
  • New
  • +Getting Started
  • -External Integration
    • +External Authentication
    • -REST API
      • Authentication
      • Making Requests
      • Uploading Files
      • +REST SDK
      • REST Best Practices for Performance
    • Webhooks
  • +Plugins/Framework extension
  • +UI Customization

Uploading Files

[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 each parameter begins and ends. Once you've collected all the required parameters, add each one, and then the file itself, to the request data.

// 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++)
	{
		var boundary = Guid.NewGuid().ToString("N");

		// Create request
		var request = (HttpWebRequest)WebRequest.Create(url + "/api.ashx/v2/cfs/temporary.json");
		request.Headers.Add("Rest-User-Token", restUserToken);
		request.ContentType = "multipart/form-data; boundary=" + boundary;

		// Collect necessary request information
		var formData = new Dictionary<string, string>
		{
			{"UploadContextId", uploadContextId.ToString()},
			{"FileName", fileName},
			{"CurrentChunk", currentChunk.ToString()},
			{"TotalChunks", totalChunks.ToString()},
		};

		// Add data to the multi-part form
		var requestData = new StringBuilder();
		foreach (var item in formData)
		{
			requestData.Append($"--{boundary}\r\nContent-Disposition: form-data; name=\"{item.Key}\"\r\n\r\n{item.Value}\r\n");
		}

		// Add the file itself
		requestData.Append($"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{fileName}\"\r\nContent-Type: {fileContentType}\r\n\r\n");
		
		// TODO: Combine request data with file chunk
		// TODO: Make request and read response
	}
}

Once all the other parameters are added, read the chunk from the file and generate the full request binary data. Then using the helper methods, make the request and return the uploaded file url, making sure to handle errors.

// 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++)
	{
		var boundary = Guid.NewGuid().ToString("N");

		// Create request
		var request = (HttpWebRequest)WebRequest.Create(url + "/api.ashx/v2/cfs/temporary.json");
		request.Headers.Add("Rest-User-Token", restUserToken);
		request.ContentType = "multipart/form-data; boundary=" + boundary;

		// Collect necessary request information
		var formData = new Dictionary<string, string>
		{
			{"UploadContextId", uploadContextId.ToString()},
			{"FileName", fileName},
			{"CurrentChunk", currentChunk.ToString()},
			{"TotalChunks", totalChunks.ToString()},
		};

		// Add data to the multi-part form
		var requestData = new StringBuilder();
		foreach (var item in formData)
		{
			requestData.Append($"--{boundary}\r\nContent-Disposition: form-data; name=\"{item.Key}\"\r\n\r\n{item.Value}\r\n");
		}

		// Add the file itself
		requestData.Append($"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{fileName}\"\r\nContent-Type: {fileContentType}\r\n\r\n");

		// Prepare data chunk
		var startDataBytes = Encoding.UTF8.GetBytes(requestData.ToString());
		var chunk = rdr.ReadBytes(MAX_CHUNK_SIZE_BYTES);
		var endDataBytes = Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n");

		// Combine all the request data into one array
		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);

		try
		{
			// Make request and read results
			var response = SendPostRequest(request, bytesToSend);
			if (response?["Errors"] != null)
			{
				return string.Join("\r\n", response["Errors"]);
			}
			fileUrl = response?["UploadedFile"]?["DownloadUrl"];
		}
		catch (Exception ex)
		{
			return InvestigateError(ex);
		}
	}
}

Once you've transmitted all chunks successfully, you can use your upload context id and/or url in other requests.

return fileUrl;

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 embed the file we've uploaded 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, which requires the file url generated and returned by the upload response.

[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.

Full Sample

Attached is all the code used in this sample along with a basic ASP form for testing.

TelligentCommunityRESTFileUpload.zip

  • Share
  • History
  • More
  • Cancel
Related
Recommended
  • Telligent
  • Professional Services
  • Submit a Support Ticket
  • Become a Partner
  • Request a Demo
  • Contact Us

About
Privacy Policy
Terms of use
Copyright 2022 Verint, Inc.
Powered by Verint Community