39

UPDATE

I was able to get everything to work properly and I just wanted to post back with the updated code. I used Darin Dimitrov's suggestion on using a separate generic http handler for handling the file uploads and so this is the code I came up with for that... let me know if you have questions.

<%@ WebHandler Language="C#" Class="Upload" %>

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Net;
using System.Web;

public class Upload : IHttpHandler {

    public void ProcessRequest(HttpContext context) {

        /**
         * If 'newTicket' is "false", then the directory to upload to already exists and we can extract it from
         * the 'ticketID' POST parameter.
         * 
         * If 'newTicket' is "true", then this is a new Ticket submission so we need to work with a NEW directory 
         * on the server, so the ID needs to be 1 more than the total number of directories in ~/TicketUploads/
         */
        String newTicket = context.Request["newTicket"] != null ? context.Request["newTicket"] : String.Empty;
        int theID = -1;
        if (newTicket.Equals("true")) {

            // we need to calculate a new ID
            theID = getNewID(context); // calculate the new ID = # of rows
            theID++; // add 1 to make it unique
        } else if (newTicket.Equals("false")) {

            // we can just get the ID from the POST parameter
            theID = context.Request["ticketID"] != null ? Convert.ToInt32(context.Request["ticketID"]) : -1;
        } else {

            // something went wrong with the 'newTicket' POST parameter
            context.Response.ContentType = "text/plain";
            context.Response.Write("Error with 'newTicket' POST parameter.");
        }

        // if theID is negative, something went wrong... can't continue
        if (theID < 0) {
            return;
        }

        // ready to read the files being uploaded and upload them to the correct directory
        int chunk = context.Request["chunk"] != null ? int.Parse(context.Request["chunk"]) : 0;
        string fileName = context.Request["name"] != null ? context.Request["name"] : string.Empty;
        var uploadPath = context.Server.MapPath("~/TicketUploads/" + theID + "/");
        HttpPostedFile fileUpload = context.Request.Files[0];

        // if the NEW directory doesn't exist, create it
        DirectoryInfo di = new DirectoryInfo("" + uploadPath + "");
        if (!(di.Exists)) {
            di.Create();
        }

        using (var fs = new FileStream(Path.Combine(uploadPath, fileName), chunk == 0 ? FileMode.Create : FileMode.Append)) {
            var buffer = new byte[fileUpload.InputStream.Length];
            fileUpload.InputStream.Read(buffer, 0, buffer.Length);
            fs.Write(buffer, 0, buffer.Length);
        }

        context.Response.ContentType = "text/plain";
        context.Response.Write("File uploaded.");
        return;
    }
}

I'm trying to integrate the Plupload file uploader in ASP.NET using C#. I've read the Angry Monkeys article as well as the Marco Valsecchi blog post but I'm a little lost.

The C# that the above articles suggest is roughly similar to the following:

int chunk = Request.QueryString["chunk"] != null ? int.Parse(Request.QueryString["chunk"]) : 0;
string fileName = Request.QueryString["name"] != null ? Request.QueryString["name"] : string.Empty;

HttpPostedFile fileUpload = Request.Files[0];

using (FileStream fs = new FileStream(Server.MapPath("~/TicketUploads/" + fileName), chunk == 0 ? FileMode.Create : FileMode.Append))
{
    Byte[] buffer = new Byte[fileUpload.InputStream.Length];
    fileUpload.InputStream.Read(buffer, 0, buffer.Length);

    fs.Write(buffer, 0, buffer.Length);
    fs.Close();
}

First, I have set up the Plupload configuration as follows:

$("#plupload_container").pluploadQueue({
  runtimes: 'html5,gears,flash,silverlight,html4',
  flash_swf_url: '../plupload/js/plupload.flash.swf',
  silverlight_xap_url: '../plupload/js/plupload.silverlight.xap',
  filters: [
    { title: "Image files", extensions: "jpg,gif" },
    { title: "Zip files", extensions: "zip" },
    { title: "Document files", extensions: "doc,pdf,txt" }
  ]
});

... but I feel like I'm missing something here that will be necessary for the uploading to work.

