0

I've done a little research on this and it always says the same thing, make sure you have enctype for "multipart/form-data" when trying to do an ajax post with a file to an mvc controller. As far as I can tell, I have done that yet still when I run through the debugger, HttpPostedFileBase always returns null. I'm not seeing where I'm missing something probably very obvious.

Here is the razor view form.

@using (Html.BeginForm("Edit", "Story", FormMethod.Post, new { encType = "multipart/form-data" }))
            {
                @Html.AntiForgeryToken()
                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                @Html.HiddenFor(x => x.StoryId)

                <div class="form-group">
                    <div class="row">
                        <div class="col-sm-4">
                            <div class="card">
                                <div class="card-body">
                                    <p class="card-text">Add/Edit cover art to your story</p>
                                    <img class="img-thumbnail" src="~/Imgs/@Model.CoverArt" />
                                    <input type="file" id="file" name="file" class="form-control" />
                                </div>
                            </div>
                        </div>
                        <div class="col-sm-8"></div>
                    </div>
                </div>

                <div class="form-row">
                    <div class="col-6">
                        @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label text-muted" })
                        @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
                        @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
                    </div>
                    <div class="col-3">
                        @Html.LabelFor(x => x.Genre, htmlAttributes: new { @class = "control-label text-muted" })
                        @Html.DropDownListFor(x => x.Genre, new SelectList(Model.Genres, "Id", "Name"), new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" })
                    </div>
                    <div class="col-3">
                        @Html.Label("Visibility", htmlAttributes: new { @class = "control-label text-muted" })
                        <i class="fa fa-question-circle-o" data-toggle="popover" title="What does visibility mean?" data-placement="right" data-content="Visibility determines whether other users can see this story or not. Public means any other user can read, vote, and review the story. You can change this to private, which will hide the story from other users. You, as the author, will be able to see all of your stories - both public and prviate - under your account."></i>
                        @Html.DropDownListFor(x => x.Visibility, new SelectList(Model.Visibilities, "Id", "Name"), new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.Visibility, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-row mt-3">
                    <div class="col-6">
                        @Html.Label("Story Type", htmlAttributes: new { @class = "control-label text-muted" })
                        @Html.DropDownListFor(x => x.StoryType, new SelectList(Model.StoryTypes, "Id", "Name"), new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.StoryType, "", new { @class = "text-danger" })
                    </div>
                    <div class="col-6">
                        @Html.Label("Target Age Range", htmlAttributes: new { @class = "control-label text-muted" })
                        @Html.DropDownListFor(x => x.StoryAgeRange, new SelectList(Model.StoryAgeRanges, "Id", "Name"), new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.StoryAgeRange, "", new { @class = "text-danger" })
                    </div>
                </div>

                <div class="form-row mt-5">
                    <div class="col-12">
                        @Html.Label("Write below", htmlAttributes: new { @class = "control-label text-muted" })
                        @Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { @class = "form-control" } })
                        @Html.ValidationMessageFor(model => model.Content, "", new { @class = "text-danger" })
                    </div>
                </div>

                <div class="form-row mt-3">
                    <div class="btn-group">
                        @Html.ActionLink("Cancel", "Index", "Story", null, new { @class = "btn btn-red" })
                        <input type="button" id="editStoryBtn" value="Edit story" class="btn btn-blue" />
                        <a class="btn btn-link ml-auto" href="@Url.Action("PublishingRules", "Legal")">Publishing Guidelines</a>
                    </div>
                </div>

            }

Here is the javascript for the ajax call (and the function I use to get the filename):

//get the filename of the uploaded file
                var fullPath = document.getElementById('file').value;
                if (fullPath) {
                    var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
                    var filename = fullPath.substring(startIndex);
                    if (filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
                        filename = filename.substring(1);
                    }
                }

                //create the new story object
                var editedStory = {
                    __RequestVerificationToken: token,
                    StoryId: storyId,
                    CoverArt: filename,
                    Title: $("input[name='Title']").val(),
                    Content: CKEDITOR.instances.Content.getData(),
                    Genre: $('#Genre').val(),
                    Visibility: $('#Visibility').val(),
                    StoryType: $('#StoryType').val(),
                    StoryAgeRange: $('#StoryAgeRange').val(),
                    WordCount: finalWordCount
                }

                //post the edited story to the controller
                $.ajax({
                    url: "/story/edit/" + storyId,
                    type: 'POST',
                    data: editedStory,
                    success: function (data) {
                        window.location.href = data.Url;
                    },
                    error: function (error) {
                        console.log("error is " + error);
                    }
                });

And here is the controller.

//POST story/edit/5
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Edit (EditStoryViewModel viewModel, HttpPostedFileBase file)
{
            //confirm model state is valid
            if (ModelState.IsValid)
            {
                //find the correct story by the id and include the reference tables
                var StoryToEdit = dbContext.Stories.SingleOrDefault(x => x.Id == viewModel.StoryId);

                //verify file upload
                if (file != null)
                {
                    StoryToEdit.CoverArt = StoryToEdit.Id + Path.GetExtension(file.FileName);
                    file.SaveAs(Server.MapPath("/Imgs/") + StoryToEdit.CoverArt);
                }

                StoryToEdit.Title = viewModel.Title;
                StoryToEdit.Content = viewModel.Content;
                StoryToEdit.GenreId = viewModel.Genre;
                StoryToEdit.StoryAgeRangeId = viewModel.StoryAgeRange;
                StoryToEdit.StoryTypeId = viewModel.StoryType;
                StoryToEdit.VisibilityId = viewModel.Visibility;
                StoryToEdit.WordCount = viewModel.WordCount;
                StoryToEdit.UpdatedAt = DateTime.Now;

                dbContext.Entry(StoryToEdit).State = EntityState.Modified;
                dbContext.SaveChanges();

                var redirectUrl = new UrlHelper(Request.RequestContext).Action("Details", "Story", new { id = StoryToEdit.Id });

                return Json(new { Url = redirectUrl });
            }
            else
            {
                return View(viewModel);
            }
        }

What's going wrong because the file is never being saved (everything else works).

J.G.Sable
  • 1,258
  • 4
  • 26
  • 62
  • You need to use `FormData` Refer this post [How to upload files using ajax to asp.net mvc controller action](https://stackoverflow.com/questions/38703182/how-to-upload-files-using-ajax-to-asp-net-mvc-controller-action/38703844#38703844) – Shyju Aug 15 '18 at 20:54
  • `enctype` has nothing at all to do with upload files using ajax –  Aug 15 '18 at 21:55

1 Answers1

0

When you want to post multipart form-data using XMLHttpRequest or a wrapper that uses it (like jQuery). You need to create a FormData object and add the files and formdata you want to send to the server. Only then the data is being sent accordingly to the server.

There are already some answers on how to use the FormData object

Sending multipart/formdata with jQuery.ajax

https://www.mkyong.com/jquery/jquery-ajax-submit-a-multipart-form/