Question

I have the following model:

public class Photo
{
    public int PhotoId { get; set; }
    public byte[] ImageData { get; set; }
    public DateTime DateUploaded { get; set; }
    public string Description { get; set; }
    public bool IsActive { get; set; }

}

I would like the user to be able to enter the details for the photo then post the model the the controller. My controller action is as follows:

[HttpPost]
    public ActionResult Create(WilhanWebsite.DomainClasses.Photo photo)
    {
        if (ModelState.IsValid)
        {
            photo.DateUploaded = DateTime.Now;
            _context.Photos.Add(photo);
            _context.SaveChanges();

            return RedirectToAction("Index");
        }
        //we only get here if there was a problem
        return View(photo);
    }

My view is as follows:

@using (Html.BeginForm()) 
{
@Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>Photo</h4>
    <hr />
    @Html.ValidationSummary(true)

    <div class="form-group">
        @Html.LabelFor(model => model.ImageData, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            <input type="file" name="uploadImages" class="input-files" />
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.DateUploaded, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.DateUploaded)
            @Html.ValidationMessageFor(model => model.DateUploaded)
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Description)
            @Html.ValidationMessageFor(model => model.Description)
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.IsActive, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.IsActive)
            @Html.ValidationMessageFor(model => model.IsActive)
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
</div>
}

The view is displayed ok and allows the user to select a file from their local disk and enter the other model details. My problem is that although the model is posted to the controller ok, the Description, Date and IsActive flags are populated ok - the Image data is null.

Could anyone please let me know what I need to change so that the byte array for the photo is included in the model posted to the controller?

Was it helpful?

Solution

The file input in your view has a name uploadImages. I can't see a property with this name in your view model. You seem to have some ImageData property which is a byte array, but there doesn't seem to be a corresponding input field with this name in your view.

This explains why you get null. You could make this work by respecting the convention. So for example if you intend to have such an input field in your view:

<input type="file" name="uploadImages" class="input-files" />

then make sure that you have a property on your view model with the same name. And of course of type HttpPostedFileBase.

public HttpPostedFileBase UploadImages { get; set; }

Also in your view make sure you are setting the proper content type of multipart/form-data:

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

You probably might want to go through the following blog post to better familiarize yourself with the basics of how uploading of files work in ASP.NET MVC. I've also written a similar answer here that you might consult.

So once you add the HttpPostedFileBase property with the UploadImages name in your view model you could adapt your controller action to read the byte array and store it your ImageData property:

[HttpPost]
public ActionResult Create(WilhanWebsite.DomainClasses.Photo photo)
{
    if (ModelState.IsValid)
    {
        photo.DateUploaded = DateTime.Now;
        photo.ImageData = new byte[photo.UploadImages.ContentLength];
        photo.UploadImages.Read(photo.ImageData, 0, photo.ImageData.Length);

        _context.Photos.Add(photo);
        _context.SaveChanges();

        return RedirectToAction("Index");
    }

    //we only get here if there was a problem
    return View(photo);
}

Now bear in mind that this is an absolutely awful solution. Never do that in a real world application. In a correctly designed application you will have a view model that your controller action will take as a parameter. You're never gonna directly use your autogenerated EF model as parameter to your controller action. You will have a view model with the HttpPostedFileBase property which will be mapped to your domain model.

So in a properly designed application you will have a PhotoViewModel view model class that your controller action will take.

OTHER TIPS

Change this line:

@using (Html.BeginForm()) 

To this:

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

Then change:

<input type="file" name="uploadImages" class="input-files" />

To:

<input type="file" name="ImageData" class="input-files" />

Then change this line:

 public byte[] ImageData { get; set; }

To this:

 public HttpPostedFileBase ImageData { get; set; }

Finally, use some code like this to read the image into a byte array:

 var bs = new byte[ImageData.ContentLength];
 using (var fs = ImageData.InputStream)
 {
     var offset = 0;
     do
     {
         offset += fs.Read(bs, offset, bs.Length - offset);
     } while (offset < bs.Length);
 }

View:

@using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
   ...
   <input type="file" id="ImageFile" name="ImageFile" .../>
   ...
}

Controller:

[HttpPost]
public ActionResult Create(Photo photo, HttpPostedFileBase ImageFile)
{
    byte[] buf = new byte[ImageFile.ContentLength];
    ImageFile.InputStream.Read(buf, 0, buf.Length);
    photo.ImageData = buf;
    ...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top