I guess my main question is how do I call the above C# code so that the uploading can begin? I have a form on a page named SubmitRequest.aspx. Clicking 'Submit' on the form results in the following:

$('form').submit(function (e) {

  // Validate number of uploaded files
  if (uploader.total.uploaded == 0) {
    // Files in queue upload them first
    if (uploader.files.length > 0) {
      // When all files are uploaded submit form
      uploader.bind('UploadProgress', function () {
        if (uploader.total.uploaded == uploader.files.length)
          $('form').submit();
      });
      uploader.start();
    }
    e.preventDefault();
  }
});

... so the uploader starts when 'Submit' is clicked and uploads the files. Once that is done, the rest of the form is submitted. I don't understand how to link this event to the C# code that will handle the uploading to a folder TicketUploads on the server.

I apologize for the longish post, but I would appreciate any help :)

Hristo
  • 45,559
  • 65
  • 163
  • 230
  • 1
    Here's a link to a blog post of mine that has an easy to use helper class and more detail on exactly how plUpload's format works. http://www.west-wind.com/weblog/posts/2013/Mar/12/Using-plUpload-to-upload-Files-with-ASPNET – Rick Strahl Apr 08 '13 at 13:36

2 Answers2

57

Here's a full working example I wrote for you:

<%@ Page Title="Home Page" Language="C#" %>
<%@ Import Namespace="System.IO" %>
<script runat="server" type="text/c#">
    protected void Page_Load(object sender, EventArgs e)
    {
        // Check to see whether there are uploaded files to process them
        if (Request.Files.Count > 0)
        {
            int chunk = Request["chunk"] != null ? int.Parse(Request["chunk"]) : 0;
            string fileName = Request["name"] != null ? Request["name"] : string.Empty;

            HttpPostedFile fileUpload = Request.Files[0];

            var uploadPath = Server.MapPath("~/TicketUploads");
            using (var fs = new FileStream(Path.Combine(uploadPath, fileName), chunk == 0 ? FileMode.Create : FileMode.Append))
            {
                var buffer = new byte[fileUpload.InputStream.Length];
                fileUpload.InputStream.Read(buffer, 0, buffer.Length);

                fs.Write(buffer, 0, buffer.Length);
            }
        }
    }
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head id="Head1" runat="server">
    <title></title>

    <style type="text/css">@import url(css/plupload.queue.css);</style>
    <script type="text/javascript" src="http://www.google.com/jsapi"></script>
    <script type="text/javascript">
        google.load("jquery", "1.3");
    </script>
    <script type="text/javascript" src="/plupload/js/gears_init.js"></script>
    <script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>
    <script type="text/javascript" src="/plupload/js/plupload.full.min.js"></script>
    <script type="text/javascript" src="/plupload/js/jquery.plupload.queue.min.js"></script>
    <script type="text/javascript">
    $(function() {
        $("#uploader").pluploadQueue({
            // General settings
            runtimes : 'gears,flash,silverlight,browserplus,html5',
            url : '/default.aspx',
            max_file_size : '10mb',
            chunk_size : '1mb',
            unique_names : true,
            // Resize images on clientside if we can
            resize : {width : 320, height : 240, quality : 90},
            // Specify what files to browse for
            filters : [
                {title : "Image files", extensions : "jpg,gif,png"},
                {title : "Zip files", extensions : "zip"}
            ],
            // Flash settings
            flash_swf_url : '/plupload/js/plupload.flash.swf',
            // Silverlight settings
            silverlight_xap_url : '/plupload/js/plupload.silverlight.xap'
        });

        // Client side form validation
        $('form').submit(function(e) {
            var uploader = $('#uploader').pluploadQueue();
            // Validate number of uploaded files
            if (uploader.total.uploaded == 0) {
                // Files in queue upload them first
                if (uploader.files.length > 0) {
                    // When all files are uploaded submit form
                    uploader.bind('UploadProgress', function() {
                        if (uploader.total.uploaded == uploader.files.length)
                            $('form').submit();
                    });
                    uploader.start();
                } else
                    alert('You must at least upload one file.');
                e.preventDefault();
            }
        });
    });
    </script>

