0

Prologue: I have code that someone else wrote that should upload an image. It used to work but somehow I've broken it. By broken, I mean that when the model ultimately arrives at the controller, the image property is null. I can find an example of where it does work (creating a "scene"). When I compare the working example to the broken one (editing a "scene"), I can not find a reasonable difference between the two. I am, myself, a novice at MVC

Question:

  1. Given the code below, why might the "Image" property of the "SceneCreateViewModel" ultimately arrive at the "SceneEdit" controller as a null?
  2. If unable to answer #1, what troubleshooting steps might I take to help me find the problem.

Form Creation:

@using (Html.BeginForm("Edit", "Scene", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
    {
        @Html.AntiForgeryToken()
        @Html.HiddenFor(m => m.AccountTitlebar.Id)
        @Html.HiddenFor(m => m.AccountTitlebar.Name)
        @Html.HiddenFor(m => m.LocationName )
        @Html.HiddenFor(m => m.CanDelete)

Image upload DIV: (within the form)

<div class="sixteen columns">
    <label>@Html.DisplayNameFor(m => m.Image) (optional)</label>
    @Html.TextBoxFor(m => m.Image, new { type = "file", accept = "image/*" })
</div>

The Model:

public class SceneCreateViewModel
{


    public SceneCreateViewModel( )
    {
        ScheduleSet = new SceneScheduleSet( );
    }

    public AccountTitlebarModel AccountTitlebar { get; set; }
    public string LocationName { get; set; }

    public SceneNewOrUpdate Scene { get; set; }
    public SceneScheduleSet ScheduleSet { get; set; }

    [Display(Name="Image")]
    public HttpPostedFileBase Image { get; set; }
    public int ImageAction { get; set; }

    public long? ImageId { get; set; }
    public bool CanDelete { get; set; }
}

The Controller: (Once here, the image property of the model will be null)

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit( long id, SceneCreateViewModel model )
{
    // CHECK IF MODEL IS VALID
    if (!ModelState.IsValid)
    {
        return Json(new AjaxResult(ValidationErrorsText));
    }

    // ..... More Code below

The Rendered Form : (Relevant parts)

<form action="/Scene/Edit/10185" enctype="multipart/form-data" method="post"><input name="__RequestVerificationToken" type="hidden" value="{deleted}" /><input id="AccountTitlebar_Id" name="AccountTitlebar.Id" type="hidden" value="10" /><input id="AccountTitlebar_Name" name="AccountTitlebar.Name" type="hidden" value="{Deleted}" /><input id="LocationName" name="LocationName" type="hidden" value="{Deleted}" /><input id="CanDelete" name="CanDelete" type="hidden" value="True" />      <div class="row">
            <div class="sixteen columns">
                <h3 class="headline">{Deleted} - Front Deck - Edit Scene</h3><span class="line"></span><div class="clearfix"></div>
            </div>
        </div>
        <div class="row nomargin">
            <div class="sixteen columns">

            </div>
        </div>
        <div class="row">
            <div class="two columns">
                <label for="Scene_OrdinalPosition">Scene</label>
                1
                <input id="Scene_OrdinalPosition" name="Scene.OrdinalPosition" type="hidden" value="1" />
            </div>
            <div class="six columns">
                <label for="Scene_Name">Name</label>
                <input class="form-control" id="Scene_Name" name="Scene.Name" type="text" value="Front Deck" />
            </div>
            <div class="six columns">
                <label for="Scene_Description">Description</label>
                <input class="form-control" id="Scene_Description" name="Scene.Description" type="text" value=" " />
            </div>
        </div>
        <div class="row">
                <div class="sixteen columns">
                    <label>Image (optional)</label>

                    <input accept="image/*" id="Image" name="Image" type="file" value="" />
                </div>
        </div>

A piece of JQuery that I found on the page.

$(function() {
    $('form').submit(function() {
        // SENDING THE MESSAGE
        $('#SceneEdit_popup').text(' Processing, please wait....');
        $('#SceneEdit_popup').bPopup();

        // WAIT FOR GATEWAY TO RECEIVE MESSAGE
        $.ajax({
            type: this.method,
            url: this.action,
            data: $(this).serialize(),
            success: function(data) {

                // AFTER MESSAGE RECEVIED, SENDING TO DEVICES
                var message = "Syncing devices, please wait...";
                if (data.Success) {
                    $('#SceneEdit_popup').text(message);
                } else {
                    message += "<p>" + data.Error + "</p>";
                    message += '<button type="button" ' +
                        'onclick="$(\'#SceneEdit_popup\').bPopup().close();">OK</button>';
                    $('#SceneEdit_popup').html(message);
                }

            },
            error: function() {
                // COMMUNICATION WAS LOST TO THE SERVER
                var message = "<p>Internet connection was lost.</p>";
                message += '<button type="button" ' +
                    'onclick="$(\'#SceneEdit_popup\').bPopup().close();">OK</button>';
                $('#SceneEdit_popup').html(message);
            }
        });

        return false;
    });
SteveJ
  • 3,034
  • 2
  • 27
  • 47
  • What does the **rendered** HTML of the form look like? – Dai Oct 05 '15 at 22:51
  • The code should work fine, but the fact your POST method has `return Json(..)` suggests you might in fact be doing an ajax post rather than a normal submit? –  Oct 05 '15 at 22:53
  • @Dai Added rendered code, thanks. – SteveJ Oct 05 '15 at 22:59
  • @Stephen I think that the json return might be a relic of my improperly modifying the code - but I am no longer certain. However, if it were an Ajax call, how would I expect it to be 'hooked' to the submit button? Presumably, the form is intact and the submit button should post to the proper controller as is. If there were Ajax in there, would I expect to see two posts to the controller or would the Ajax interrupt the standard html post some how? – SteveJ Oct 06 '15 at 00:49
  • That would depend on if the script were cancelling the default submit or not. If you are posting the form using ajax, you need to use `FormData` to include files (refer [this answer](http://stackoverflow.com/questions/29293637/how-to-append-whole-set-of-model-to-formdata-and-obtain-it-in-mvc/29293681#29293681)) but if not, your code should work fine (unless you have also included a hidden input for `Image` but I cant see that in your code). You also have not shown a submit button in the code so cant be sure what your doing. –  Oct 06 '15 at 01:12
  • I think you might be on to something. I looked further and found some jquery that seems to handle form.submit. That may be my problem. So now I just have to figure out how to get the fields from my form back into the model - which I assume is the FormData that you are referring to. – SteveJ Oct 06 '15 at 13:21
  • @Stephen Thank you, that did the trick. By changing "data: $(this).serialize" to "data: FormData", I now get the image to to the controller. If you want to place this in the answer, I'll mark it as such so that you get the rep for it. If not, I guess that I should delete the question as I don't know that it is terribly useful for anyone else. – SteveJ Oct 06 '15 at 13:29
  • I know this is probably not it. But to clean it up, remove the "id long" from "public ActionResult Edit( long id, SceneCreateViewModel model )" it should be "public ActionResult Edit( SceneCreateViewModel model )" unless you are handling id somewhere I can not see. Are you passing the same SceneCreateViewModel into the view, I think you are just making sure. – Seabizkit Oct 06 '15 at 13:31
  • @Seabizkit Thank you – SteveJ Oct 06 '15 at 13:50

1 Answers1

1

In order to upload files using ajax, you can use FormData and set the appropriate ajax options (using form.serialize() will not include file inputs)

$('form').submit(function() {
    var formdata = new FormData($(this).get(0));
    ....
    $.ajax({
        type: this.method,
        url: this.action,
        data: formdata,
        processData: false,
        contentType: false, 
        success: function(data) {
            ....