views:

165

answers:

1

I'm attempting to use the script found from http://valums.com/ajax-upload/

My controller is as follows

using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using MHNHub.Areas.ViewModels;
using MHNHub.Models;
using MHNHub.ViewModels;

namespace MHNHub.Areas.Admin.Controllers
{
    [Authorize(Roles = "Administrator")]
    public class ImageController : Controller
    {

        private MHNHubEntities _entities = new MHNHubEntities();

        //
        // GET: /Image/
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult ImageUploader()
        {
            var viewModel = new ImageViewModel()
            {
                Image = new Image()
            };

            return PartialView(viewModel);
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult ImageUploader(Image image)
        {
            try
            {
                _entities.Images.AddObject(image);
                _entities.SaveChanges();

                return RedirectToAction("Index", "Product");
            }
            catch (Exception ex)
            {
                var viewModel = new ImageViewModel()
                                {
                                    Image = image,
                                    HasError = true,
                                    ErrorMessage = ex.Message
                                };
                return PartialView(viewModel);

            }
        }

        private string _uploadsFolder = HostingEnvironment.MapPath("~/App_Data/Files");

        public Guid Upload(HttpPostedFileBase fileBase)
        {
            var identifier = Guid.NewGuid();
            fileBase.SaveAs(GetDiskLocation(identifier));
            return identifier;
        }

        private string GetDiskLocation(Guid identifier)
        {
            return Path.Combine(_uploadsFolder, identifier.ToString());
        }

    }

}

And I have a partial view like this

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MHNHub.ViewModels.ImageViewModel>" %>

<script type="text/javascript">
    $(function () {
        $("#imagedialog").dialog({
            bgiframe: true,
            height: 170,
            width: 430,
            modal: true,
            autoOpen: false,
            resizable: true
        })
    });

    $(document).ready(function createUploader() {
        var uploader = new qq.FileUploader({
            element: document.getElementById('fileuploader'),
            action: '/Image/Upload/',
            name: 'name'
        });

    });

</script>    

<div id="imagedialog" title="Upload Image">

                <div id="fileuploader">

                </div>
                <h6>Drag and drop files supported in Firefox and Google Chrome with javascript enabled.</h6> 
                    <noscript>
                         <form action="/image/upload" enctype="multipart/form-data" method="post">
                            Select a file: <input type="file" name="photo" id="photo" />   

                            <input type="submit" value="Upload" name="submit"/>
                        </form>
                    </noscript>


</div>

<div class="editor-field">
    <img src="<%: Model.Image.FileName %>" />
    <%: Html.TextBoxFor(model => model.Image.FileName) %>
    <%: Html.ValidationMessageFor(model => model.Image.FileName)%>
    <a href="#" onclick="jQuery('#imagedialog').dialog('open'); return false">Upload Image</a>
</div>

I have fileuploader.js and fileuploader.css linked properly on the master page, and the uploader appears correctly, and its even calling my action, but the HttpPostedFileBase is null and the upload action throws an exception. Any insight as to what I should do?

Edit

So I've figured out using firebug that its sending an XmlHttpRequest. How do I handle this in my upload action?

+3  A: 

The reason you are getting an empty parameter in your controller action is because this plugin doesn't send a multipart/form-data request to the server. Instead it sends application/octet-stream content type request header and it writes the file contents directly to the request stream, appending a parameter ?qqfile to the URL containing the file name. So if you want to retrieve this on the controller you will need to directly read the stream:

[HttpPost]
public ActionResult Upload(string qqfile)
{
    using (var reader = new BinaryReader(Request.InputStream))
    {
        // This will contain the uploaded file data and the qqfile the name
        byte[] file = reader.ReadBytes((int)Request.InputStream.Length);
    }
    return View();
}

If you select multiple files the plugin simply sends multiple requests to the server so this will work.

Also if you want to handle files bigger than int.MaxValue you will have to read from the request stream in chunks and write directly to an output stream instead of loading the whole file into a memory buffer:

using (var outputStream = File.Create(qqfile))
{
    const int chunkSize = 2 * 1024; // 2KB
    byte[] buffer = new byte[chunkSize];
    int bytesRead;
    while ((bytesRead = Request.InputStream.Read(buffer, 0, buffer.Length)) > 0)
    {
        outputStream.Write(buffer, 0, bytesRead);
    }
}

Remark: Remove the createUploader function name from your document.ready. It should be an anonymous function there. You could even merge it with the $(function() { ... }); you already have to setup the modal dialog.

Darin Dimitrov
Then how do I save the file(which is always going to be an image), say, to app_data/uploads or something like that?
Gallen
That's what my second snippet does. You only need to specify the path in the `File.Create`.
Darin Dimitrov
Thanks Darin, its saving the files now. Just one more thing - how do I maintain the content type? Like uploading a .jpg saves as a .jpg etc. Instead of just as a file.
Gallen
`qqfile` contains the filename. You could use the extension to *guess* the content type but this won't be 100% reliable. There's no 100% reliable way of knowing the content type.
Darin Dimitrov
i ended up doing just that. I grabbed the file extension off qqfile using Path.GetExtension() and just appended it to the saved filename. Everything I've tested so far works great, except that the plugin is saying the file transfer 'failed' when it really didn't. You wouldn't happen to know what the action should be returning to display a success message, do you? The examples he provides has nothing on this.
Gallen
In the Upload action you are returning a View. Are you sure that this view exists? You could also return some plain text or JSON.
Darin Dimitrov
I returned a JSON object, return Json(new{success=true}) and it works. For returning errors, use {error="error message"}. Thanks Darin!
Gallen