Upload action

From Sense/Net Wiki
Jump to: navigation, search
  •  
  •  
  •  
  •  
  • 100%
  • 6.2.1
  • Enterprise
  • Community
  • Planned

Overview

Upload action
Uploading files to Sense/Net ECM Content Repository from a web browser or a third party application can be done using the Upload action. The upload functionality consists of two features: a page where users can select and upload files from their local file system, and a server handler (implemented OData custom action) that accepts and stores the uploaded files in the Sense/Net Content Repository. This article describes how the upload action works and how to use it from a client application, therefore it is also guide when integrating custom upload controls into Sense/Net.

In case you are working with large files, regularly updating those can be slower than uploading new ones. In this case please consider using the FILESTREAM feature.

Details

The upload action is implemented as a custom OData action. The process consists of two parts: you send an initial create request to the server with just enough data to create the content (e.g. name and type) and then in the subsequent request(s) you send the actual binary in one round or in chunks.

For more information about the specifics of each request, please check the Built-in Upload action article.

Initial request

The first request is a create request that tells the system whether it should create a new file or use an existing one, and whether it should start a chunk upload process or not. The latter means if you want to upload the binary of a file in more parts - usually if the file is bigger than a configured value.

Subsequent requests

The first request returns with an upload token that contains essential information for the upload process. You have to pass this data to subsequent requests without modification. If you declared in the first request that this will be a chunk upload, you have to specify the offset (Content-Range header) and the actual binary chunk in subsequent requests - otherwise you'll post the whole file in one round.

Upload whole files instead of chunks

If the files are small, you may upload the whole file in one round instead of chunks. In the initial request you can set the UseChunk parameter to false and send the whole file in the next request. It is possible to avoid the initial request. In this case you have to send the whole file and the necessary parameters according to the second request specification (see the OData action article for more info). The chunk token in this case needs to be the following:

0*0*False

Upload text file from Javascript

It is possible to create or modify a text file using the Upload action if you have the text in Javascript. See an example below.

Content versioning

Depending on the versioning mode in the particular folder or list where you want to upload files and the actual state of existing files, the versioning behavior of the uploaded files can be different. The following sections describe whether new versions will be created or not for the uploaded files if they already exist. In case of new files there is not much to tell: the file will be created with the default version number determined by the versioning and approving settings (0.1 draft or 1.0 approved, etc.).

No versioning

If the file already exists, it will be overwritten - except if it is stated otherwise using the Overwrite parameter of the first request. See the Built-in Upload action for more details.

Major versioning

If the file already exists, a new version will be created with the next major number - or, if the Overwrite parameter was false, a completely new file with a similar name.

Major and minor versioning

A new minor version will be created - again, you can modify this behavior using the Overwrite parameter.

If the existing content is checked out by the current user, that version will be overwritten by the uploaded file and you will have to check in the file manually to make it accessible for others. If the content is checked out by another user, the file will not be uploaded.

Interrupted uploads

If the upload process was interrupted, the database may contain a partially uploaded file. If the file existed before the upload operation, you can simply revert to the previous version by choosing the Undo changes action. Partially uploaded new files can only be deleted. If you try to upload the file again, the upload process will be restarted and the whole file will be uploaded again from the beginning. It is only possible to continue a previously interrupted upload process if you have the chunk token received from the first request and know the exact position where the upload process stopped.

Starting with version 6.3.1 Patch 4 Sense/Net ECM offers a possibility to resume a previously interrupted upload process, and developers may build a custom UI above our API. Please read the following article about the details.

Custom upload control

The client side of the upload functionality in Sense/Net ECMS is implemented using the jQuery File Upload component. The current version of the modul can be found here:

  • /Root/Global/scripts/jquery/plugins/fileupload

You may employ a custom upload control using the upload action described in this article or customize the built-in one. The built-in control for the upload functionality can be found here:

  • /Root/System/SystemPlugins/Portlets/IntraUploadDialog.ascx

This file contains the necessary JavaScript code for managing uploads that you can customize. You may set the maximum size of the uploaded file chunks in this file by modifying the maxChunkSize variable (in bytes). If you plan to work with large files, it is advisable to set this variable to a really big value - even tens of megabytes.

Custom upload action

If you as a developer want to customize or extend the behavior of the upload action you may do so by creating a custom upload action and overriding some or all of the virtual methods of the built-in Upload action described above. Only make sure that you use the Chunk save API of Sense/Net to save files into the Content Repository in chunks instead of keeping whole files in the memory.

Example/Tutorials

Upload text to a file binary

The following example describes how can you upload a simple text file from the browser (or modify an existing content), if you do not have the file in the file system but in a textbox or JavaScript variable in the browser instead. The example code below upload a settings file to the (settings) folder under a workspace.

$.ajax({
    url: '/OData.svc/Root/Sites/Default_Site/workspaces/Project/budapestprojectworkspace(\'(settings)\')/Upload',
    type: 'POST',
    data: {
        "ContentType": "Settings",
        "FileName": "MyCustom.settings",
        "Overwrite": true,
        "UseChunk": false,
        "PropertyName": "Binary",
        "FileText": " *** file text data ***"
    },
    success: function (data) {
        //...
    },
    error: function (data) {
        //...
    }
});

