0

I want to post a file to server asynchronously without posting the form. I have the following code:

var fileInput = document.getElementById('FileInput');
var file = fileInput.files[0];
var formData = new FormData();
formData.append('file', file, file.name);
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://servername/controllername/AnalyseFile', true);
xhr.setRequestHeader('Content-Type', 'multipart/form-data');
xhr.send(formData);

However, when the method is executed on the server, the post body contains no files. The following is from ASP.NET MVC4:

[HttpPost]
public JsonResult AnalyseFile()
{
  int filesCount = Request.Files.Count;
  if(filesCount == 0) { throw new Exception('no files...'); }
  // do stuff
}

The Files collection contains no files and I can't figure out why. Any help appreciated.

Mark Micallef
  • 2,643
  • 7
  • 28
  • 36
  • Whilst it is _possible_ to upload files without using a form- why do you want to do it? If it's to save a full page post back, you could just use a form in a partial. Here's an example: http://stackoverflow.com/questions/21675176/asp-net-mvc3-upload-a-file-from-a-partial-view-and-fill-the-corresponding-field – wazdev Mar 07 '16 at 02:28
  • http://stackoverflow.com/questions/15680629/mvc-4-razor-file-upload – David Pine Mar 07 '16 at 02:29
  • @wazdev I don't want to submit the form at all, I want to pre-emptively upload the file while the user is still working away in their browser and do some stuff with it and return the results to the browser. Again, all I want to do is asynchronously post the file on its own to the server, and have the server return some metadata about the file to the browser which I'll display dynamically. – Mark Micallef Mar 07 '16 at 02:55
  • @DavidPine Thanks for your answer, but as far as I can tell that's just submitting the form to the server. I want to send the file to the server asynchronously and have it analyse the file and send back some metadata without submitting the form. – Mark Micallef Mar 07 '16 at 02:56

2 Answers2

1

In the View, you can do:

<form>
<input name="input1" id="input1"/>
<input name="input2" id="input2"/>
<input name="input3" id="input3"/>
...
<input id="SelectedFile" name="SelectedFile" type="file"/>
</form>

And Javascript:

function AttLogic(_url, _data, _callback) {
    $.ajax({
        url: _url,
        type: 'POST',
        xhr: function () {
            var myXhr = $.ajaxSettings.xhr();
            if (myXhr.upload) { }
            return myXhr;
        },
        data: _data,
        cache: !1,
        success: _callback,
        contentType: !1,
        processData: !1
    });
}

function FormDataCustom(f) {
    var __frm = jQuery(f), data = new FormData(f);
    $(':disabled[name]', __frm).each(function () {
        data.append(this.name, $(this).val());
    });
    return data;
}

function SaveLogic(){
var dt = FormDataCustom(document.forms[0]);
AttLogic(yourUrl, dt, function (r) {
        //do something here
    });
}

In the Controller:

public ActionResult Save(parameter1,parameter1,..., List<HttpPostedFileBase> SelectedFile)
{
    //do something here
}
Jonathan
  • 1,017
  • 10
  • 21
0

You will need to read the MultiPartFormData from the Request.

As per this post:

Your method will look something like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace Some.Namespace
{
    public class SomeController : ApiController
    {
        [HttpPost]
        public async Task<JsonResult> AnalyseFile()
        {
             if (!Request.Content.IsMimeMultipartContent())
             {
                 //If not throw an error
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
             }

             MultipartFormDataStreamProvider streamProvider = new MultipartFormDataStreamProvider("c:\\tmp\\uploads");

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

            // 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.
            //depending on your version of .net, this might have been changed to FileData instead. 
            // see: https://msdn.microsoft.com/en-us/library/system.net.http.multipartformdatastreamprovider(v=vs.118).aspx
            IDictionary<string, string> bodyPartFileNames = streamProvider.BodyPartFileNames;

            //rest of code here

    }
}

I haven't tested the above code, but it should point you in the right direction.

Also have a look at How To Accept a File POST

For a more recent article: https://code.msdn.microsoft.com/AngularJS-with-Web-API-22f62a6e

Community
  • 1
  • 1
JanR
  • 6,052
  • 3
  • 23
  • 30
  • Won't this still require a form to POST - hence the "multipart/form-data"? – wazdev Mar 07 '16 at 02:31
  • You would do a request of the type `POST`, and you would set the payload to be the files, and the content type to be multipart/form data. Effectively you are posting part of the form, without doing a proper form post. – JanR Mar 07 '16 at 02:33
  • This is the standard setup for a WebAPI to be able to accept files. If you want to do an actual form post, you would just post to the controller instead of the api. – JanR Mar 07 '16 at 02:34
  • The point is that I don't want to post the form while the user is still filling it out. However, I want to pre-emptively upload the file to the server while the user is still working away in their browser. Therefore, a solution that requires the the forms to be submitted doesn't solve my problem. Also, I mean for the task to be asynchronous on the client (the browser), the server can just respond to it synchronously like any request. – Mark Micallef Mar 07 '16 at 02:52
  • Have you considered posting back the same form multiple times instead, and handling a progressive build out of an object with add/update logic instead? – wazdev Mar 07 '16 at 03:02
  • You don't need to submit the form, that's the whole point of setting up a WebAPI, you can send a request after the user selects a file (or using dropzone.js or whatever you choose). You just need to make sure that the requests contains the contents of the fields you want to submit to the api. This can all happen asynchronously in the background without the user noticing. – JanR Mar 07 '16 at 03:04
  • @MarkyMark you could also have _multiple_ forms i.e. one form within multiple partial views, posting back each partial view as the user focus moves past the partial section you are working in. – wazdev Mar 07 '16 at 03:14
  • @JanR Thanks. That sounds exactly like what I'm trying to achieve. In fact, my javascript runs on the onchange event of the file input element so I can quietly upload the file in the background while the user continues working in the form. I'm trying to understand your example, I'm just not sure why the server-side code needs to be async? And how do I get the file from the bodyPartFileNames? Are the files written to the temp folder by the MultipartFormDataStreamProvider? – Mark Micallef Mar 07 '16 at 03:22
  • The MultipartFormDataStreamProvider writes the contents of the MIME multipart body parts to the folder specified, in this case some temp folder. – JanR Mar 07 '16 at 03:23
  • Sorry, better explanation in this post: http://stackoverflow.com/questions/26158789/why-should-i-create-async-webapi-operations-instead-of-sync-ones, in short you need it because the files are read asynchronously – JanR Mar 07 '16 at 03:32
  • @JanR Sorry if this is a silly question, but the Request object doesn't have a "Content" property. Is this not the standard Request (HttpRequestBase) object? – Mark Micallef Mar 07 '16 at 03:52
  • I'll update my answer, but it assumes you use an `ApiController` instead of a basic controller. The object is actually an `HttpRequestMessage` – JanR Mar 07 '16 at 03:53
  • @JanR The line "IEnumerable bodyparts = await Request.Content.ReadAsMultipartAsync(streamProvider);" indicates that this is an implicit cast. I just can't get that line to work. – Mark Micallef Mar 07 '16 at 05:05
  • @wazdav Is it possible to submit a partial form without submitting the whole page? – Mark Micallef Mar 07 '16 at 05:18
  • Are you using an ApiController? – JanR Mar 07 '16 at 05:28
  • @JanR Yes, I created an API controller and put a new method in it, and then pretty much copied in your code, however VS shows the "IEnumerable bodyparts = await Request.Content.ReadAsMultipartAsync(streamProvider)" line as being an invalid cast. – Mark Micallef Mar 07 '16 at 23:51
  • @MarkyMark Yes- absolutely. You can call form submit from Ajax on a specific partial, and avoid a full page reload. – wazdev Mar 08 '16 at 23:12