HOWTO: Properly and Securely Enable File Upload Feature in WebDocumentViewer


IMPORTANT: Disable if not using

First point is this: if you are not planning on using the File Upload feature in WebDocumentViewer (WDV) you should follow best practices and completely disable the feature by handling the FileUpload event server side and explicitly setting e.Cancel = true;

Please see: HOWTO: Completely Disable Upload Feature in WebDocumentViewer

Upload files with Web Document Viewer

Web Document Viewer (WDV) supports uploading files on a server. It can be done using WDV JavaScript API or WDV UI.

Enabling file upload

File upload is disabled by default. By setting an 'upload' configuration section to any value, including the 'empty object,' you can enable it.

Minimal config to enable upload with default parameters and uploading to FileUploads directory:

NOTE: The uploadpath value from client side should be verified in the FileUpload event handler server side. Just accepting user input for directories or file names is not a safe or secure practice

var viewer = new Atalasoft.Controls.WebDocumentViewer({
    //... other viewer settings
    upload: { uploadpath: '/FileUploads'}
});

Config to enable upload with specified upload path

var viewer = new Atalasoft.Controls.WebDocumentViewer({
    //... other viewer settings

    upload: {
        uploadpath: 'folder\\subfolder'
    },

    //...
});

Using this section, you not only enable/disable upload feature but also configure it, to perform files filtering for upload on a client-side. Files can be filtered by size, extension or MIME-type. The viewer performs minimal filetypes checks, and for best practice/secure file filtering the server-side API should be used. (Note the preceding link was for use in .NET Framework, for .NET 6/ 7/ 8, use WebDocumentViewerCallbacks API)

There are also several flags that allow a developer to enable or disable multiple files upload using WDV UI which includes configuration of maximum active parallel file upload requests; to enable or disable for an end-user possibility to add files for upload using drag-and-drop mechanism.

The last thing is 'enabled' flag. It allows to the developer disable upload feature even with if upload section is presented in WDV config.

NOTE: client side config and enabling/disabling is a convenience - any secure implementation of file upload must inspect/verify all user input on the server side API.

Config section with all flags mentioned

var viewer = new Atalasoft.Controls.WebDocumentViewer({
    //... other viewer settings
    upload: {
        enabled: false, // disable configured upload
        uploadpath: 'Upload/Viewer', // path where upload files should be saved
        allowedfiletypes: '.jpg,image/tiff', // Allow to upload files with 'jpg' extension and 'image/tiff' MIME-type
        allowedmaxfilesize: 10 * 1024 * 1024, // Allow to upload files not bigger than 10 MB
        allowmultiplefiles: true, // Allow to perform upload of several files simultaneously through UI
        allowdragdrop: true, // Allow to add files for upload using Drag-and-Drop
        filesuploadconcurrency: 2 // Only two files upload AJAX requests can run simultaneously
        closeuiafterupload: false // Control will require user action to close it after upload.
    }

});

Upload API

WDV provides API to perform files upload programmatically, this includes methods and events on both client and server APIs.

Client-side JS API

The client-side API for upload includes new methods to actually upload files - uploadFile and uploadFiles. These methods only upload files to the specified folder without any additional filtering from viewer config. To perform this filtering before the upload, a new method was added filterFilesForUpload. By separating these two operations, the developer can collect all files for upload and validate them if needed, before sending any requests and upload operation start.

To track the upload operation status and control it new events were introduced: fileaddedtoupload, uploadstarted, uploadfinished, fileuploadstarted, fileuploading, fileuploadfinished, fileuploaderror. All these events can be used for additional end-user notification about process progress and gives an ability to abort operation at any moment.

This feature also supports server requests customization from beforehandlerrequest event, new type for such requests is a fileupload type.

Clientside events for file upload

Clientside Functions for File Upload

  • filterFilesForUpload(files, filteredFilesopt, callbackopt) -> {Array.<File>}
    Filters files for upload using the settings from config upload section.

    Filters files that should be uploaded using the settings from config upload section. This includes filtering by size, by type and even by name in order to find out files for upload that have same names. It can be useful, because all events in WDV related to upload use filename as a key, thus you can find duplicates and upload uch files in separate uploadFiles method calls.

    This method is fully optional and even if some files failed to pass this filtering, they still can be uploaded to server

Server-side .NET events

On the server-side, the relevant events for file upload are in WebDocumentRequestHandler. These are FileUpload, FileUploaded and FileUploadResponseSend since this feature also supports response customization as others features do. The sample below is demonstrates how using these events and server-side files filtering and saving in custom destinations can be implemented

public class MyWdv : WebDocumentRequestHandler
{
    public MyWdv()
    {
        FileUpload += MyWdv_FileUpload;
        FileUploaded += MyWdv_FileUploaded;
    }

