Monday, February 14, 2011

Validating an uploaded image file using ASP.NET MVC and DataAnnotations

I recently wrote a feature for a small MVC app that required me to allow a user to upload an image to my web server. This got me wondering "how does a web server know if the file I uploaded is an image file?" There are a few issues to think through:
  • Not every operating system implements file name conventions the same way. Rather, we shouldn't assume that we know anything about the format of the file name we'll receive, it's just a string -- and it may be an empty string.
  • We can't assume that the browser used to upload the file will accurately identify the content type of the uploaded file.
  • The user may upload an image type that they recognize, but our web server does not recognize as an image.
Since this is an ASP.NET MVC app, I started by identifying the Model I want to deal with. Here's my UploadedFileModel:
    public class UploadedFileModel
    {
        [Required, ImageType]
        public HttpPostedFileBase Image { get; set; }
    }
Pretty simple, just one property, nothing too fancy. If you're familiar with DataAnnotations, you'll recognize the Required validator right away. This just checks to see if the property is null when the model is bound to my action.

The other thing one might notice is the ImageType attribute. This is where I put my secret sauce. Take a look:
  public class ImageTypeAttribute : ValidationAttribute
  {
    public override bool IsValid(object value)
    {
      // don't duplicate the Required validator
      if (value == null)
        return true;

      // is it the type we expect?
      if (!(value is HttpPostedFileBase))
        return false;

      // We got this far, is it a recognizable image type?
      var fileStream = ((HttpPostedFileBase) value).InputStream;
      try
      {
        var image = Image.FromStream(fileStream);
      }
      catch
      {
        return false;
      }
      return true;
    }

    public override string FormatErrorMessage(string name)
    {
      return string.Format("{0} is not a valid image format.", name);
    }
  }
At this point, I'm effectively asking the web server if it recognizes whether the posted file as an image type. Yes? Hooray! No? We'll tell the user that it didn't look like a valid image type to us.

Once all that is wired up, we can let the action just do action stuff:
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Insert(UploadedFileModel viewModel)
    {
      if (!ModelState.IsValid)
        return View("Create");

      var model = ImageUtilities.SaveImage(viewModel.Image);

      if (model == null)
      {
        ModelState.AddModelError("Image",
            "File could not be saved, this could happen if the client did not have permission to save the file to disk.");
        return View("Create");
      }

      repository.Add(model);
      return RedirectToAction("Details", new {id = model.Name});
    }
And that's pretty much it. I tried to take advantage of as much default behavior as I could, and I ended up with a validator that I can reuse.

1 comment:

Memtech said...

This is awesome!! really helpful for me. Thanks for sharing with us. Following links also helped me to complete my task.

http://www.mindstick.com/Articles/0a636769-717f-408a-8070-659986d700b5/?Upload%20File%20using%20Model%20Validation%20in%20ASP%20NET%20MVC

http://stackoverflow.com/questions/6388812/how-to-validate-uploaded-file-in-asp-net-mvc