Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Using plUpload to upload Files with ASP.NET


:P
On this page:

Uploading files to a Web Server is a common task in Web applications these days. All sorts of Web applications require accepting media content, images, or zip attachments with various pieces of data that the application needs. While HTML supports basic file uploading via the <input type="file"> control, let's face it: Uploading of anything with just the HTML file input control sucks. There are lots of problems with this approach, from the inability to select multiple files (in older browsers - HTML 5 can help for some modern browsers), from lack of any sort of progress information while your HTML upload is going, to the inability to potentially resume uploading an earlier upload.

In this article I demonstrate using plUpload to upload files to an ASP.NET server, using a single Image Uploader page as an example and provide a base HttpHandler (Westwind.plUploadHandler) implementation that greatly simplifies the process of handling uploaded files from the plUpload client.

Everything you need is available online:

The sad State of HTML/HTTP Uploads

It's sad to think that this important aspect of HTML and HTTP hasn't been addressed in the interim of Web proliferation. Instead developers still have to fumble with hacked together solutions to get files transferred to servers. So much so that many people simply give up and fall back to FTP… yuk!

For this reason, HTML Upload controls of various types have been around for a long time, both commercial and open source. These controls wrap the somewhat complex process of pushing data to the server in small chunks that can report progress while files are being sent. Until recently, getting access to local files wasn't possible through the HTML DOM APIs, so the only way to do chunked uploads was to use plug-ins like Flash or Silverlight to the job. Recently with various new HTML 5 APIs you can now gain access to user opened files using the FileReader API and you can select multiple files in order to open the files. However, support for the HTML5 FileReader API has been spotty and inconsistent among browsers until very recently and even now there are still quite a few implementation differences that it's not the best idea to screw around with this as part of an application as an all inclusive solution - you still need fallback to other avenues to upload like Flash or Silverlight when HTML5 is not available.

plUpload to the Rescue

One control that I've used in a number of projects is the plUpload component. plUpload provides a simple component along with a couple of user interface controls that can present a nice looking upload UI that you can just drop into an application in most cases. plUpload works by supporting chunked uploads to server via various plug-in technologies, depending on what the browser supports which means it works even with ancient IE browsers. plUpload supports various technologies to perform these uploads and you can choose which ones you want to support as part of your app. Maybe you really want to support only HTML5 or maybe HTML5 and Flash to get most of the reach you need. Or add Silverlight and Flash components to provide a better experience for those that have these components installed. You just provide the technologies you want and plUpload will use your preferences in order until it finds one that works with the browser its running.

plUpload has jQuery and jQuery UI components that provide a user interface. I've only used the jQuery jquery.plUploadQueue component because this component provides a richer interface. Here's is what the uploader looks like as part of the image uploader example I'll show in this post:

plUploadComponent

The plUploader Queue component is the square box on the bottom of the screen shot. You basically can click the Add Files button to add one or more files using the standard OS file open dialog which lets you see images (or other content) as icons for easy selection which is great for image selection:

FileOpenDialog

If using the HTML 5 (or Google Gears) mode, you can also drag and drop images into the control out of any Shell interface like Internet Explorer.

Implementing a plUpload Image Uploader

Lets look at the example and how to build it using plUpload on the client and an ASP.NET HTTP Handler on the server using a custom HttpHandler that greatly simplifies handling the incoming file or file chunks. Using this custom plUploadHandler implementation you can listen for a number of 'events', but for most applications the completion event is the only one that you need to implement.

Let's look at the example and the application code involved to make it work. Let's start on the client with the plUpload component.

Client Side plUpload Setup

To install plUpload you can download the zip distribution and then copy the relevant files into your project. I plan to use HTML5, Flash, and Silverlight so I pull the relevant components for those and drop it all into an /scripts/plUpload folder:

ProjectSetup 

I like keeping all the plUpload related files in a single folder so it's easy to swap in a new version at a later point.

Then once these scripts and styles are added to the project you can then reference them in your HTML page that will display the upload component:

<link href="scripts/plupload/jquery.plupload.queue/css/jquery.plupload.queue.css" 
        rel="stylesheet"
        type="text/css" />

<script src="scripts/jquery.min.js"></script>

<script src="scripts/plupload/plupload.full.js"></script>    
<script src="scripts/plupload/jquery.plupload.queue/jquery.plupload.queue.js"></script>

<!-- just for this demo: draggable, closable, modalDialog -->
<script src="Scripts/ww.jquery.min.js"></script>    
        
<!-- page specific JavaScript that puts up plUpload component -->
<script src="UploadImages.js"></script>

You need to reference the style for the UI and the scripts for plUpload and the jQuery plUpload.queue component which are separate. You also need jQuery since the queue component relies on it. The other two scripts are application specific - UploadImage.js contain the page JavaScript and ww.jquery.js includes some UI helpers and jQuery plug-ins from my UI library. The latter is not required - just used for the sample.

To embed the plUpload component into the page create an empty <div> element like this:

<div id="Uploader">        
</div> 

If you want to see the full HTML you can check out the UploadImages.htm page on GitHub along with the rest of this example.

Setting up the basic plUpload Client Code

Next, we'll need some script to actually display and make the component work. The page script code lives in UploadImages.js.

The base code to create a plUpload control and hook it up looks like this:

$(document).ready(function () {
    
    $("#Uploader").pluploadQueue({
        runtimes: 'html5,silverlight,flash,html4',
        url: 'ImageUploadHandler.ashx',
        max_file_size: '1mb',
        chunk_size: '65kb',
        unique_names: false,
        // Resize images on clientside if we can
        resize: { width: 800, height: 600, quality: 90 },
        // Specify what files to browse for
        filters: [{ title: "Image files", extensions: "jpg,jpeg,gif,png" }],
        flash_swf_url: 'scripts/plupload/plupload.flash.swf',
        silverlight_xap_url: 'scripts/plupload/plupload.silverlight.xap',
        multiple_queues: true
    });

    // get uploader instance
    var uploader = $("#Uploader").pluploadQueue();      

});

Most of these settings should be fairly self explanatory. You can find a full list of options on the plUpload site.

