Model binding posted file to byte array

Uploading files

The improvements made in model binding from ASP.NET MVC 2 helps us to easily map the uploaded files to models. The HttpPostedFileBaseModelBinder is the one that maps the file(s) available in the Request.Files to single or collection of HttpPostedFileBase instances. Whenever you have HttpPostedFileBase as a parameter in an action method or as a property in the model the HttpPostedFileBaseModelBinder comes to play and does the magic.

An example is,

public ActionResult Upload(MyModel model, HttpPostedFileBase file)
{
	// the uploaded file is automatically available in the HttpPostedFileBase by the built-in
	// model binder
	if (file != null && file.ContentLength > 0) 
	{
		var fileName = Path.GetFileName(file.FileName);
		var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
		file.SaveAs(path);
  	}
            
  	return RedirectToAction("Index");
}

Listing 1. HttpPostedFileBase as action parameter

But sometimes we need little more convenience for ex. when an uploaded file needs to be persisted in database, we would love to have the uploaded file automatically converted into a byte array and available right in the action.

public ActionResult Upload(MyModel model, byte[] file)
{
	...
}

Listing 2. byte[] as action parameter

In this article we are going to see how we can achieve that by extending the built-in ByteArrayModelBinder.

ByteArrayModelBinder

The ByteArrayModelBinder is used to convert the base64 encoded string into byte array and it doesn't care about convering the uploaded file into byte array unless we do something about that!

We can't just use a new binder for binding byte array unless we remove the ByteArrayModelBinder from Binders collection. Instead of adding a brand new model binder that will take care of only converting the file into byte array we can extend the ByteArrayModelBinder to take of this conversion along with the usual job.

So here is our custom ByteArrayModelBinder.

CustomByteArrayModelBinder

public class CustomByteArrayModelBinder : ByteArrayModelBinder
{
	public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
	{
		var file = controllerContext.HttpContext.Request.Files[bindingContext.ModelName];

		if (file != null)
		{
			if (file.ContentLength > 0)
			{
				var fileBytes = new byte[file.ContentLength];
				file.InputStream.Read(fileBytes, 0, fileBytes.Length);
				return fileBytes;
			}

			return null;
		}

		return base.BindModel(controllerContext, bindingContext);
	}
}

Listing 3. CustomByteArrayModelBinder

In the BindModel method we are cheking out whether the Request.Files collection contains a file with the model name and if yes then we are reading the file, converting it into byte array and returning it else we are just by-passing the call to the base ByteArrayModelBinder.

Last but not least we have to throw away the ByteArrayModelBinder from the Binders collection and add our custom made one to it.

protected void Application_Start()
{
    ...
	ModelBinders.Binders.Remove(typeof(byte[]));
	ModelBinders.Binders.Add(typeof(byte[]), new CustomByteArrayModelBinder());
}

Listing 4. Registering CustomByteArrayModelBinder in Global.asax.cs

Yeah, we are done! now we can do things like this,

public class Profile
{
	public string Name {get; set;}
	public int Age{get; set;}
	public byte[] photo{get; set;}
}

[HttpPost]
public ActionResult Save(Profile profile)
{
	if(ModelState.IsValid)
	{
		db.Save(profile);
		return RedirectToAction("Home");
	}

	return View();
}

Listing 4. Using CustomByteArrayModelBinder

In the Save action, the uploaded photo is automatically converted into byte array and stored in the photo property by our CustomByteArrayModelBinder. Isn't that cool? Yes, ofcourse!

Summary

Custom model binders are the great way to go when you want to customize the binding process and reuse it in more than one place. Our custom ByteArrayModelBinder helps to avoid manually convert the file stream from the HttpPostedFileBase into byte array.

blog comments powered by Disqus