Showing posts with label plupload. Show all posts
Showing posts with label plupload. Show all posts

Friday, July 15, 2011

ASP.NET MVC3 App Using plupload to Upload Directly to Amazon S3

UPDATE 8/15/2011:
I can't recommend using this for large files (video files). It's very unreliable. Web size image files and normal email attachment sized files would probably do fine. What I ended up doing to get around it was creating a web app on AppHarbor (which runs on EC2) that I upload the files to and it saves them to S3. Since the AppHarbor app is on a different domain you are still limited to Flash and Silverlight support but you do gain file chunking which brings back some reliability. Alternatively to AppHarbor you could setup your own server on Amazon as well.

I created a working example of using plupload to upload files directly to Amazon S3 and threw it on github, since the .NET examples I found on the web we're incomplete or broken.
https://github.com/crdeutsch/MVC3PluploadToAmazonS3

The files that are being uploaded do not even touch your web server. They are posted directly to Amazon. At the moment plupload only supports the Flash and Silverlight plugins as far as I can tell.

The AmazonS3Helper.cs library originated from Cary Abramoff off of this plupload forum thread.

I've made some changed based on iwasrobbed's excellent Ruby on Rails example.

Usage

Upload the crossdomain.xml found in the root of the site to your Amazon S3 Bucket.

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" secure="false" />
</cross-domain-policy>

Modify the settings in HomeController.cs to use your Amazan credentials and set your S3 bucket.

public ActionResult Index()
{
 string acl = "private";
 string bucket = "YOUR S3 BUCKET NAME";
 string accessKeyId = "YOUR AMAZON ACCESS KEY ID";
 string secret = "YOUR AMAZON SECRET ACCESS KEY";
 string policy = AmazonS3Helper.ConstructPolicy(bucket, DateTime.UtcNow.Add(new TimeSpan(0, 10, 0, 0)), acl, accessKeyId);
 string signature = AmazonS3Helper.CreateSignature(policy, secret);

 var model = new PluploadAmazonS3Model()
 {
  AWSAccessKeyId = accessKeyId,
  Policy = policy,
  Signature = signature,
  Bucket = bucket,
  Acl = acl
 };

 return View(model);
}

On the View side you could just use Razor to dump the Model variables directly into your javascript, but I decided to use hidden form variables instead in case you want to move the javascript to its own file where embedding Razor syntax won't work.

Hope this saves somebody else some time!

Wednesday, February 16, 2011

Plupload and ASP.NET MVC3

This post will explain howto integrate Plupload into an ASP.NET MVC3 project. It will probably work with lesser version of MVC without too many changes.

Integrating it into my project went smoother then any upload plugin I've ever used before. My previous favorite was NeatUpload but I've been having issues with it with large files, and since Plupload supports chunked uploads I figured I'd give it a try.

Here's what I did to get the Custom Upload example working in MVC.

First add the following to one of your Controllers or create a new Controller to hold this Action. (most of the credit goes to these two Stackoverflow questions: Question 1, Question 2)

/// <summary>
/// Handles chuncked file uploads like the ones from plupload.
/// </summary>
/// <param name="chunk"></param>
/// <param name="name"></param>
/// <returns></returns>
[HttpPost]
public ActionResult Upload(int? chunk, string name)
{
    var fileUpload = Request.Files[0];
    var uploadPath = Server.MapPath("~/App_Data/Uploads");
    chunk = chunk ?? 0;

    //UPDATE 2/17/2011: Removed this since it doesn't work. I recommend setting the unique_names param client side if you want unique names.
    ////find a free filename if this is the first chunk
    //if (!chunk.HasValue || chunk < 1)
    //{
    //    int xx = 1;
    //    while (System.IO.File.Exists(uploadedFilePath))
    //    {
    //        uploadedFilePath = Path.Combine(uploadPath, Path.GetFileNameWithoutExtension(name) + "_" + xx + Path.GetExtension(name));
    //        xx++;
    //    }
    //}
    
    //TODO: cleanup old files

    //write chunk to disk.
    string uploadedFilePath = Path.Combine(uploadPath, name);
    using (var fs = new FileStream(uploadedFilePath, 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);
    }

    return Content("Success", "text/plain");
}

NOTE: If you want to secure this Action and you're using Forms Authentication add the [Authorize] attribute above [HttpPost]

NOTE 2: I'm uploading files to ~/App_Data/Uploads. Create this folder or change the code above.

Now open or create a View file that you want to add the upload to and add the following HTML:

<div id="container">
    <div id="filelist">No runtime found.</div>
    <br />
    <a id="pickfiles" href="#">[Select files]</a>
    <a id="uploadfiles" href="#">[Upload files]</a>
</div>

Add the following Javascript to your View assuming you've placed everything from the js folder in the Plupload zip into the ~/Scripts/plupload/ folder of your project:

<!--Load 3rd party plupload scripts-->
<script src="@Url.Content("~/Scripts/plupload/gears_init.js")" type="text/javascript"></script>
<script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>
<!--Load plupload and all its runtime scripts-->
<script src="@Url.Content("~/Scripts/plupload/plupload.full.min.js")" type="text/javascript"></script>

<script type="text/javascript">

    $(document).ready(function () {
        var uploader = new plupload.Uploader({
            runtimes: 'gears,html5,flash,silverlight,browserplus',
            browse_button: 'pickfiles',
            container: 'container',
            max_file_size: '2048mb',
            url: '@Url.Action("upload", "home")',
            flash_swf_url: '@Html.ScriptPath("plupload/plupload.flash.swf")',
            silverlight_xap_url: '@Html.ScriptPath("plupload/plupload.silverlight.xap")',
            filters : [
                {title : "Image files", extensions : "jpg,gif,png"},
                {title : "Zip files", extensions : "zip"}
            ]
        });

        uploader.bind('Init', function (up, params) {
            $('#filelist').html("<div>Current runtime: " + params.runtime + "</div>");
        });

        $('#uploadfiles').click(function (e) {
            uploader.start();
            e.preventDefault();
        });

        uploader.init();

        uploader.bind('FilesAdded', function (up, files) {
            $.each(files, function (i, file) {
                $('#filelist').append('<div id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ') <b></b>' + '</div>');
            });

            up.refresh(); // Reposition Flash/Silverlight
        });

        uploader.bind('UploadProgress', function (up, file) {
            $('#' + file.id + " b").html(file.percent + "%");
        });

        uploader.bind('Error', function (up, err) {
            $('#filelist').append("<div>Error: " + err.code + ", Message: " + err.message + (err.file ? ", File: " + err.file.name : "") + "</div>");

            up.refresh(); // Reposition Flash/Silverlight
        });

        uploader.bind('FileUploaded', function (up, file) {
            $('#' + file.id + " b").html("100%");
        });
    });

</script>

NOTE: I'm using Razor as my View Engine. If you are not do a search for @* in the HTML above and replace with <%= %> type syntax.

NOTE 2: Update the url param in the javascript to point to where your Upload Action is. Mine is in the Home Controller

You will also need to have jQuery included. I left that out since most people already do, but if you don't you can use this:

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("jquery", "1.3");
</script>

The only other thing you may want to immediately do is adjust the filters option in the javascript to allow/disallow file types as needed.

That's it! Hope this was as easy for you as it was for me!