40

I was trying to use an example mentioned here How to do a ASP.NET MVC Ajax form post with multipart/form-data?

But I keep getting "fail" error message box

Index.cshtml

<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<h2>Files Upload</h2>
<script type="text/javascript">
$(function() {
    $("#form0").submit(function(event) {
        var dataString;
        event.preventDefault();
        var action = $("#form0").attr("action");
        if ($("#form0").attr("enctype") == "multipart/form-data") {
            //this only works in some browsers.
            //purpose? to submit files over ajax. because screw iframes.
            //also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
            dataString = new FormData($("#form0").get(0));
            contentType = false;
            processData = false;
        } else {
            // regular form, do your own thing if you need it
        }
        $.ajax({
            type: "POST",
            url: action,
            data: dataString,
            dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
            contentType: contentType,
            processData: processData,
            success: function(data) {
                //BTW, data is one of the worst names you can make for a variable

            },
            error: function(jqXHR, textStatus, errorThrown) {
                //do your own thing
                alert("fail");
            }
        });
    }); //end .submit()
});
</script>
<div id="uploadDiv">
@Html.Action("Files", "Home")
</div>

@using (Ajax.BeginForm("Files", "Home", new AjaxOptions { UpdateTargetId = "uploadDiv", HttpMethod = "Post" }, new { enctype = "multipart/form-data", @id="form0"}))
{
<div>
    <div>Upload new file:
        <input type="file" name="file" /></div>
    <input type="submit" value="Save" />
</div>
}
<br />

Controller

