1

I have a huge data object within which should map to a view model through controller parameter. I also have a list of files (photos) that i need to capture within an array in the aforementioned data object.

My Model:

public class TestPerson
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Place { get; set; }
    public int Age { get; set; }
    public byte[] File { get; set; }

    //[FileSize(10240)]
    //[FileTypes("jpg,jpeg,png")]
    //public HttpPostedFileBase File { get; set; }
}

My Controller Method:

    public void SavePersonData(HttpPostedFileBase personPhoto, TestPerson person)
    {

        var dataObject = Request.Form["person"];
        var serializer = new JavaScriptSerializer();
        TestPerson personReport = serializer.DeserializeObject(dataObject, typeOf(TestPerson));

        System.Console.WriteLine("dummy line");
    }

My .cshtml page:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.3.0.js"></script>

<div id="testKo">
    Name: <input type="text" data-bind="value: Name"/>
    Place: <input type="text" data-bind="value: Place" /><br />
    Age: <input type="text" data-bind="value: Age" /><br />

    <input type="button" id="submitForm" class="btn-lg" value="Submit" />
</div>

<form id="photoForm" method="post" enctype="multipart/form-data">
    <input type="hidden" id="personId" name="person" />
    <input type="file" id="personPhotoId" name="personPhoto" />
</form>

<script>
    $(document).ready(function () {

        $(document).ready(function () {

            var element = document.getElementById("testKo");
            ko.applyBindings(viewModel, element);

            $("#submitForm").on("click", function () {

                var vmData = ko.toJSON(viewModel);
                //$("#personId").val({ person: vmData });
                var formData = new FormData($("#photoForm")[0]);

                formData.append("person", vmData, "person");

                //var data = new FormData();
                //data.append("vmData", vmData);
                //data.append("photo", $("#personPhoto").get(0).files[0])

                $.ajax({
                    type: "POST",
                    url: '@Url.Action("SavePersonData", "Home")',
                    //cache: false,
                    //async: false,
                    contentType: false,
                    processData: false,
                    enctype: "multipart/form-data",
                    dataType: "json",
                    //data: {
                    //    testPerson: vmData
                    //},
                    //data: {
                    //    personPhoto: formData,
                    //    person: vmData
                    //},
                    data: formData,
                    success: function (data) {
                        alert("success!");
                    }
                });
            });
        });

        function generatedViewModel() {
            var self = this;

            self.Id = ko.observable("1");
            self.Name = ko.observable();
            self.Place = ko.observable();
            self.Age = ko.observable();
            self.File = ko.observable();
        }
        var viewModel = new generatedViewModel();

    });
</script>

Is there any way i can receive both the parameters in my controller action method with this code? Or do i need to tweak it in any way? I just need to send all the content including file / image upload data and non-form data object to my controller method.

Web Developer
  • 333
  • 4
  • 17
zapper
  • 181
  • 3
  • 15
  • it is possible without ajax call. you have to do full post back. – saif iqbal Apr 15 '16 at 09:30
  • You need to use HttpPostedFile base , there is no other way except for in the action result you do var myFile = Request.Files[i]; – REDEVI_ Apr 15 '16 at 09:31
  • 1
    Otherway is to convert the file into base64string and send it as a model property. – saif iqbal Apr 15 '16 at 09:32
  • @Saif, i cannot convert to base64 as Image is too big. I have to use ajax only as my actual page is a 1000 line code with too many knockout data bindings, etc. It's not inside a form element or i cannot use razor. – zapper Apr 15 '16 at 09:36
  • If you remove ajax call and do full post back then you will receive file at the controller action. – saif iqbal Apr 15 '16 at 09:39
  • @REDEVI_, how do i sent my TestPerson model object then? I either need to send File and Model as separate parameters or if it's possible, send Model with a property for file within it. – zapper Apr 15 '16 at 09:46
  • what is the problem with having separate params ? – REDEVI_ Apr 15 '16 at 09:47
  • @REDEVI_, as per my above code, i can get value of the "personPhoto" parameter. But my object for the "person" parameter does not come mapped as a view model. I am only getting it as Request.Form["person"] which needs to be Deserialized, throwing errors which i am trying to avoid. Or is there any good way to deserialize Json object and map the properties to view model? – zapper Apr 15 '16 at 09:53
  • is it a mandate yo use jquery ? Since you are already posting data , post the file as input type file ? – REDEVI_ Apr 15 '16 at 09:57
  • Both will be posted ie form data as well as file – REDEVI_ Apr 15 '16 at 09:57
  • @REDEVI_, how do you do that? i cannot use Html.BeginForm unfortunately... I cannot use any razor in my page. If i used razor, for some data and jquery object for some data, how do i combine them and send to controller? – zapper Apr 15 '16 at 10:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109234/discussion-between-redevi-and-zapper). – REDEVI_ Apr 15 '16 at 10:02
  • [how to append whole set of model to formdata and obtain it in MVC](http://stackoverflow.com/questions/29293637/how-to-append-whole-set-of-model-to-formdata-and-obtain-it-in-mvc/29293681#29293681) –  Apr 15 '16 at 11:13

2 Answers2

1

Well, the best solution for this would be to make 2 synchronized ajax calls one within the other. Send the "personPhoto" data first and then convert it into byte array and set it as a Session variable and in the success event of this ajax call, make another ajax call to the main method where you process the Form / Model data and use the cached session object for the "personPhoto".

Replace your ajax code with the following:

            var data = new FormData($("#photoForm").get(0));

            $.ajax({
                type: "POST",
                url: '@Url.Action("CacheUploads", "Home")',
                data: data,
                processData: false,
                contentType: false,
                dataType: "json",
                success: function () {

                    var vmData = ko.toJS(viewModel);

                    $.ajax({
                        type: "POST",
                        url: '@Url.Action("SavePersonData", "Home")',
                        data: { person: vmData },
                        success: function (data) {

                        }
                    });
                }
            });

And replace your controller methods with the following:

    public void SavePersonData(TestPerson person)
    {
        // You no longer need to deserialize as you'll have data properly mapped to the TestPerson object.

        //var dataObject = Request.Form["person"];
        //var serializer = new JavaScriptSerializer();
        //object personReport = serializer.DeserializeObject(dataObject);

        person.File = (byte[])Session["UploadedPhoto"];

        System.Console.WriteLine("dummy line");
    }

    public JsonResult CacheUploads(HttpPostedFileBase personPhoto)
    {
        byte[] photoAsBytes = null;

        using (var binaryReader = new BinaryReader(personPhoto.InputStream))
        {
            photoAsBytes = binaryReader.ReadBytes(personPhoto.ContentLength);
        }

        Session.Add("UploadedPhoto", photoAsBytes);

        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }

There is no other way you can handle FormData and Non-Form ViewModel data together in a single ajax call. This is the closest I can get from a solution perspective.

Web Developer
  • 333
  • 4
  • 17
Ananth G
  • 36
  • 4
  • Awesome!... This is way out of the box and genius!. Thanks for the solution. Totally appreciate it!. – zapper Apr 15 '16 at 18:09
-1

use this.

@using (Html.BeginForm("Action", "Controller", System.Web.Mvc.FormMethod.Post, new { @id = "formid", enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken()

 <input type="hidden" id="personId" name="person" />
<input type="file" id="personPhotoId" name="personPhoto" />

 <input type="submit" id="submitForm" class="btn-lg" value="Submit" />
}
saif iqbal
  • 219
  • 1
  • 9