</head>
<body>
    <form id="Form1" runat="server">
        <div id="uploader">
            <p>You browser doesn't have Flash, Silverlight, Gears, BrowserPlus or HTML5 support.</p>
        </div>
    </form>
</body>
</html>

As you will see in this example files are uploaded to the same page called default.aspx. Notice that parameters such as chunk and name are POSTed so you shouldn't use Request.QueryString to read them but Request["chunk"] directly as this will look at the POST body as well. You should also make sure that the TicketUploads folder exists on the server at the root.

In this example the same page default.aspx is used for showing the upload form and handling the uploads. In a real world application this is not something I would do. I would recommend you using a separate script which will handle the file uploads such as a generic http handler (upload.ashx).

Finally you will notice that I have used some default settings that you might wish to modify and reconfigure the plugin to fit your needs. I just took the settings from the documentation.


UPDATE:

And since I recommended using a separate generic http handler for handling the file uploads here's how it might look :

using System.IO;
using System.Web;

public class Upload : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        int chunk = context.Request["chunk"] != null ? int.Parse(context.Request["chunk"]) : 0;
        string fileName = context.Request["name"] != null ? context.Request["name"] : string.Empty;

        HttpPostedFile fileUpload = context.Request.Files[0];

        var uploadPath = context.Server.MapPath("~/TicketUploads");
        using (var fs = new FileStream(Path.Combine(uploadPath, fileName), chunk == 0 ? FileMode.Create : FileMode.Append))
        {
            var buffer = new byte[fileUpload.InputStream.Length];
            fileUpload.InputStream.Read(buffer, 0, buffer.Length);

            fs.Write(buffer, 0, buffer.Length);
        }

        context.Response.ContentType = "text/plain";
        context.Response.Write("Success");
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

Now all that's left is to reconfigure the plugin to point to this generic handler:

