Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Posting ByteArray from Knockout.js to a Web Service

I'm very new to KnockoutJS, and I dig it so far, but try as I might I couldn't find this info anywhere so I am hoping the community can help! In my View I have the following data-binding on a file input:

 <input type="file" data-bind="value: ImageToUpload"/>
 <button data-bind="click: $root.saveImage">Upload</button>

This is part of a list in a "foreach" div, so the variable "ImageToUpload" corresponds to a property on an object from that list.

In my ViewModel the Upload button calls saveImage() and I call the web service and pass the form data to a .aspx page:

self.saveImage = function (MyObject, event) {

    $.post("Service.aspx", MyObject,  function (returnedData) {

    });
}

The object passes to my service just fine and I can access all the form data as expected including the "ImageToUpload" variable... but here is where I am stuck:

1) The "ImageToUpload" is only a string representing the name of the file I uploaded and is not a ByteArray. How do I access the image file and not just the name?

2) Is there a better way to pass the ByteArray as a Stream or other format in the response header?

3) Is my technique totally off? Is there a better way to do this? My goal is to have a dynamic list of image "slots" that be uploaded to.

Thanks in advance!

like image 647
INNVTV Avatar asked Aug 19 '12 01:08

INNVTV


2 Answers

Inability to access local file contents was a fundamental limitation of JavaScript security sandbox. It was lifted with the introduction of HTML5 file API, but, unfortunately, support is far from universal. If your code needs to work in non-compliant browsers, your only option is to let the browser handle the traditional multipart/form-data upload. If, on the other hand, not supporting IE < 10 is fine for you, it is possible to use File API "the Knockout way" with custom bindings. Have a look at this example: http://khayrov.github.com/jsfiddle/knockout-fileapi (for some reason jsFiddle breaks badly for me when I try to run this code in it). Source code at Github.

like image 55
rkhayrov Avatar answered Sep 24 '22 22:09

rkhayrov


File uploads need to be handled in a special manner. They are generally not part of your model. Take a look at the following ApiController. Specialized classes such as the MultipartFormDataStreamProvider are used to access the binary data that was submitted to the controller.

public class FileUploadController : ApiController
{
    [HttpPost]
    public List<FileResult> UploadFile()
    {
        // Verify that this is an HTML Form file upload request
        if (!Request.Content.IsMimeMultipartContent("form-data"))
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

        // Create a stream provider for setting up output streams
        MultipartFormDataStreamProvider streamProvider = new MultipartFormDataStreamProvider();

        // Read the MIME multipart content using the stream provider we just created.
        IEnumerable<HttpContent> bodyparts = Request.Content.ReadAsMultipartAsync(streamProvider).Result;

        // The submitter field is the entity with a Content-Disposition header field with a "name" parameter with value "submitter"
        string submitter;
        if (!bodyparts.TryGetFormFieldValue("submitter", out submitter))
        {
            submitter = "unknown";
        }

        // Get a dictionary of local file names from stream provider.
        // The filename parameters provided in Content-Disposition header fields are the keys.
        // The local file names where the files are stored are the values.
        IDictionary<string, string> bodyPartFileNames = streamProvider.BodyPartFileNames;

        // Create response containing information about the stored files.
        return bodyPartFileNames.Select(kv =>
        {
            FileInfo fileinfo = new FileInfo(kv.Value);
            return new FileResult
            {
                FileName = kv.Key,
                LocalPath = fileinfo.FullName,
                LastModifiedTime = fileinfo.LastWriteTimeUtc,
                Length = fileinfo.Length,
                Submitter = submitter
            };
        }).ToList();
    }

    private static bool TryGetFormFieldValue(IEnumerable<HttpContent> contents, string dispositionName, out string formFieldValue)
    {
        HttpContent content = contents.FirstDispositionNameOrDefault(dispositionName);
        if (content != null)
        {
            formFieldValue = content.ReadAsStringAsync().Result;
            return true;
        }

        formFieldValue = null;
        return false;
    }
}

An alternative solution that's a little less heavy on the code is to use HttpPostedFileBase as a parameter for your ApiController. HttpPostedFileBase is essentially a wrapper for a file that was uploaded through a form. I must add that using MultipartFormDataStreamProvider is preferred over HttpPostedFileBase, because the latter is part of the ASP.NET MVC framework. Remember, WebAPI technology can be used outside of ASP.NET MVC.

More info: http://blogs.msdn.com/b/henrikn/archive/2012/03/01/file-upload-and-asp-net-web-api.aspx and ASP.NET Web API File saved as "BodyPart_3ded2bfb-40be-4183-b789-9301f93e90af"

like image 29
Martin Devillers Avatar answered Sep 21 '22 22:09

Martin Devillers