POST a Model with an Image Attachment

MVC POST a Model with Image

When POSTing a model you may need to stream some file content; like an image. The template controller will need to be modified to accomplish this. For this example, we will look at the source code I used to add ‘products’ to theCosmics.com. You will notice the use of the [Authorize] data annotation, which means only a logged in user can submit the POST.

The Product Model

Note that the model has properties for ImageData and ImageMimeType. If you need to update your models, be sure to enable data migrations.

namespace theCosmics.Models
{
    public class Product
    {
        // Product Details
        public int ID { get; set; }
        [Required]
        public string Category { get; set; }
        [Required]
        public string ProductName { get; set; }
        [Required]
        public decimal Price { get; set; }
        [Required]
        public string ProductHeader { get; set; }
        [Required]
        [DataType(DataType.MultilineText)]
        public string LongDescription { get; set; }
        [Required]
        public string TrackingURL { get; set; }

        // Image
        public byte[] ImageData { get; set; }
        public string ImageMimeType { get; set; }

        // Page Hit Tracking
        public int Hits { get; set; }

        // Featured Flag
        public bool Featured { get; set; }
    }
}

The Create View

You will need to modify the default template’s Html form to include the multipart/form-data. Right now my ListItemsĀ are hardcoded so please forgive that bit of rough code.

@model theCosmics.Models.Product

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm("Create", "Product", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Product</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Category)
        </div>
        @{
            // TODO: Make this a method to dynamically get/post the data
            List<SelectListItem> listItems = new List<SelectListItem>();
            listItems.Add(new SelectListItem
                {
                    Text = "Apparel",
                    Value = "Apparel"
                });
            listItems.Add(new SelectListItem
            {
                Text = "Comics",
                Value = "Comics"
            });
            listItems.Add(new SelectListItem
            {
                Text = "Gadgets",
                Value = "Gadgets"
            });
            listItems.Add(new SelectListItem
            {
                Text = "Games",
                Value = "Games"
            });
            listItems.Add(new SelectListItem
            {
                Text = "Movies",
                Value = "Movies"
            });
        }

        <div class="editor-field">
            @Html.DropDownListFor(model => model.Category, listItems, "-- Category --")
            @Html.ValidationMessageFor(model => model.Category)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ProductName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ProductName)
            @Html.ValidationMessageFor(model => model.ProductName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Price)
            @Html.ValidationMessageFor(model => model.Price)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ProductHeader)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ProductHeader)
            @Html.ValidationMessageFor(model => model.ProductHeader)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.LongDescription)
        </div>
        <div class="editor-field">
            @Html.TextAreaFor(model => model.LongDescription, new {style = "width: 50%; height: 200px;"})
            @Html.ValidationMessageFor(model => model.LongDescription)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.TrackingURL)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.TrackingURL)
            @Html.ValidationMessageFor(model => model.TrackingURL)
        </div>
        <div>
            <label>Image</label>
            <p>Upload new image: <input type="file" name="Image" /></p>
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Product", "Admin", new { Category = @ViewBag.Category } , null)
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Modifying the Edit Method for the Product Controller

//
// POST: /Product/Create
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product, HttpPostedFileBase image)
{
	if (ModelState.IsValid)
	{
		if (image != null)
		{
			product.ImageMimeType = image.ContentType;
			product.ImageData = new byte[image.ContentLength];
			image.InputStream.Read(product.ImageData, 0, image.ContentLength);
		}

		product.Hits = 0;
		db.Products.Add(product);
		db.SaveChanges();

		// Send user back to the appropriate admin
		return RedirectToAction("Product", "Admin", new { Category = product.Category });
	}

	return View(product);
}

In the code (above) I have modified the parameters of the Create method to include a second parameter
HttpPostedFileBase. As long as the image is not null, it will save two columns of data to the database; the first column is the MIME type (jpg, gif, png, etc.) and the second column is the byte data of the image. NOTE: You will need to modify your EDIT controller method, too.

Using the Image

The webapp can use the SQL data to reassemble the data into a usable image using a new controller.

//
// GET: /Product/GetImage/5

public FileContentResult GetImage(int id)
{
	Product product = db.Products.Find(id);
	if((product.ImageData != null) && (product.ImageMimeType != null))
	{
		return File(product.ImageData, product.ImageMimeType);
	}
	else
	{
		return null;
	}
}

This controller will accept the model’s ID and return the image for that product.

Further Reading

Pro ASP.NET MVC 2 Framework

  • Much of the code (above) is derived from this book.