Chunk Upload in EJ2 TypeScript File Upload

08 Sep 202621 minutes to read

The Uploader splits the large file into small chunks and transmits them to the server using AJAX. You can also pause, resume, and retry failed chunk uploads.

  • The chunk upload works in asynchronous upload only.
  • This feature is available from the Essential Studio® Vol 2, 2018 release.

To enable chunk upload, set the chunkSize option, which accepts the value in bytes.

import { Uploader } from '@syncfusion/ej2-inputs';

// initialize Uploader component
let uploadObject: Uploader = new Uploader({
    asyncSettings: {
        saveUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Save',
        removeUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Remove'
        // split the files into chunks of the size 102400 bytes
        chunkSize: 102400
    }
});

// render initialized Uploader
uploadObject.appendTo('#fileupload');
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Essential JS 2 Uploader</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Essential JS 2 Uploader Component" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-buttons/styles/material.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
</head>
<body>
    <div id='loader'>Loading....</div>
        <div id='container' class="fileupload">
            <input type="file" id="fileupload" name="UploadFiles"/>
        </div>
    </div>
</body>
</html>
#container {
  visibility: hidden;  
  margin: 0 auto;
  width: 400px;
}

#loader {
  color: #008cff;
  font-family: 'Helvetica Neue','calibiri';
  font-size: 14px;
  height: 40px;
  left: 45%;
  position: absolute;
  top: 45%;
  width: 30%;
}

The chunk upload functionality separates the selected files into data blobs, or chunks. These chunks are transmitted to the server using an AJAX request. The chunks are sent in sequential order, and the next chunk can be sent to the server according to the success of the previous chunk. If any chunk fails, the remaining chunks cannot be sent to the server. The chunkSuccess or chunkFailure event is triggered when a chunk is sent to the server successfully or fails. If all the chunks are sent to the server successfully, the Uploader’s success event is triggered.

Chunk upload will work when the selected file size is greater than the specified chunk size; otherwise, it uploads the files normally.

Additional configurations

To modify the chunk upload, the following options can be used.

  • RetryAfterDelay - If an error occurs while sending a chunk request, the operation is paused for 500 milliseconds (by default) before retrying. This can be achieved using the asyncSettings.retryAfterDelay property, which accepts the holding time interval in milliseconds.

  • RetryCount - Specifies the number of retry attempts performed when a chunk fails to upload. By default, the retry action is performed 3 times. If the file continuously fails to upload, the request is aborted and the Uploader’s failure event triggers.

The following sample specifies the chunk upload delay with 3000 milliseconds and the retry count is 5. The failure event is triggered as the wrong saveUrl is used.

import { Uploader } from '@syncfusion/ej2-inputs';

// initialize Uploader component
let uploadObject: Uploader = new Uploader({
    asyncSettings: {
        // provided the wrong url to showcase the chunk upload failure related properties.
        saveUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Save',
            removeUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Remove',
        // set chunk size for enable the chunk upload
        chunkSize: 102400,
        // set count for automatic retry when chunk upload failed
        retryCount: 5,
        // set time delay for automatic retry when chunk upload failed
        retryAfterDelay: 3000
    }
});

// render initialized Uploader
uploadObject.appendTo('#fileupload');
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Essential JS 2 Uploader</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Essential JS 2 Uploader Component" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-buttons/styles/material.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
</head>
<body>
    <div id='loader'>Loading....</div>
        <div id='container' class="fileupload">
            <input type="file" id="fileupload" name="UploadFiles"/>
        </div>
    </div>
</body>
</html>
#container {
  visibility: hidden;  
  margin: 0 auto;
  width: 400px;
}

#loader {
  color: #008cff;
  font-family: 'Helvetica Neue','calibiri';
  font-size: 14px;
  height: 40px;
  left: 45%;
  position: absolute;
  top: 45%;
  width: 30%;
}

Resumable upload

Allows you to resume an upload operation after a network failure or a manual interruption (pause) of the upload. You can perform pause and resume upload actions using the public methods (pause and resume) and UI interaction. The pause icon appears in the UI after the upload begins.

The pause and resume features are available only when chunk upload is enabled.

import { Uploader } from '@syncfusion/ej2-inputs';

// initialize Uploader component
let uploadObject: Uploader = new Uploader ({
    asyncSettings: {
        saveUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Save',
            removeUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Remove',
        // set chunk size for enable the chunk upload
        chunkSize: 102400
    }
});

// render initialized Uploader
uploadObject.appendTo('#fileupload');
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Essential JS 2 Uploader</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Essential JS 2 Uploader Component" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-buttons/styles/material.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
</head>
<body>
    <div id='loader'>Loading....</div>
        <div id='container' class="fileupload">
            <input type="file" id="fileupload" name="UploadFiles"/>
        </div>
    </div>
</body>
</html>
#container {
  visibility: hidden;  
  margin: 0 auto;
  width: 400px;
}

#loader {
  color: #008cff;
  font-family: 'Helvetica Neue','calibiri';
  font-size: 14px;
  height: 40px;
  left: 45%;
  position: absolute;
  top: 45%;
  width: 30%;
}

