0

I have a regular form on view with a set of inputs. I use jQuery to serialize and post it.

$("#formId").submit(function (e) {
 e.preventDefault();

 var fields = {};

 var formSerialized = $("#formId").serializeArray();

 $.each(formSerialized, function (i, field) {
  fields[field.name] = field.value; });
 e.data = fields;

 $.post("myUrl", {
   FirstName: e.data.FirstName,
   LastName: e.data.LastName
            }, function (success) {
                if (success) {
                    alert('Ok')
                } else {
                    alert('fail');
                }
            });
        }
    });

On backend I have ASP.NET WebAPI2 server with action that gets this request and automatically binds all properties to model.

Now I need to add multiple file inputs to the same form.

Is there a way to: - send files and regular properties that I send in code sample in the same time in the same request? - is it possible to extend model on WebAPI side with HttpPostedFileBase properties and to keep automatic binding of data?

What is the best way to send regular text properties (values from form) + multiple files in the same request to process them in one single method on WebAPI2 side?

Thanks!

2 Answers2

1

This answer discusses some options, but you might run into some browser compatibility issues:

jQuery Ajax File Upload

If it's an option, you might consider doing a regular non async form submission, and making sure your form has enctype="multipart/form-data", but if not, you can try some of the things discussed in that link. Hope this helps

Edit - another approach is to use this jQuery form plugin: http://malsup.com/jquery/form/

In this case, I wasn't using a viewmodel but instead was binding the inputs directly to parameters, but I can't think of any reason why it wouldn't work the exact same way with a typical vm.

Example use:

Front end

    @using (Html.BeginForm("SaveComment", "Comments", FormMethod.Post, new { enctype = "multipart/form-data" }))
    {

        [text and file inputs, submit button]
    }

<script>
$('#addNote form').ajaxForm({
    success: function (response) {
        $.notify(response.result, {
            className: "success",
            position: "top center"
        });

        //reload comments section
        $.ajax({
            url: './Comments/Index',
            data: {
                labelId: '@Model.LabelId',
                orderNumber: '@Model.OrderNumber'
            },
            success: function (response) {
                $('#commentsSection').html(response);
            }
        });
    },
    error: function () {
        $.notify("Failed to save note");
    },
    beforeSubmit: function () {
        $('#addNote').modal('toggle');
    }
});

Back end

public JsonResult SaveComment(string saveNote, string labelId, string orderNumber, string comment, string criteria, HttpPostedFileBase file)
    {
       [text input named saveNote went into saveNote param]
       [file input named file went into HttpPostedFileBase file param]

       [...process other params...]

        var ms = new MemoryStream();
        file.InputStream.CopyTo(ms);
        deliveryItemComment.Attachment = ms.ToArray();
        db.SaveChanges();

        var result = "Note Succesfully Added";
        return Json(new { result = result }, JsonRequestBehavior.AllowGet);
    }
Community
  • 1
  • 1
frax
  • 443
  • 4
  • 10
  • Compatibility with old browsers is not an issue in my case. This link suggests to use form data, which I already use in other placed, but it only allows me to post file itself. While I really need to post file AND fields in the same request. Ideally I would also like to keep model binding on WebAPI2 side, but that's not necessary. – Vladimir Glushkov Mar 11 '16 at 14:14
  • @Dragonheart I ended up needing this myself just a few days later, and this worked quite well: http://malsup.com/jquery/form/ . I was able to create an otherwise normal form that included both regular form data and files, and they were bound to the controller parameters. I see you already got a solution working, but I thought I'd mention this. Thanks – frax Mar 14 '16 at 20:06
  • one of my colleagues has also suggested me to use this plugin some time ago, looks nice and simple on JS side. What I'm unsure of is how to process it on backend. Do you have any example of processing ajax requests generated by this plugin on WebAPI2? – Vladimir Glushkov Mar 15 '16 at 09:54
0

Pretty ugly solution I suppose, but I ended in that way.

JS uses form data:

            var formSerialized = $("formId").serializeArray();

            var data = new FormData();
            var files = $("#fileInputId").get(0).files;
            data.append("File", files[0]);

            $.each(formSerialized, function (i, field) {
                data.append(field.name, field.value);
            });

            $.ajax({
                type: "POST",
                contentType: false,
                processData: false,
                url: "some URL",
                data: data
                }
            });

On WebAPI side I have to separately read form fields and files from form data:

        var myModel = new MyModel();

        var root = HttpContext.Current.Server.MapPath("~/App_Data/");
        var provider = new MultipartFormDataStreamProvider(root);

        await Request.Content.ReadAsMultipartAsync(provider);

        //Maps fields from form data into my custom model
        foreach (var key in provider.FormData.AllKeys)
        {
            var value = provider.FormData.GetValues(key).FirstOrDefault();
            if (value != null && value != "undefined")
            {
                var prop = myModel.GetType().GetProperty(key);

                if (prop != null)
                {
                    prop.SetValue(myModel, value, null);
                }
            }
        }

          //Resaves all files in my custom location under App_Data and puts their paths into list
            var fileNames = new Collection<string>();
           foreach (var file in provider.FileData)
            {
                var fileExt = file.Headers.ContentDisposition.FileName.Split('.').Last().Replace("\"", string.Empty);
                var combinedFileName = string.Format("{0}.{2}", file.Headers.ContentDisposition.Name.Replace("\"", string.Empty), fileExt);
                var combinedPath = Path.Combine(root + "CustomDirectory/", combinedFileName);
                File.Move(file.LocalFileName, combinedPath);
                fileNames.Add(combinedPath);
            }