Upload a file in chunks from a console application

From version 6.5.4 there is a Client library available that lets you upload files from a console app using a C# API in a single line. It can replace the whole functionality of the example below.

The following example source code demonstrates how can you send the appropriate Upload requests to the portal to upload a file. Please note that this is a sample code and in a real-life application chunks should be a lot bigger than in this example to minimize the number of requests.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
 
namespace UploadTest
{
    class Program
    {
        private static readonly string ODATA_URL = "http://localhost/OData.svc/workspaces/Project/budapestprojectworkspace('Document_Library')/Upload";
        private static readonly int CHUNK_SIZE = 4096;
        private static readonly string FILENAME = "SampleDocument.docx";
 
        static void Main(string[] args)
        {
            var fileInfo = new FileInfo(FILENAME);
            var fileLength = fileInfo.Length;
 
            var myReq = GetInitWebRequest(fileLength);
            string token;
 
            //send initial request
            var wr = myReq.GetResponse();
            using (var stream = wr.GetResponseStream())
            {
                using (var reader = new StreamReader(stream))
                {
                    token = reader.ReadToEnd();
                }
            }
 
            var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
            var trailer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
 
            //open file and send subsequent requests
            using (var fileStream = new FileStream(fileInfo.Name, FileMode.Open, FileAccess.Read))
            {
                var buffer = new byte[CHUNK_SIZE];
                int bytesRead;
                var start = 0;
 
                while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                {
                    //get the request object for the actual chunk
                    myReq = GetChunkWebRequest(fileLength, fileInfo.Name, token, boundary);
                    myReq.Headers.Set("Content-Range", string.Format("bytes {0}-{1}/{2}", start, start + bytesRead - 1, fileLength));
 
                    //write the chunk into the request stream
                    using (var reqStream = myReq.GetRequestStream())
                    {
                        reqStream.Write(buffer, 0, bytesRead);
                        reqStream.Write(trailer, 0, trailer.Length);
                    }
 
                    start += bytesRead;
 
                    //send the request
                    wr = myReq.GetResponse();
 
                    //optional: get the response (contains the content path, etc.)
                    using (var stream = wr.GetResponseStream())
                    {
                        using (var reader = new StreamReader(stream))
                        {
                            var response = reader.ReadToEnd();
                        }
                    }
                }
            }
        }
 
        private static WebRequest GetInitWebRequest(long fileLength)
        {
            var myReq = WebRequest.Create(new Uri(ODATA_URL + "?create=1"));
            myReq.Method = "POST";
            myReq.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes("builtin\\admin:admin")));
            myReq.ContentType = "application/x-www-form-urlencoded";
 
            var useChunk = true;
            var postData = string.Format("ContentType=File&FileName={0}&Overwrite=true&UseChunk={1}", FILENAME, useChunk);
            var postDataBytes = Encoding.ASCII.GetBytes(postData);
 
            myReq.ContentLength = postDataBytes.Length;
 
            using (var reqStream = myReq.GetRequestStream())
            {
                reqStream.Write(postDataBytes, 0, postDataBytes.Length);
            }
 
            return myReq;
        }
 
        private static WebRequest GetChunkWebRequest(long fileLength, string fileName, string token, string boundary)
        {
            var myReq = (HttpWebRequest)WebRequest.Create(new Uri(ODATA_URL));
 
            myReq.Method = "POST";
            myReq.ContentType = "multipart/form-data; boundary=" + boundary;
            myReq.KeepAlive = true;
 
            myReq.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes("builtin\\admin:admin")));
            myReq.Headers.Add("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
 
            var boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
            var formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
            var headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n Content-Type: application/octet-stream\r\n\r\n";
 
            var useChunk = true;
            var postValues = new NameValueCollection
                                 {
                                     {"ContentType", "File"},
                                     {"FileName", FILENAME},
                                     {"Overwrite", "true"},
                                     {"UseChunk", useChunk.ToString()},
                                     {"ChunkToken", token}
                                 };
 
            //we must not close the stream after this as we need to write 
            //the chunk into it in the caller method
            var reqStream = myReq.GetRequestStream();
 
            //write form data values
            foreach (string key in postValues.Keys)
            {
                reqStream.Write(boundarybytes, 0, boundarybytes.Length);
 
                var formitem = string.Format(formdataTemplate, key, postValues[key]);
                var formitembytes = Encoding.UTF8.GetBytes(formitem);
 
                reqStream.Write(formitembytes, 0, formitembytes.Length);
            }
 
            //write a boundary
            reqStream.Write(boundarybytes, 0, boundarybytes.Length);
 
            //write file name and content type
            var header = string.Format(headerTemplate, "files[]", fileName);
            var headerbytes = Encoding.UTF8.GetBytes(header);
 
            reqStream.Write(headerbytes, 0, headerbytes.Length);
 
            return myReq;
        }
    }
}

Related links

References