public PartialViewResult Files(HttpPostedFileBase file)
    {
        IEnumerable<string> files;
        if ((file != null) && (file.ContentLength > 0))
        {
            string fileName = file.FileName;
            string saveLocation = @"D:\Files";
            string fullFilePath = Path.Combine(saveLocation, fileName);               


            try
            {
                file.SaveAs(fullFilePath);
                FileInfo fileInfo = new FileInfo(fullFilePath);
                file.InputStream.Read(new byte[fileInfo.Length], 0, file.ContentLength);                    
            }
            catch (Exception e)
            {
                TempData["FileUpload"] = e.Message;
                return PartialView();
            }
            files = Directory.GetFiles(@"D:\Files\");
            return PartialView(files);
        }
        else
        {
            files = Directory.GetFiles(@"D:\Files\");
            return PartialView(files);
        }
    }

Files.cshtml

@model IEnumerable<string>

@foreach (string f in Model)
{
<p>@f</p>
}

Global.asax

ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
Community
  • 1
  • 1
Alexander C.
  • 1,171
  • 4
  • 16
  • 29
  • I wrote a blog post on this a while back - might help you out: http://blog.shadowmoses.co.uk/2013/06/handling-uploads-with-mvc4-jquery.html – Spikeh Jan 03 '14 at 11:26
  • You are missing route values on Ajax.BeginForm method. – hakan Jan 07 '15 at 22:15

3 Answers3

69

That is complicated better use jquery forms plugin.

Here is the sample:

Html.BeginForm

 @using (Html.BeginForm("YourAction", "YourController"))
{
    @Html.AntiForgeryToken()
    <input type="file" name="files"><br>
    <input type="submit" value="Upload File to Server">
}

Action Method

    [HttpPost]
    [ValidateAntiForgeryToken]
    public void YourAction(IEnumerable<HttpPostedFileBase> files)
    {
        if (files != null)
        {
            foreach (var file in files)
            {
                // Verify that the user selected a file
                if (file != null && file.ContentLength > 0)
                {
                    // extract only the fielname
                    var fileName = Path.GetFileName(file.FileName);
                    // TODO: need to define destination
                    var path = Path.Combine(Server.MapPath("~/Upload"), fileName);
                    file.SaveAs(path);
                }
            }
        }
    }

Progress Bar

<div class="progress progress-striped">
   <div class="progress-bar progress-bar-success">0%</div>
</div>

Jquery & Form script

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
<script src="http://malsup.github.com/jquery.form.js"></script>

<script>
(function() {

var bar = $('.progress-bar');
var percent = $('.progress-bar');
var status = $('#status');

$('form').ajaxForm({
    beforeSend: function() {
        status.empty();
        var percentVal = '0%';
        bar.width(percentVal)
        percent.html(percentVal);
    },
    uploadProgress: function(event, position, total, percentComplete) {
        var percentVal = percentComplete + '%';
        bar.width(percentVal)
        percent.html(percentVal);
    },
    success: function() {
        var percentVal = '100%';
        bar.width(percentVal)
        percent.html(percentVal);
    },
    complete: function(xhr) {
        status.html(xhr.responseText);
    }
}); 

})();       
</script>

Update...

People who are getting issue of calling action method twice is due to Ajax.BeginForm, just convert it to Html.BeginForm(). For more clarification and to download sample code please refer at this blog.

Ashwini Verma
  • 7,477
  • 6
  • 36
  • 56
  • 2
    try adding dataType: 'json', type: "POST", contentType: "application/json" in $('form').ajaxForm(); – Ashwini Verma Sep 28 '13 at 12:15
  • After adding this code in IE file uploder is disabled – Haris N I Feb 21 '14 at 11:37
  • @AshwiniVerma - With this I am not getting selected file in argument of the method - files,it is coming as null.Can you please tell me about this.Also I have not added Jquery and form script since I dont want progress bar. – Saroj Aug 27 '14 at 05:50
  • @Saroj: You make sure file input's name attribute matches with action method's argument. In my example, it's *name="files"* . – Ashwini Verma Aug 27 '14 at 15:00
  • @AshwiniVerma : I have checked those and it is fine ,still in argument the files shows null.Please help me..I need to solve this,its too urgent for me.If u want I can show you the code.I followed the exact steps mentioned by you even included the couple of links( src scripts) but not working. – Saroj Aug 27 '14 at 17:57
  • I just want that file to be caught in the argument,rest I can take care of..Please help me – Saroj Aug 27 '14 at 18:00
  • @AshwiniVerma : your sample work fine, but i don't understand why the action is called twice? first with null parameter and the second with non null parameter? – Hichamveo Dec 04 '14 at 23:46
  • 1
    This is a void method. After I submit, I want to return back to the form. – JoshYates1980 Jan 07 '15 at 22:40
  • this is going to prevent updates. I cannot even change the view when adding this script – Andreas Feb 20 '17 at 15:33
  • [this blog](http://mitracircle.com/post/5/file-upload-without-page-refresh-in-mvc) doesn't work. You can go instead to [here](https://www.codeproject.com/Articles/1108516/File-Upload-Without-Page-Refresh-in-ASP-NET-MVC) or to the [web archive](http://web.archive.org/web/20161109215400/http://mitracircle.com/post/5/file-upload-without-page-refresh-in-mvc). – stomy Jun 19 '17 at 23:07
  • @Saroj multipart/form-data is missing, use using (Html.BeginForm("YourAction", "YourController", FormMethod.Post, new { enctype = "multipart/form-data" })) – nickornotto Apr 27 '18 at 13:33
6

The answer from Ashwini Verma is almost correct but it has a drawback, the form is submitted twice.

This is caused by the use of Ajax.BeginForm(). Using Html.BeginForm() will fix it.

Here's an example:

@* do not use Ajax.BeginForm() as it would cause the form to submit twice in connection with jQuery.Form *@
@using (var lForm = Html.BeginForm( 
  <ActionName>, <ControllerName>, FormMethod.Post, 
  new Dictionary<string, object> {{"name", <YourFormName>}, {"enctype", "multipart/form-data"}}))
{
ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
  • You are right. It send the form twice, but how to work with Html.BeginForm() and put a UpdateTargetId? – kavain Dec 08 '15 at 20:06
  • @kavain Have a look at the HTML code generated by Ajax.BeginForm() and adopt it to the Html.BeginForm() call. It's basically all HTML tag attributes that will be parsed by the jQuery Ajax Unobtrusive plugin. So if you ensure that the attributes for the update target are generated correctly then target update should also work. – ViRuSTriNiTy Dec 09 '15 at 20:28
3

You need html5 file handling and read file contents on client to get base64 encoded data.

On client you have to put:

<div>
        @Html.HiddenFor(m => m.AttachmentFileName)
        @Html.HiddenFor(m => m.AttachmentFileSize)
        @Html.HiddenFor(m => m.AttachmentFileType)
        @Html.HiddenFor(m => m.AttachmentFileContentsBase64)

    <input type="file" name="AttachmentFile" id="AttachmentFile" onchange="handleAttachmentFileChange(this.files)" />
    @Html.ValidationMessageFor(m => m.AttachmentFile)
</div>

<script>
    function handleAttachmentFileChange(files) {
        var file = files[0];
        $("#AttachmentFileName").val(file.name);
        $("#AttachmentFileSize").val(file.size);
        $("#AttachmentFileType").val(file.type || "application/octet-stream");

        var fileReader = new FileReader();
        fileReader.onload = function () {
            fileReader.result;

            $("#AttachmentFileContentsBase64").val(fileReader.result);
        };
        fileReader.readAsDataURL(file);
    };
</script>

So your code will fill hidden fields with file data (filename, type, size, base64 encoded contents). And on server side you put:

            if (AttachmentFileSize > 0)
            {
                string fileName = AttachmentFileName.Contains("\\") ? AttachmentFileName.Substring(AttachmentFileName.LastIndexOf("\\") + 1) : AttachmentFileName;

                byte[] fileBytes = Convert.FromBase64String(AttachmentFileContentsBase64.Substring(AttachmentFileContentsBase64.IndexOf(',') + 1));

//save file to file system or db

                ModelState.Remove("CurrentAttachmentFileId");
                ModelState.Remove("CurrentAttachmentFileName");

            }
            else if (AttachmentFileSize == -1)
            {
//remove existing file from fs or db
            }

The code needs to be modified with your models and logic but it worked for me

Pavel Nazarov
  • 723
  • 6
  • 10