...
runtimes: 'gears,flash,silverlight,browserplus,html5',
url: '/upload.ashx',
max_file_size: '10mb',
...
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • wow that was fast... I'll look over it and report back with further questions. thanks! – Hristo Dec 06 '10 at 17:22
  • Since you recommend to use a separate script, could you provide an example of what that would look like as well? – Hristo Dec 06 '10 at 17:25
  • @Darin... I'm not familiar at all with these 'handlers'. Would you mind giving a high-level explanation on how program flow works here, what purpose `ProcessRequest()` and `IsReusable()` serve and how they are invoked, how this new file `upload.ashx` will communicate with the old .aspx file, etc... – Hristo Dec 06 '10 at 19:29
  • 3
    A file handler is server side script which receives an HTTP request, processes it and returns a response. An ASPX page is a special case of a file handler except that there are much more additional steps, like loading view state, instantiating server side controls, rendering, ... So when you perform an HTTP request to `/upload.ashx`, the `ProcessRequest` method will be called. In this method you have access to all request parameters, so you can extract the file, save it to disk in chunks and send some response to the client. The `IsReusable` property indicates whether the same instance ... – Darin Dimitrov Dec 06 '10 at 19:42
  • 3
    .. of the generic handler could be reused to serve multiple HTTP requests. You should set this property to true only if your handler is thread safe. I would recommend you going through some tutorials if you want to get deeper understanding of the inner workings. Here's a nice one: http://dotnetperls.com/ashx. As far as your question about communicating between the ashx and aspx page I don't understand what do you mean. What exactly would you like to communicate? Please provide more details about your scenario. – Darin Dimitrov Dec 06 '10 at 19:43
  • Thanks for the explanation. So the `ProcessRequest()` method is called automatically... I don't need to call it myself right? What I meant by communicating between ashx and aspx is like... if I want to upload some files, but for some reason something went wrong... how do I tell the aspx page that something went wrong and submission of the form should stop? – Hristo Dec 06 '10 at 21:24
  • Yes, the `ProcessRequest` method is called automatically by the ASP.NET framework. When you request `/upload.ashx` it will hit this method and it is the `plupload` plugin which is configured to send the files to this url. – Darin Dimitrov Dec 06 '10 at 21:27
  • Sounds great. I'll start implementing this and if I run into questions I'll post back here. Thanks for your help. I wish I could upvote more :) – Hristo Dec 06 '10 at 22:34
  • @Darin... Thanks again for your detailed answer. It seems like it works! I had one last question regarding the communication between the .aspx and .ashx pages. After the .ashx is done handling the file uploading, how can I POST the directory where everything was uploaded to? I would rather have .ashx either return or somehow tell the .aspx page the new directory instead of having to find it in the .aspx page. Can you help me with this? – Hristo Dec 08 '10 at 16:26
  • @Hristo, I don't quite understand your requirement. What exactly do you want to communicate to the aspx page and when? Isn't the directory you are uploading to known in advance (`~/TicketUploads`)? – Darin Dimitrov Dec 08 '10 at 16:57
  • Ah... `~/TicketUploads/` is known in advance but I will be writing to a sub-directory inside `~/TicketUploads/`, for example `~/TicketUploads/1000/`... so the sub-directory is unknown since it is created in the http handler. Thus, I need the http handler to tell the .aspx page about the new directory it has created, namely, tell the .aspx page that the files were uploaded to `~/TicketUploads/1000/`. Does this make sense? – Hristo Dec 08 '10 at 17:15
  • @Hristo, if you want to to retrieve the name of the uploaded file you could use the [FileUploaded](http://www.plupload.com/example_events.php) event. Inside this javascript callback you will get the name of the freshly uploaded file on the server. – Darin Dimitrov Dec 08 '10 at 17:51
  • @Darin, I'm not trying to retrieve the name of the uploaded file. I'm trying to retrieve the DIRECTORY of where the file was uploaded. The FileUploaded event doesn't list the directory :( – Hristo Dec 08 '10 at 19:22
  • @Hristo, when I tested all files were uploaded to the same directory: `~/TicketUploads` with some random generated names. – Darin Dimitrov Dec 08 '10 at 21:20
  • Right... that is because you're using the code above. I have other unrelated code that generates unique sub-directories that the files are uploaded in. – Hristo Dec 08 '10 at 21:56
  • 1
    @Hristo, OK, I understand your problem. You should really try a bit harder. Here's what you could do: in the generic handler simply write the full path you used like `context.Response.Write("foo bar")`. Now inside the `FileUploaded` event simply `alert(info.response);` and you will get `foo bar` alerted. Congtratulations, you have successfully passed information from your generic handler to the calling javascript function. I would also recommend you installing and using [FireBug](http://getfirebug.com/) which will help you more easily debug problems like this as you'll see the actual requests. – Darin Dimitrov Dec 08 '10 at 22:38
  • would you please send me the soultion as i want to know how to use it and i did not found the css and js when i downloaded it from plupload – Eslam Soliman Jan 31 '12 at 14:46
7

Here is a VB example of an ashx file for pluploader.

Thanks to @@Darin Dimitrov Who suggested this. Its the best solution ever with many fall backs like silverlight,flash.. etc.. its mega!

I did not find any VB examples so i converted it my self- tested and works! With chunking!

To add a ashx file to your website in VisualStudio. Right click the website

website > add new item > generic handler

This is all in one page no need for code behind. Just call this from the pluplaod plugin

<%@ WebHandler Language="VB" Class="upload" %>

Imports System
Imports System.IO
Imports System.Web


Public Class upload : Implements IHttpHandler


    Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        Dim chunk As Integer = If(context.Request("chunk") IsNot Nothing, Integer.Parse(context.Request("chunk")), 0)
        Dim fileName As String = If(context.Request("name") IsNot Nothing, context.Request("name"), String.Empty)

        Dim fileUpload As HttpPostedFile = context.Request.Files(0)

        Dim uploadPath = context.Server.MapPath("~/uploads")
        Using fs = New FileStream(Path.Combine(uploadPath, fileName), If(chunk = 0, FileMode.Create, FileMode.Append))
            Dim buffer = New Byte(fileUpload.InputStream.Length - 1) {}
            fileUpload.InputStream.Read(buffer, 0, buffer.Length)

            fs.Write(buffer, 0, buffer.Length)
        End Using

        context.Response.ContentType = "text/plain"
        context.Response.Write("Success")
    End Sub

    Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

End Class
Piotr Kula
  • 9,597
  • 8
  • 59
  • 85