The most important setting is the url which points at the server side HttpHandler URL that will handle the uploaded Http chunks, and the runtimes that are supported. I like to use HTML5 first, then fallback to Silverlight, then Flash and finally plain HTML4 uploads if all else fails. In order for Flash and Silverlight to work I have to specify the flash_swf_url and silverlight_xap_url and point them at the provided Flash and Silverlight components in the plUpload folder.

I can also specify the max file size which is detected on the client and prevents uploads of very large files. plUpload - like most upload components sends data one small chunk at a time which you can set in the chunk_size option. The server then picks up these chunks and assembles them, one HTTP request at a time by appending them to a file or other data store.

You can specify whether plUpload sends either the original filename or a random generated name. Depending on your application a random name might be useful to avoid guessing what the uploaded filename is. In this example I want to actually display the image that were uploaded immediately, so I don't want unique names.

plUpload also can automatically resize images on the client side when using the Flash and Silverlight components which can reduce bandwidth significantly.

You can also specify a filename filter list when picking files. Here I basically filter the list to a few image formats I'm willing to accept from the client. Note that although I'm filtering extensions here on the client, it's important that you also check the file type on the server, as it's possible to directly upload to the server bypassing the plUpload UI. A malicious hacker might try to upload an executable file or script file and then call it via a Web browser. ALWAYS check file names on the server and make sure you don't write out files in formats that can be executed in any way. You'll see in the server side code that I explicitly check for the same image formats specified here on the client and if it's a different extension, I disallow the file from being written to disk.

Detecting when a File has been uploaded

If you want to have something happen when a file is uploaded you can implement the FileUploaded event on the uploader instance in created in the code above. In my example, the server returns the URL where the uploaded and resized image is available on the server. In the code below response.response contains the URL to the image, which is then used to construct an <img> element that is then appended to the ImageContainer <div> tag in the document.

Here's what that code looks like:

// bind uploaded event and display the image
// response.response returns the last response from server
// which is the URL to the image that was sent by OnUploadCompleted
uploader.bind("FileUploaded", function (upload, file, response) {
    // remove the file from the list
    upload.removeFile(file);

    // Response.response returns server output from onUploadCompleted
    // our code returns the url to the image so we can display it
    var imageUrl = response.response;

    $("<img>").attr({ src: imageUrl })
                .click(function () {
                    $("#ImageView").attr("src", imageUrl);
                    setTimeout(function () {
                        $("#ImagePreview").modalDialog()
                                        .closable()
                                        .draggable();
                        $("#_ModalOverlay").click(function () {
                            $("#ImagePreview").modalDialog("hide");
                        });
                    }, 200);
                })
                .appendTo($("#ImageContainer"));
});

The image that is added to the page can also be clicked and a bigger overlay is then displayed of the image. This code is obviously application specific. What you do when an upload completes is entirely up to you. If you upload a Zip file, maybe you want to update your UI with the filename of the Zip file or show an attachment icon etc. With images you typically want to immediately display them as soon as they are uploaded which is nice.

Note that I explicitly remove the uploaded file from the list first - by default plUpload leaves files uploaded in the list with a success icon next to it. I find that this adds too much clutter and is confusing to users, especially if you allow users to do multiple uploads, so I prefer removing files from the list so when uploads are complete the list is empty again to accept more files.

Error Handling

Error handling is one of the weak points of plUpload as it doesn't report much information when an error occurs. Some errors - like a file that's too big - pop up alert() windows which is kind of annoying. Client and IO errors while uploading result in fairly generic errors like Http Error even if the server side returns a full error message,  but at least you get some feedback so you can tell what happened.

To handle these errors implement the Error event like this:

// Error handler displays client side errors and transfer errors
// when you click on the error icons
uploader.bind("Error", function (upload, error) {
    showStatus(error.message,3000,true);
});

Limiting the Number of Files added to plUpload

There's no built-in way to limit the number of files that can be uploaded. However there's a FilesAdded event you can hook and you can look at how many files are in the control before the new files are displayed. To check for the number of files to be uploaded at once you can use code like the following:

// only allow 5 files to be uploaded at once
uploader.bind("FilesAdded", function (up, filesToBeAdded) {
    if (up.files.length > 5) {
        up.files.splice(4, up.files.length - 5);
        showStatus("Only 5 files max are allowed per upload. Extra files removed.", 3000, true);
        return false;
    }
    return true;
});

Overall the client process of uploading is pretty simple to implement. There are a few additional events you can capture and use to determine how to handle the UI, but there are not a ton of options for managing reloading the UI or disabling downloads altogether after the first upload for example. To do this you have manually hide/remove DOM elements.

ASP.NET Server Side Implementation

The client side of plUpload is reasonably well documented, but when it comes to the server side code, you can basically look at the sample PHP code and the reverse engineer it. I've provided a base handler implementation that parses the plUpload client format and accepts the chunks in a generic handler interface. The Westwind.plUploadHandler component contains:

  • plUploadHandlerBase
  • plUploadHandlerBaseAsync
  • plUploadFileHandler

The first two handle the base parsing logic for plUploads request data, and then expose a few 'event' hook methods that you can override to receive data as it comes in. The plUploadFileHandler is a more specific implementation that accepts incoming data as files and writes them out to a path specified in the handler's properties.

ClassHierarchy

Before I look at how these handlers work, let's look at the implementation example for the sample Image Uploader sample app. The process for the server side here is simple:

  • Add a reference to the Westwind.plUploadHandler assembly (or project)
  • Create a new HttpHandler that derives from any of the above handlers - here plUploadFileHandler
  • Override at least the OnUploadCompleted() method to do something on the server when a file has completed uploading

In this example I use an ASHX based HttpHandler called ImageUploadHandler.ashx and inherit it from the plUploadFileHandler since we are effectively uploading images files to the server. The code for this application level handler looks like this:

public class ImageUploadHandler : plUploadFileHandler
{
    const string ImageStoragePath = "~/UploadedImages";
    static int ImageHeight = 480;

    public ImageUploadHandler()
    {
        // Normally you'd set these values from config values
        FileUploadPhysicalPath = "~/tempuploads";
        MaxUploadSize = 2000000;
    }