    private void MyWdv_FileUpload(object sender, FileUploadEventArgs e)
    {
        // Files that should be uploaded in wrong folder
        // or have incorrect extension should be rejected
        if (e.SaveFolder.Contains("StopWord") ||
            e.FileName.EndsWith(".exe"))
            e.Cancel = true;


        // Some files, for instance, we want to save in MemoryStream to save to database 
        // for example and not on file system.

        if (e.SaveFolder.Contains("database") {
            e.DestinationStream = new MemoryStream();
        } else {
            // OK we're NOT sending to database so we will do the file stream here
            // If you want, you can set e.DestinationStream to a FileStream right here

             string newFileName = Guid.NewGuid().ToString() + "_" + e.FileName;
            // this will be passed back to the client as the relative file path to the uploaded file
            // they can access it as the filepath  in the fileuploadfinished event client-side
            e.DestinationName =  "/uploads/" + newFileName;

            // FileStream needs a full path  - in .NET framework ashx handler we get this from HttpContext.Current.Request.MapPath
            string fullPath = Path.Combine(HttpContext.Current.Request.MapPath("/uploads/"), newFileName );

            // IMPORTANT: we just open and set it here
            // It will be written to and then the FileUploaded event will fire and you can access it then if you need to further
            // but if all you neeeded was to redirect the file you're done
            e.DestinationStream = new FileStream(fullPath, FileMode.Create FileAccess.ReadWrite, FileShare.Read);
    }

    private void MyWdv_FileUploaded(object sender, FileUploadedEventArgs e)
    {
        // This is how you can take the incoming and write to a file
        if (e.Destination.GetType() == typeof(MemoryStream))
        {
            // remember the stream may not be "rewound" so do that first
            e.DestinationStream.Seek(0, SeekOrigin.Begin);
            // your code here to shove e.DestinationStream (a MemoryStream) to a web servce?
            // or perhaps call  e.DestinationStream.ToArray() for a byte[] you wil send to a database?
            // NOTE you can also set e.DestinationName from here
            // so for instance you have code that takes the file and saves it to a database and then returns the record ID back to the client side
            e.DestinationName = YourCodeToWriteToDbAndReturnRecordId( e.DestinationStream.ToArray() );
        }

        //Free memory if needed
        e.DestinationStream?.Dispose();
    }
}

Again, please note that if you are not using the file upload feature it is best practice to explicitly disable it. Please see:

HOWTO: Completely Disable Upload Feature in WebDocumentViewer

Upload UI

On the UI side, when upload is enabled, a new button appears on a WDV toolbar in the top-left corner. Using this button end user can upload files through UI. Also, new API methods became available to the developer.

Toolbar upload button

The default scenario with file upload is to perform upload using only open file dialog from a browser. The open file dialog behavior can be customized by configuration properties config.upload.allowmultiplefiles and config.upload.allowedfiletypes.

Upload control

Upload control is a new container for show upload UI and upload progress to end users, it also provides a possibility to handle files drag-and-drop operation in the case when WDV is configured to support it.

Single file

When a web application is set up to perform an upload of a single file only, then when the user finished selecting files, WDV control is replaced by an upload control, as demonstrated below. This control shows upload progress and provides an ability to cancel the operation at any time, by clicking Cancel button or any thumbnail in Web Document Thumbnailer (WDT).

Single file uploading

Multiple files

In a case when the application is set up to support multiple files upload, the upload control represents a table with files ready to upload brief information about them. It also provides a possibility to cancel upload for each file individually, even if upload operation has started.

Multiple files uploading

Drag-and-drop

When the developer enables drag-and-drop support for WDV upload feature, then upload control changes its behavior. First of all it starts to support drop files into it, and even perform some checkings to notify user whether or not current dragdata can be used for upload. The other change is a new state, where upload control shows nothing except the notification to drop files into it. Of course the end-user still can add files using open file dialog by open it on click "Add.." button.

Drag-and-drop file for upload

UI customization

Upload control supports theming using jQuery-UI themes like also a other viewer parts, but also it provides set of CSS classes to customize it more accurately. Below is the list of using CSS classes for upload control customization:

/* Buttons related classes. */
.atala-upload-buttons /* Represents the class for internal buttons container. */
.atala-upload-button /* Represents the base class for upload buttons Add, Ok, Cancel. */
.atala-upload-close-button /* Represents the class for Close button. */
.atala-upload-cancel-button /* Represents the class for Cancel button. */
.atala-upload-ok-button /* Represents the class for OK button. */
.atala-upload-add-button /* Represents the  class for Add button. */

/* Drag-and-Drop related classes. */
.atala-upload-drag-and-drop /* Represents the class for span element in D&D message. */
.atala-upload-drag-and-drop-image /* Represents the class for image element in D&D message. */

/* Upload progress classes. */
.atala-upload-progress-file /* Represents the class for span element in file upload progress message for single file only. */
.atala-upload-progress-file-image /* Represents the class for image elemen in file upload progress mesage for single file only. */

/* Control elements structure classes. */
.atala-upload-control /* Represents the class for top-level element in upload control. */
.atala-upload-flex-area /* Represents the class for second-level control container with table layout. */
.atala-upload-flex-area-non-table /* Represents the class for second-level control container with non-table layout. */
.atala-upload-flex-buttons /* Represents the class for second-level upload control container. It's using for buttons container. */

/* File(-s) upload controls. */
.atala-upload-single-file /* Represents the class for single file upload container, including drag-and-drop. */
.atala-upload-single-file-no-drag-drop /* Represents the class for single file upload progress container. */
.atala-upload-multiple-files /* Represents the class for files table. */
.atala-upload-multiple-files-drag-and-drop /* Represents the class for drag-and-drop area when multiple files upload enabled. */

/* Files table classes. */
.atala-upload-files-header-row /* Represents the class for header row in files table. */
.atala-upload-files-header /* Represents the base class for header cell in files table. */
.atala-upload-files-header-name /* Represents the class for Name header cell in files table. */
.atala-upload-files-header-size /* Represents the class for Size header cell in files table. */
.atala-upload-files-header-status /* Represents the class for Status header cell in files table. */
.atala-upload-files-row /* Represents the class for file row in files table. */
.atala-upload-files-cell /* Represents the base class for file cell in files table. */
.atala-upload-files-cell-size /* Represents the class for files cell Size in files table. */
.atala-upload-files-cell-status /* Represents the class for files cell Status in files table. */
.atala-upload-files-cancel-button /* Represents the class for Cancel button in Status cells. */

/* Miscellaneous. */
.atala-upload-text-element /* Represents the shared class for all elements in upload control with text. */