File uploads and MVC Controllers

August 27th, 2010

Last week I had to implement some functionality to support uploading images to an MVC 2 application and then subsequently display them. Naturally there are a mountain of blog posts on this topic but I was unable to find anything comprehensive that covered everything that I needed on both the client and server side. What follows is my attempt to fill that hole.

My constraints were:

  1. I did not have an HTML form (the application I was working with is very “AJAX heavy”).
  2. I needed to pass parameters in addition to the file (the ID of the entity that I want to associate the file with).

I used the jQuery.upload plugin too assist me with both of these. Obviously that means I am also using jQuery.

Let’s look at the code. Here’s the Content from my View:

Select File to Upload: <input id="file" name="file" type="file" />
<img id="fileView" />

<script type="text/javascript">
    $(document).ready(function () {
        $('#file').change(function () {
            var data = { ID: "b53dd6b4-f24c-4450-bf6a-246e5835a125" };
            $(this).upload(
                "FileUpload/AttachFileToEntity",
                data,
                function () {
                    $("#fileView").attr("src", "FileUpload/GetFileDataFromEntity/" + data.ID);
                }
            );
        });
    });
</script>

I am simply attaching an change event handler the the <input type=”file” /> control. The event handler does two things:

  1. Uploads the selected file
  2. Displays it. I am assuming that you are uploading an image (but not verifying this)

Note that the name attribute of the input control must be set and it must match the name of the HttpPostedFileBase parameter in the AttachFileToEntity Controller Action. Speaking of the Controller here it is:

public class FileUploadController : Controller
{
    private readonly EntityRepository _entityRepository = new EntityRepository();

    public ActionResult Get()
    {
        return View();
    }

    public ActionResult AttachFileToEntity(Guid ID, HttpPostedFileBase file)
    {
        var entity = _entityRepository.Get(ID);

        entity.FileData = GetFileData(file);

        return Content("File saved");
    }

    private byte[] GetFileData(HttpPostedFileBase file)
    {
        var length = file.ContentLength;
        var fileContent = new byte[length];

        file.InputStream.Read(fileContent, 0, length);

        return fileContent;
    }

    public FileResult GetFileDataFromEntity(Guid ID)
    {
        return File(_entityRepository.Get(ID).FileData, "image");
    }
}

The imaginatively named Entity could not be simpler:

public class Entity
{
    public Guid ID { get; set; }

    public byte[] FileData { get; set; }
}

In a production scenario you are using probably using an O/RM such as NHibernate. In this case you will want to deviate from what I have above and store the uploaded file in a different entity. This is to avoid having to load the binary data for the file each time you access the entity. Note that NHibernate 3 supports (to a limited degree) lazy loading properties. However even if you are utilizing this feature you still want to make sure that the binary data is stored in a different table in the underlying RDBMS so that any full table scans (for example if you are performing some type of aggregation) don’t end up reading mountains of irrelevant data.

The one thing that you are missing is the Repository. Rather than introduce the complexity of data access I am simply using a static Dictionary to store the data:

public class EntityRepository
{
    private static readonly IDictionary<Guid, Entity> _entities = new Dictionary<Guid, Entity>
    {
        { new Guid("b53dd6b4-f24c-4450-bf6a-246e5835a125"), new Entity { ID = new Guid("b53dd6b4-f24c-4450-bf6a-246e5835a125") }}
    };

    public Entity Get(Guid ID)
    {
        return _entities[ID];
    }

    public void Save(Entity entity)
    {
        if (entity.ID == Guid.Empty) entity.ID = Guid.NewGuid();

        if (_entities.ContainsKey(entity.ID)) _entities[entity.ID] = entity;
        else _entities.Add(entity.ID, entity);
    }
}

That’s everything you need. Hope that you find this useful.

  • alex pascanu

    COngratulation!
    This is a usefull tuttorial for uploading files with asp.net mvc

  • bimal

    Dear Jason

    Can we upload file automaticaly without click on browse ?

    User will open website and a file is upload by default form a default folder.

  • http://guild3.com Jason Grundy

    @bimal As far as I know this is not possible. Browsers, well apart from IE anyway ;), are designed to protect the resources on your computer. If it was possible for a website to access arbitrary files then obviously the security implications would be significant.

    However you are going to be able to do more with files as HTML5 becomes more pervasive. For example here’s some developer documentation for FF 3.6 which outlines what is currently possible in that browser.

  • Thanigainathan

    This article can be more eloborate and informative.

  • http://guild3.com Jason Grundy

    @Thanigainathan Obviously familiarity with a lot of technologies is assumed. What do you want me to elaborate upon?

  • http://www.taccsupport.com Daniel

    Question: what is a guid for and why use it?

    • http://guild3.com Jason Grundy

      Basically a Guid is a means of uniquely identifying something. See here for a more comprehensive definition.

      In a database you’ll typically see some sort of auto incrementing integer or a Guid used for providing identity. There’s been a lot of discussion over the years as to which approach is best. The Guid approach seems to have gained more favor recently. IMHO both are viable options in the great majority of scenarios. Google this topic to find out more (make sure you look for more recent articles).