Uploading files
The improvements made in model binding from ASP.NET MVC 2 helps 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)
{
if (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
converting 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 CustomFileModelBinder : ByteArrayModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var file = controllerContext.HttpContext.Request.Files[bindingContext.ModelName];
if (file != null)
{
if (file.ContentLength != 0 && !String.IsNullOrEmpty(file.FileName))
{
var fileBytes = new byte[file.ContentLength];
file.InputStream.Read(fileBytes, 0, fileBytes.Length);
return fileBytes;
}
return null;
}
return base.BindModel(controllerContext, bindingContext);
}
}
Listing 3. CustomFileModelBinder
In the BindModel method we are checking 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 CustomFileModelBinder());
}
Listing 4. Registering CustomFileModelBinder 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 CustomFileModelBinder
Summary
Custom model binders are a 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
HttpPostedFileBase into byte array.
I'm a software developer from south tip of India. I spent most of the time in learning new technologies. I've a keen interest in client-side technologies especially JavaScript and admire it is the most beautiful language ever seen.
I like sharing my knowledge and written some non-popular articles. I believe in quality and standards but blames myself for lagging them.
I believe in small things and they makes me happy!