    protected override void OnUploadCompleted(string fileName)
    {
        var Server = Context.Server;

        // Physical Path is auto-transformed
        var path = FileUploadPhysicalPath;
        var fullUploadedFileName = Path.Combine(path, fileName);


        var ext = Path.GetExtension(fileName).ToLower();
        if (ext != ".jpg" && ext != ".jpeg" && ext != ".png" && ext != ".gif")
        {
            WriteErrorResponse("Invalid file format uploaded.");
            return;
        }

        // Typically you'd want to ensure that the filename is unique
        // Some ID from the database to correlate - here I use a static img_ prefix
        string generatedFilename = "img_" + fileName;

        string imagePath = Server.MapPath(ImageStoragePath);

        try
        {
            // resize the image and write out in final image folder
            ResizeImage(fullUploadedFileName, Path.Combine(imagePath, generatedFilename), ImageHeight);

            // delete the temp file
            File.Delete(fullUploadedFileName);
        }
        catch (Exception ex)
        {
            WriteErrorResponse("Unable to write out uploaded file: " + ex.Message);
            return;
        }

        string relativePath = VirtualPathUtility.ToAbsolute(ImageStoragePath);
        string finalImageUrl = relativePath + "/" + generatedFilename;

        // return just a string that contains the url path to the file
        WriteUploadCompletedMessage(finalImageUrl);
    }

Notice that this code doesn't have to deal with plUpload's internal upload protocol format at all - all that's abstracted in the base handlers. Instead you can concentrate on what you want to do the with the incoming data - in this case a single completed, uploaded file. Here I only deal with the OnUploadCompletedEvent() which receives a single parameter of the filename that plUpload provided. This filename is either the original filename the user entered or a unique name if unique_names was set to true on the client. In this case I had the setting false so that I do receive the original filename.

The code in this completion handler basically resizes the uploaded file and then copies it to another folder as part of the resizing operation. Simple.

In general you don't want to expose your upload folder directly to the Web to avoid people guessing the name of uploaded files and accessing them before the file has completely uploaded. Here files upload to ~/tempUploads and then copies the files to the ~/UploadedImages folder from which the images can be displayed.

Note also that I check for valid extensions as mentioned earlier - you should always check to ensure that files are of the proper type that you want to support to avoid uploading of scriptable files. If you don't let people access files after upload (as I'm doing here for image viewing) you should ensure that your upload folders either are out of the Web site/virtual space, or that permissions are such that unauthenticated users can't access those files.

If any kind of error occurs I can use WriteErrorResponse to write out an error message that is sent to the client. plUpload can display these errors.

When all is done, I can write out an optional string of data that is sent back to plUploads FileUploaded event that I handled in script code. I can use WriteUploadCompletedMessage() to write out this message. Whatever string you write here goes directly back to the FileUploaded event handler on the client and becomes available as response.response. Here I send back the image url of the resized image, so that the client can display it as soon as the individual file has been uploaded.

I can implement additional events on my handler implementation. For example, my code for this handler also includes the OnUploadStarted() method which basically deletes files that are older than 15 minutes to avoid cluttering the upload folders:

protected override bool OnUploadStarted(int chunk, int chunks, string name)
{
    // time out files after 15 minutes - temporary upload files
    DeleteTimedoutFiles(Path.Combine(FileUploadPhysicalPath, "*.*"), 900);

    // clean out final image folder too
    DeleteTimedoutFiles(Path.Combine(Context.Server.MapPath(ImageStoragePath), "*.*"), 900);

    return base.OnUploadStarted(chunk, chunks,name);            
}

// these aren't needed in this example and with files in general
// use these to stream data into some alternate data source
// when directly inheriting from the base handler

//protected override bool  OnUploadChunk(Stream chunkStream, int chunk, int chunks, string fileName)
//{
//     return base.OnUploadChunk(chunkStream, chunk, chunks, fileName);
//}

//protected override bool OnUploadChunkStarted(int chunk, int chunks, string fileName)
//{
//    return true;
//}
The other two event methods are not used here, but if you want to do more low level processing as data comes in you can capture OnUploadChunkStarted() and OnUploadChunk(). For a plUploadFileHandler subclass this doesn't make much sense, but if you are subclassing directly from plUploadHandlerBase this is certainly useful. For example, you might capture incoming output and stream it one chunk at a time into a database using OnUploadChunk().

How plUploadHandler works

The format that plUpload uses is pretty simple - it sends multi-part form data for chunks of data uploaded with each message containing a chunk of file data plus information about the upload - specifically it includes:

  • name - the name of the file uploaded (or a random name if you chose to unique_names to true)
  • chunks - the number of total chunks that are sent
  • chunk - the number of the chunk that is being sent
  • file - the actual file binary data

Here's what a typical raw chunk looks like:

POST http://localhost/plUploadDemo/ImageUploadHandler.ashx HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 41486
Origin: http://localhost
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22
Content-Type: multipart/form-data; boundary=----pluploadboundaryp17l6c9gil157rq4pdhp7kc180a5
Accept: */*
Referer: http://localhost/plUploadDemo/UploadImages.htm
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,de-DE;q=0.6,de;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

------pluploadboundaryp17l6c9gil157rq4pdhp7kc180a5
Content-Disposition: form-data; name="name"

DSC_0154.JPG
------pluploadboundaryp17l6c9gil157rq4pdhp7kc180a5
Content-Disposition: form-data; name="chunk"

0
------pluploadboundaryp17l6c9gil157rq4pdhp7kc180a5
Content-Disposition: form-data; name="chunks"

3
------pluploadboundaryp17l6c9gil157rq4pdhp7kc180a5
Content-Disposition: form-data; name="file"; filename="DSC_0154.JPG"
Content-Type: image/jpeg


… binary data first chunk

You can see that this request has 3 chunks and this is the first (0 = 1st in 0 based) chunk. The binary data contains the first chunk of data. The server receives the chunk and returns a JSON response which plUpload processes. The message returned looks like this:

{"jsonrpc" : "2.0", "result" : null, "id" : "id"}

The success response can include a message, but this comes from the default handler and the value is simply null. Errors send n error object as part of the json along with an error message that can be passed back to the plUpload UI (actually that doesn't work - plUpload always reports Http Error). Although you can send these messages the plUpload UI doesn't actually display them unfortunately instead sending generic error messages.

The base Http Handler Code

To deal with the plUpload protocol implementation here is the HttpHandler that sets up the base processing of these plUpload chunk requests.

The main class is the plUploadBaseHandler which provides the core cracking of the incoming chunk messages and then firing 'event hook methods' (the Onxxxx methods) that can be overridden and subclassed to more easily handle the file upload without worrying about the semantics of the plUpload protocol.

Here's what base plUploadBaseHandler looks like:

/// <summary>
/// Base implementation of the plUpload HTTP Handler.
/// 
/// The base handler doesn't handle storage in any way
/// it simply gets message event methods fired when 
/// the download is started, when a chunk arrives and when 
/// the download is completed.
/// 
/// This abstract class should be subclassed to do something
/// with the received chunks like stream them to disk 
/// or a database.
/// </summary>
public abstract class plUploadBaseHandler : IHttpHandler
{
    // Don't use HttpContext.Current - Async handlers don't see it
    protected HttpContext Context;
    protected HttpResponse Response;
    protected HttpRequest Request;

    public bool IsReusable
    {
        get { return false; }
    }

    /// <summary>
    /// Maximum upload size in bytes
    /// default: 0 = unlimited
    /// </summary>
    protected int MaxUploadSize = 0;

    /// <summary>
    /// Comma delimited list of extensions allowed,
    /// extension preceded by a dot.
    /// Example: .jpg,.png
    /// </summary>
    protected string AllowedExtensions = ".jpg,.jpeg,.png,.gif,.bmp";


    public void ProcessRequest(HttpContext context)
    {
        Context = context;
        Request = context.Request;
        Response = context.Response;

        // Check to see whether there are uploaded files to process them
        if (Request.Files.Count > 0)
        {
            HttpPostedFile fileUpload = Request.Files[0];

            string fileName =fileUpload.FileName;
            if (string.IsNullOrEmpty(fileName))
                fileName = Request["name"] ?? string.Empty;

            // normalize file name to avoid directory traversal attacks
            // always strip any paths
            fileName = Path.GetFileName(fileName);


            string tstr = Request["chunks"] ?? string.Empty;
            int chunks = -1;
            if (!int.TryParse(tstr, out chunks))
                chunks = -1;
            tstr = Request["chunk"] ?? string.Empty;
            int chunk = -1;
            if (!int.TryParse(tstr, out chunk))
                chunk = -1;

            // If there are no chunks sent the file is sent as one 
            // this likely a plain HTML 4 upload (ie. 1 single file)
            if (chunks == -1)
            {
                if (MaxUploadSize == 0 || Request.ContentLength <= MaxUploadSize)
                {
                    if (!OnUploadChunk(fileUpload.InputStream, 0, 1, fileName))
                        return;
                }
                else
                {
                    WriteErrorResponse(Resources.UploadedFileIsTooLarge, 413);
                    return;
                }

                OnUploadCompleted(fileName);

                return;
            }
            else
            {
                // this isn't exact! We can't see the full size of the upload
                // and don't know the size of the large chunk
                if (chunk == 0 && MaxUploadSize > 0 && Request.ContentLength * (chunks - 1) > MaxUploadSize)
                    WriteErrorResponse(Resources.UploadedFileIsTooLarge, 413);
            }


            // check for allowed extensions and block
            string ext = Path.GetExtension(fileName);
            if (!("," + AllowedExtensions.ToLower() + ",").Contains("," + ext.ToLower() + ","))
            {
                WriteErrorResponse(Resources.InvalidFileExtensionUploaded);
                return;
            }

            if (!OnUploadChunkStarted(chunk, chunks, fileName))
                return;

            // chunk 0 is the first one
            if (chunk == 0)
            {
                if (!OnUploadStarted(chunk, chunks, fileName))
                    return;
            }

            if (!OnUploadChunk(fileUpload.InputStream, chunk, chunks, fileName))
                return;

            // last chunk
            if (chunk == chunks - 1)
            {
                // final response should just return
                // the output you generate
                OnUploadCompleted(fileName);
                return;
            }

            // if no response has been written yet write a success response
            WriteSucessResponse();
        }
    }


    /// <summary>
    /// Writes out an error response
    /// </summary>
    /// <param name="message"></param>
    /// <param name="statusCode"></param>
    /// <param name="endResponse"></param>
    protected void WriteErrorResponse(string message, int statusCode = 100, bool endResponse = false)
    {
        Response.ContentType = "application/json";
        Response.StatusCode = 500;

        // Write out raw JSON string to avoid JSON requirement
        Response.Write("{\"jsonrpc\" : \"2.0\", \"error\" : {\"code\": " + statusCode.ToString() + ", \"message\": " + JsonEncode(message) + "}, \"id\" : \"id\"}");
        if (endResponse)
            Response.End();
    }

    /// <summary>
    /// Sends a message to the client for each chunk
    /// </summary>
    /// <param name="message"></param>
    protected void WriteSucessResponse(string message = null)
    {
        Response.ContentType = "application/json";
        string json = null;
        if (!string.IsNullOrEmpty(message))
            json = JsonEncode(message);
        else
            json = "null";

        Response.Write("{\"jsonrpc\" : \"2.0\", \"result\" : " + json + ", \"id\" : \"id\"}");
    }

    /// <summary>
    /// Use this method to write the final output in the OnUploadCompleted method
    /// to pass back a result string to the client when a file has completed
    /// uploading
    /// </summary>
    /// <param name="data"></param>
    protected void WriteUploadCompletedMessage(string data)
    {
        Response.Write(data);
    }

    /// <summary>
    /// Completion handler called when the download completes
    /// </summary>
    /// <param name="fileName"></param>
    protected virtual void OnUploadCompleted(string fileName)
    {

    }

    /// <summary>
    /// Fired on every chunk that is sent
    /// </summary>
    /// <param name="chunk"></param>
    /// <param name="chunks"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    protected virtual bool OnUploadChunkStarted(int chunk, int chunks, string fileName)
    {
        return true;
    }

    /// <summary>
    /// Fired on the first chunk sent to the server - allows checking for authentication
    /// file size limits etc.
    /// </summary>
    /// <param name="chunk"></param>
    /// <param name="chunks"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    protected virtual bool OnUploadStarted(int chunk, int chunks, string fileName)
    {
        return true;
    }

    /// <summary>
    /// Fired as the upload happens
    /// </summary>
    /// <param name="chunkStream"></param>
    /// <param name="chunk"></param>
    /// <param name="chunks"></param>
    /// <param name="name"></param>
    /// <returns>return true on success false on failure</returns>
    protected virtual bool OnUploadChunk(Stream chunkStream, int chunk, int chunks, string fileName)
    {
        return true;
    }

    /// <summary>
    /// Encode JavaScript
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    protected string JsonEncode(object value)
    {
        var ser = new JavaScriptSerializer();
        return ser.Serialize(value);
    }
}

This key is the ProcessRequest() method which cracks the plUpload method and then fires of various event hook methods in response and sends them the relevant data from the uploaded chunk. This handler will be called multiple times potentially (depending on the number of chunks). The rest of the class are simply the default 'event hook method' base implementations that don't do anything in this abstract class.

This class is abstract so it has to be inherited to do something useful. The base handler requires that you implement at least the OnUploadChunk() method to actually capture the uploaded data, and probably also the OnUploadCompleted() method to do something with the upload once has completed.

One provided specialization is the plUploadFileHandler subclass which inherits the base handler and writes the chunked output to file, cumulatively appending to the same file:

public class plUploadFileHandler : plUploadBaseHandler
{
    /// <summary>
    /// Physical folder location where the file will be uploaded.
    /// 
    /// Note that you can assign an IIS virtual path (~/path)
    /// to this property, which automatically translates to a 
    /// physical path.
    /// </summary>
    public string FileUploadPhysicalPath
    {
        get
        {
            if (_FileUploadPhysicalPath.StartsWith("~"))
                _FileUploadPhysicalPath = Context.Server.MapPath(_FileUploadPhysicalPath);
            return _FileUploadPhysicalPath;
        }
        set
        {
            _FileUploadPhysicalPath = value;
        }
    }
    private string _FileUploadPhysicalPath;


    public plUploadFileHandler()
    {
        FileUploadPhysicalPath = "~/temp/";
    }

    /// <summary>
    /// Stream each chunk to a file and effectively append it. 
    /// </summary>
    protected override bool OnUploadChunk(Stream chunkStream, int chunk, int chunks, string uploadedFilename)
    {
        var path = FileUploadPhysicalPath;

        // try to create the path
        if (!Directory.Exists(path))
        {
            try
            {
                Directory.CreateDirectory(path);
            }
            catch (Exception ex)
            {
                WriteErrorResponse(Resources.UploadDirectoryDoesnTExistAndCouldnTCreate);
                return false;
            }                
        }

        string uploadFilePath = Path.Combine(path, uploadedFilename);
        if (chunk == 0)
        {
            if (File.Exists(uploadFilePath))
                File.Delete(uploadFilePath);
        }

        Stream stream = null;
        try
        {
            stream = new FileStream(uploadFilePath, (chunk == 0) ? FileMode.CreateNew : FileMode.Append);
            chunkStream.CopyTo(stream, 16384);                
        }
        catch
        {
            WriteErrorResponse(Resources.UnableToWriteOutFile);
            return false;
        }
        finally
        {
            if (stream != null)
                stream.Dispose();
        }

        return true;
    }
}

This class adds a property for the upload path where files uploaded are stored and overrides the OnUploadChunk() method to write the received chunk to disk which effectively appends the chunk to the existing file.

If all you want to do is fire and forget file uploads into a specified folder you can use this handler directly. But typically - as in the ImageUploadHandler.ashx application implementation - you'll want to do something with the data once it arrives. In our example, I subclassed from plUploadBaseHandler and implemented the OnUploadCompleted method to handle the resizing and storing of the image in a separate folder. I suspect most applications will want to do that sort of thing.

The application level ImageUploadHandler then simply inherits from the plUploadFileHandler class and only implements the OnUploadCompleted() method which simply receives the finished file to do something with - resize and copy in this case.

In summary, implementation of custom plUpload handlers is easy with this class. I've used this uploader on quite a few applications of reasonably large volume and it's been solid. At this point I have an easy framework for plugging uploads into any ASP.NET application. Hopefully you some of you find this useful as well.

Resources

Posted in ASP.NET  JavaScript  

The Voices of Reason


 

Matt
March 09, 2013

# re: Using plUpload to upload Files with ASP.NET

I think you needed more detail in this article.

Just kidding, great post. Never heard of this before, but I'll definitely give it a go.

MCKLMT
March 11, 2013

# re: Using plUpload to upload Files with ASP.NET

Do you know if plUpload use parallel ou sequencial upload? I do not found on the website.

Thanks

Darrell
March 11, 2013

# re: Using plUpload to upload Files with ASP.NET

Thanks for the detailed article. I've been using the plupload tool for about a year now on a few of my company's websites. For the most part it's been great. A couple of gotchas I've run across that you don't mention in your article that could be useful to others. For our use, we're actually sending the files to amazon ec2. Using silverlight, flash I needed to setup the clientaccesspolicy and crossdomain files. I had to set those files to allow posts from not only my localhost but also for my main website domain. The other tricky thing that has pretty much been unsolvable to this point is we get some IO errors. Sometimes it's weird buggy flash issues (I suspect from reviewing plupload forums), other times it's some proxy or firewall issue on a user's network blocking outbound flash/silverlight request (another guess). It can be pretty frustrating at times when our best guess to some of our customers is "try another computer". Other times we had them check with their IT departments to see if stuff is getting blocked. Not sure if you've run into any of these issues.

Leon
March 12, 2013

# re: Using plUpload to upload Files with ASP.NET

This is awesome! I just started doing this myself over the past week and was trying to reverse engineer the PHP for ASP.NET. Thanks for paving the way!

Craig
March 16, 2013

# re: Using plUpload to upload Files with ASP.NET

I've been using uploadify for a web site I built. I found plUpload a couple of years ago and really liked it, so tried to replace my uploadify implementation. Unfortunately the one thing I never figured out how to do was send additional data along with the upload. In my site the user has to select some additional information along with the upload (e.g. whether it should go in an existing album or create a new album, name for the new album) and I just could never get that additional information sent and received with the rest of the upload, so sadly I ended up giving up and sticking with uploadify.

If you have any examples of posting data in addition to the actual file upload I'd love to see it.

luke baughan
March 16, 2013

# re: Using plUpload to upload Files with ASP.NET

Another great post! Another fantastic uploader is BluImp jQuery File Uploader which has multiple backend implementation most notably Max Pavlovs ASP.NET MVC 3 implementation which can be found herehttps://github.com/maxpavlov/jQuery-File-Upload.MVC3 well worth a look!

Rick Strahl
March 17, 2013

# re: Using plUpload to upload Files with ASP.NET

@Craig - Yes you can post additional POST parameters using the multipart_params option on the uploader. You pass an object with properties and values that are serialized. Here's a forum topic that discusses: http://www.plupload.com/punbb/viewtopic.php?id=42

@Luke - thanks for the reference to BluImp. Looks very nice, but it has a bunch of dependencies, which I don't like. Especially the dependence on BootStrap is problematic if you don't actually use bootstrap. I prefer something generic I can plug in anywhere quickly with minimum dependencies to collect.

Obviously there are a lot of choices available for uploaders. In the end it's picking the one you want and going with it. I've actually been using this for a few years, just posted it now, so there are new choices.

Leon Sandcastle
March 19, 2013

# re: Using plUpload to upload Files with ASP.NET

It seems that the error handler only gets "HTTP Error" every time. I see in firebug that the json was returned from the handler, but cannot figure out where in the JS you can actually get the meaningful error to display.

Rick Strahl
March 19, 2013

# re: Using plUpload to upload Files with ASP.NET

@Leon - yes the default plUpload files do that unfortunately. It's the client script that looks for HTTP errors (status > 400) and then returns HTTP ERROR.

If you use the version I ship in the example - I fixed this by looking at the response and picking out the error message if a JSON parser is present. In the latest sample code from GitHub you should get a real error message.

mike ryan
March 23, 2013

# re: Using plUpload to upload Files with ASP.NET

Has anyone tried this file hosting site called speedyshare ? An Indian guy over here has posted a review on the site saying it comes with no ads or pop ups of any kind and there are no waiting time either.

But what I need to know is whether it truely delivers and are there any downtimes ?

http://www.gauravbidasaria.com/reviews/best-reliable-online-file-sharing-file-hosting-web-site/

You can read his review above.

Obika
March 28, 2013

# re: Using plUpload to upload Files with ASP.NET

I am trying to use the multipart_param but cannot figure out how to get the values in the handler. I followed the samples shown in the forum but not able to get it working using Request.Form. Any help would be appreciated.

Pavel Patino
April 01, 2013

# re: Using plUpload to upload Files with ASP.NET

@MCKLMT plUpload uses sequential uploads. At the company I work for, I was in charge of replacing an old component with the plUpload control for an image uploader. The old control supported concurrent/parallel uploads. It turns out through some basic testing that the parallel uploads completed almost 3 times faster than the sequential uploads. Obviously that was a very simplistic testing scenario, but the point is it was much slower. I looked for a parallel implementation that could send multiple requests to the server at the same time, but I could not find one anywhere. So I pulled the plUpload source from github and modified it for concurrent uploads. I would love to at some point share that solution with the public but at the moment that is not possible. Although it is a fairly big change, it was not too terribly difficult to implement - I did not have to rework the entire plugin, only small portions of it. I had to update jQuery.pluploadQueue js (the jQuery interface), the plupload js core, the javascript that handles the Silverlight runtime / requests, as well as Silverlight 'runtime' code itself. I did not develop the concurrent runtimes for Flash or anything else, but this was fine in our particular situation. In the end, we ended up with a much better user interface and highly improved performance with concurrent uploads.

Leon Sandcastle
April 08, 2013

# re: Using plUpload to upload Files with ASP.NET

@ Obika

Javascript:
        up.settings.multipart_params = {
                    description: $("#imageDescription").val(),
                    tags: $('#image_tags').val()
                };


C#:
                    string description = context.Request.Form["description"];
                    string tags = context.Request.Form["tags"];

Taylor B
April 09, 2013

# re: Using plUpload to upload Files with ASP.NET

If only they broke it down like this on the plupload site themselves... Amazing. Thank you so much for this! I have been searching for an answer to an issue I was having, and after hours of searching found this page and rebuilt the control step by step using ur instructions and it worked perfectly. Thank you so much again!!

Bill B
August 09, 2013

# re: Using plUpload to upload Files with ASP.NET

I agree with Taylor B above. This is way better than the instructions on the plupload site for ASP.NET developers. However, I have a need to use plupload on a site that I maintain in Webmatrix (cshtml and razor). Has anyone done this?

Rick Strahl
August 09, 2013

# re: Using plUpload to upload Files with ASP.NET

@Bill - this code will work with WebMatrix - you just add the assembly to the bin folder to have the default behavior described here or you can add a new C#/VB file to create a subclass and customize the behavior. WebMatrix supports this model just fine you just can't use the project and have to use the precompiled assembly provided in this project.

bincoder
August 19, 2013

# re: Using plUpload to upload Files with ASP.NET

Hi Rick,

I have a problem to handle some situation with plUploadHandler.

Scenario:

Upload.js file
     // Specify what files to browse for  
        filters: [
           {
               title: "Files (.doc, .docx, .xls, .xlsx, .pdf, .txt ,.rtf, .jpg, .gif, .png, .zip, .rar)",
               extensions: "doc,docx,xls,xlsx,pdf,txt,rtf,jpg,gif,png,zip,rar"
           }],


UploadHandler.ashx.cs
AllowedExtensions = ".jpg,.jpeg,.png,.gif,.bmp";


The point is: I didn't include (by mistake) extension(s) defined as allowed in Upload.js. So I will be able to select file but after upload I will recieve error.

The problem is, the displayed error is always: "IOError" instead of content "Resources.InvalidFileExtensionUploaded" selected in plUploadBaseHandler.
            if (!("," + AllowedExtensions.ToLower() + ",").Contains("," + ext.ToLower() + ","))
            {
                WriteErrorResponse(Resources.InvalidFileExtensionUploaded);
                return;
            }

It could be quite confusing. IOError when you just didn't remember to synchronize extensions names from .JS file and .CS (app configuration) file.

I don't know how to change the code to display correct error message in this case.

Thank you for your help.

Okan SARICA
January 14, 2014

# re: Using plUpload to upload Files with ASP.NET

Hi
I want to show the images uploaded before and let user to update images or insert new images in ASP.NET in a record update page

Can I?

Rick Strahl
January 14, 2014

# re: Using plUpload to upload Files with ASP.NET

@Okan - Not with plUpload. You can preview images as you pick them from the native UI picture pickers, but plUpload provides no facility to capture the image before upload and display it.

You can do this with JavaScript and HTML 5 though. The HTML 5 file API allows for grabbing file content as hex binary and you can capture that file content and use it as input to an img src attribute. Not sure offhand how you could hook this into plUpload however - it wouldn't be trivial.

Raphaël Désalbres
January 26, 2014

# re: Using plUpload to upload Files with ASP.NET

Thanks for sharing this code...I use it to upload large files, not only images. I found a bug when uploading those large files, the file name cames a "blob" instead of the file name...After some research I've found the solution : Instead of getting the file name like this:

HttpPostedFile fileUpload = Request.Files[0]; //in flUploadBaseHandler.cs
string fileName = fileUpload.FileName;

You should use this:
string fileName = Request.Form["name"];

It works fine!!!

Rick Strahl
January 29, 2014

# re: Using plUpload to upload Files with ASP.NET

@Rapheal - thanks for the heads up. Wonder why the filename is not getting set - is the file you are uploading not setting the filename attribute on the form var? This comes from the browser (or Flash/SL client) and I'd expect that to be there. Changed to use Request.Form["name"] to be sure.

tego
February 21, 2014

# re: Using plUpload to upload Files with ASP.NET

Thanks for the tutorial and project code. I wonder how you handle transparent PNG resize. I noticed that the script is able only to upload it with transparency without resize. Kindly let me know if the iis even another tutorial about that or if there is a line of code we can add. Thank you again :)

MihaiB
March 21, 2014

# re: Using plUpload to upload Files with ASP.NET

Hi,

Does PLUpload control support multiple web servers, about the chunking going to different web servers ?
We need to implement a new file upload framework that works when our Load Balancers are set to Persist:None, meaning each request gets handled by the next server via Round Robin and users aren't stuck to one web server.

Rick Strahl
March 21, 2014

# re: Using plUpload to upload Files with ASP.NET

Mihai - As long as the file paths are pointing at a shared consistent location - yes you should be able to persist to multiple locations, but you have to make sure that the both the temporary and final locations are accessible and referenced the same for each of the servers.

Then again - I think for something like this you'd want to have server affinity set.

MihaiB
April 02, 2014

# re: Using plUpload to upload Files with ASP.NET

Indeed it's working.

Thanks Rick

lyh
April 05, 2014

# re: Using plUpload to upload Files with ASP.NET

awesome

Steve
June 06, 2014

# re: Using plUpload to upload Files with ASP.NET

Thanks so much for this - it is brilliant. I just used it to develop a module in the Mojoportal CMS to upload batches of pictures for a Real Estate Agent website with resizing and thumbnail creation etc and the plUploadHandler project really helped make it a piece of cake!

Only snag I had (and a thing to watch out for) is that when I uploaded it the production server it failed because ~UploadedImages ~tempuploads did not have write permission - duh! The JSON error put me in the right direction and I soon fixed that. Great job and thanks again!

Pedro Miguel
September 04, 2014

# re: Using plUpload to upload Files with ASP.NET

Hi There ,
I Wonder if this problem have ever occur to you.
On localhost i can access error messages generated by ImageUploadHandler.ashx.

When i make the thrown exception with custom message :
throw new Exception("Hey you reached the limmiiitttt");
and i can access it in the JS file with the code :
        uploader.bind('Error', function (Up, ErrorObj) {
            alert(ErrorObj.message);
        });


all was cool :) but now i've just passed the project to prod environment and the only message i get is "HTTP Error" ...I really don't know where to start to debug the problem, can you advice please ?

tks
PEdro

Rick Strahl
September 04, 2014

# re: Using plUpload to upload Files with ASP.NET

I think it has to do with IIS 500 error responses overriding the default content. I think Response.TrySkipIIsCustomErrors would fix that and always return the full response instead of default IIS error pages.

http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors(v=vs.110).aspx

Pedro Miguel
September 05, 2014

# re: Using plUpload to upload Files with ASP.NET

Rick tks a lot for the advice. It worked !

Solved with this Web.Config setting :
  <system.webServer>
    <httpErrors existingResponse="PassThrough" />
  </system.webServer>


Nice article you have on this : http://weblog.west-wind.com/posts/2009/Apr/29/IIS-7-Error-Pages-taking-over-500-Errors

good work !

Drew H
September 18, 2014

# re: Using plUpload to upload Files with ASP.NET

Hi Rick,

I was able to adapt your plUpload for an application, but I started running into performance problems. To address this I made the chunks larger and I increased the chunk stream size in plUploadFileHandler's OnUploadChunk method as large as .NET will handle for the large object heap, 81920.

Thanks a bunch!

Rick Strahl
September 19, 2014

# re: Using plUpload to upload Files with ASP.NET

@Drew - yes the chunk size is configurable and for a reason. If you upload relatively small files you'll want smaller chunks so that the download progress actually has something to show. If you primarily deal with large files though making the chunksize large definitely is a benefit for performance as less connections are needed.

Ersin
December 18, 2014

# re: Using plUpload to upload Files with ASP.NET

Hello Rick, thanks for your well-documented sample. I want to upload files except images. (zip,rar). I changed filters but it didn't work. How can i change this extensions ? Or can i :)

Thanks

Rick Strahl
December 18, 2014

# re: Using plUpload to upload Files with ASP.NET

@Ersin - That should work. All files are passed through, and filtered with the extension filter list so I'm not sure why this wouldn't work. The data coming up is just raw data - is it possible that there's some client side restriction? I haven't used this in a while maybe something's changed?

Ersin
December 19, 2014

# re: Using plUpload to upload Files with ASP.NET

Hello Rick, you're right it was a client-side problem. Actually it seems Cache problem :) Thank you again.

Umar Rehman
November 01, 2015

# re: Using plUpload to upload Files with ASP.NET

I am trying to implement PlUplaod in asp.net but getting error message.

" Object doesn't support property or method 'pluploadQueue' "

Its nothing to do with .net and i believe its my browser. I have tried IE10 and FireFox and no success.

I get the same error message when i go to http://www.plupload.com/examples/queue

I have also tried your .net sample URL avalaible on github and i get the same error;

http://www.west-wind.com/tools/samples/pluploaddemo/UploadImages.htm

Rick Strahl
November 01, 2015

# re: Using plUpload to upload Files with ASP.NET

@Umar - You're missing the scripts in the page.

Steve Dyson
November 02, 2015

# re: Using plUpload to upload Files with ASP.NET

Hi Rick,

I am trying to add input fields after file have been added in UI widget. Each line will have few extra text fields, i have tried adding fields the following code and it works but input text field doesn't retain values after second file is added to queue.

i.e. Add file and add some text to input field, add another file, the input field text disappears added in first step.

function b(f, e) {
e.contents().each(function (g, h) {
h = c(h);
if (!h.is(".plupload")) {
h.remove()
}
});
e.prepend('<div class="plupload_wrapper plupload_scroll"><div id="' + f + '_container" class="plupload_container"><div class="plupload"><div class="plupload_header"><div class="plupload_header_content"><div class="plupload_header_title">' + a("Select files") + '</div><div class="plupload_header_text">' + a("Add files to the upload queue and click the start button.") + '</div></div></div><div class="plupload_content"><div class="plupload_filelist_header"><div class="plupload_file_name">' + a("Filename") + '</div><div class="plupload_file_name_">' + '&nbsp;&nbsp;myname ' + '</div><div class="plupload_file_action">&nbsp;</div><div class="plupload_file_status"><span>' + a("Status") + '</span></div><div class="plupload_file_size">' + a("Size") + '</div><div class="plupload_clearer">&nbsp;</div></div><ul id="' + f + '_filelist" class="plupload_filelist"></ul><div class="plupload_filelist_footer"><div class="plupload_file_name"><div class="plupload_buttons"><a href="#" class="plupload_button plupload_add">' + a("Add files") + '</a><a href="#" class="plupload_button plupload_start">' + a("Start upload") + '</a></div><span class="plupload_upload_status"></span></div><div class="plupload_file_action"></div><div class="plupload_file_status"><span class="plupload_total_status">0%</span></div><div class="plupload_file_size"><span class="plupload_total_file_size">0 b</span></div><div class="plupload_progress"><div class="plupload_progress_container"><div class="plupload_progress_bar"></div></div></div><div class="plupload_clearer">&nbsp;</div></div></div></div></div><input type="hidden" id="' + f + '_count" name="' + f + '_count" value="0" /></div>')
}

Sarah Jones
November 03, 2015

# re: Using plUpload to upload Files with ASP.NET

I have tried your code works very well.

Just a quick question on file chunk part, does it work when using IE9? I have heard that IE9 doesn't support file API.

Could you please clarify this.

Rick Strahl
November 03, 2015

# re: Using plUpload to upload Files with ASP.NET

@Sarah - plUpload has a number of different ways to handle uploads. Flash or fallback to standard file uploads (ie. not chunking) based on availability. So it will work, but the experience

Bill B
January 21, 2016

# re: Using plUpload to upload Files with ASP.NET

When using the plUploadBaseHandler is the AllowedExtensions field required? I don't have a requirement to restrict the types of files uploaded. I'd be more apt to exclude certain file types by extension. Is there a way to do this?

Daniel B
April 22, 2016

# re: Using plUpload to upload Files with ASP.NET

Rick, what do you do (or can you do) when attempting to upload images larger than the httpRuntime maxRequestLength set in the web.config (default if unset is 4,096 KB)? If this is not set, or is too low, then any attempts to upload a file over this amount will result in the file _seeming_ to have uploaded (no error thrown, progress bar increments as if the the upload is happening) but the file isn't in the destination folder, because the web server throws the error 400 (bad request) quietly.

I'm tempted to increase the maxRequestLength to some ludicrous-high value, but then that leaves the door open to malicious attempts (DOS). I'd rather let the client set their value, but be informed if they attempt to upload something too large, so they know they need to adjust this value higher, or deal with the upload some other way.

Suraj Gharat
May 20, 2016

# re: Using plUpload to upload Files with ASP.NET

Awesome article.
Is is possible to do concurrent chunking ?

Bharath
July 24, 2017

# re: Using plUpload to upload Files with ASP.NET

I have a situation where i want to simulate video uploads to our application using Jmeter performance test tool. But we cannot able to simulate video uploads using jmeter since the file upload mechanism is by chunking method. Can anyone help us how to record chunking file uploads using jmeter tool ???


Rick Strahl
April 19, 2018

# re: Using plUpload to upload Files with ASP.NET

@eyb - You can probably just recompile the project in 2.0...

But then Why??? Time to upgrade to 4.x and get the benefits of performance improvements and additional features with backwards compatbility. In most cases existing 2.0 code will just run in 4.x.


Mike
February 25, 2020

# re: Using plUpload to upload Files with ASP.NET

in the plUploadBaseHandler should the check for the allowed file extensions happen earlier. If there are no chunks then it looks like the file extension is not being checked.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024