Cancel upload

The Uploader component allows you to cancel an in-progress upload. This can be achieved by clicking the cancel icon or using the cancel method. The canceling event is fired whenever the file upload request is canceled. When an upload request is canceled, the partially uploaded file is removed from the server.

When the request fails, the pause icon changes to a retry icon. Clicking the retry icon resends the failed chunk request to the server, and the upload resumes from where it failed. You can retry a canceled upload request using the retry UI or the retry method. However, in this case, the file upload restarts from the beginning.

The following example explains chunk upload with cancel support.

import { Uploader } from '@syncfusion/ej2-inputs';

// initialize Uploader component
let uploadObject: Uploader = new Uploader({
    asyncSettings: {
        saveUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Save',
        removeUrl: 'https://services.syncfusion.com/js/production/api/FileUploader/Remove'
        // set chunk size for enable the chunk upload
        chunkSize: 102400
    }
});

// render initialized Uploader
uploadObject.appendTo('#fileupload');
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Essential JS 2 Uploader</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Essential JS 2 Uploader Component" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/34.2.2/ej2-buttons/styles/material.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
</head>
<body>
    <div id='loader'>Loading....</div>
        <div id='container' class="fileupload">
            <input type="file" id="fileupload" name="UploadFiles"/>
        </div>
    </div>
</body>
</html>
#container {
  visibility: hidden;  
  margin: 0 auto;
  width: 400px;
}

#loader {
  color: #008cff;
  font-family: 'Helvetica Neue','calibiri';
  font-size: 14px;
  height: 40px;
  left: 45%;
  position: absolute;
  top: 45%;
  width: 30%;
}

The retry action has different working behavior for chunk upload and default upload.

  • Chunk upload - Retries to upload the failed request where it is failed previously.
  • Default upload - Retries to upload the failed file again from initial.

Server-Side configurations

The server-side implementation entirely depends on the application requirements and logic. The following code snippet provides the server-side logic to handle the chunk upload using the uploader components.

The chunk-index and total-chunk values are accessible through the form data using Request.Form, which retrieves these details from the incoming request.

  • chunk-index - Indicates the index of the current chunk being received.
  • total-chunk - Represents the total number of chunks for the file being uploaded.
public string uploads = Path.Combine(Directory.GetCurrentDirectory(), "Uploaded Files"); // Set your desired upload directory path

public async Task<IActionResult> Save(IFormFile UploadFiles)
{
    try
    {
        if (UploadFiles.Length > 0)
        {
            var fileName = UploadFiles.FileName;

            // Create upload directory if it doesn't exist
            if (!Directory.Exists(uploads))
            {
                Directory.CreateDirectory(uploads);
            }

            if (UploadFiles.ContentType == "application/octet-stream") //Handle chunk upload
            {
                // Fetch chunk-index and total-chunk from form data
                var chunkIndex = Request.Form["chunk-index"];
                var totalChunk = Request.Form["total-chunk"];

                // Path to save the chunk files with .part extension
                var tempFilePath = Path.Combine(uploads, fileName + ".part");

                using (var fileStream = new FileStream(tempFilePath, chunkIndex == "0" ? FileMode.Create : FileMode.Append))
                {
                    await UploadFiles.CopyToAsync(fileStream);
                }

                // If all chunks are uploaded, move the file to the final destination
                if (Convert.ToInt32(chunkIndex) == Convert.ToInt32(totalChunk) - 1)
                {
                    var finalFilePath = Path.Combine(uploads, fileName);

                    // Move the .part file to the final destination without the .part extension
                    System.IO.File.Move(tempFilePath, finalFilePath);

                    return Ok(new { status = "File uploaded successfully" });
                }

                return Ok(new { status = "Chunk uploaded successfully" });
            }
            else //Handle normal upload
            {
                var filePath = Path.Combine(uploads, fileName);

                using (var fileStream = new FileStream(filePath, FileMode.Create))
                {
                    await UploadFiles.CopyToAsync(fileStream);
                }

                return Ok(new { status = "File uploaded successfully" });
            }
        }

        return BadRequest(new { status = "No file to upload" });
    }
    catch (Exception ex)
    {
        return StatusCode(500, new { status = "Error", message = ex.Message });
    }
}

// Method to handle file removal (optional if needed)
public async Task<IActionResult> Remove(string UploadFiles)
{
    try
    {
        var filePath = Path.Combine(uploads, UploadFiles);

        if (System.IO.File.Exists(filePath))
        {
            System.IO.File.Delete(filePath);
            return Ok(new { status = "File deleted successfully" });
        }
        else
        {
            return NotFound(new { status = "File not found" });
        }
    }
    catch (Exception ex)
    {
        return StatusCode(500, new { status = "Error", message = ex.Message });
    }
}

You can also explore JavaScript File Upload feature tour page for its groundbreaking features. You can also explore our JavaScript File Upload example to understand how to browse the files which you want to upload to the server.