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:
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:
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:
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.
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
Other Posts you might also like