ASP.NET MVC: Uploading Files

I've recently had a need to upload and store images in a database, but how did I get them there? Today, I go over a technique on how to upload and store images using ViewModels.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
Send or Upload File

When I was using WebForms, I would write code to read the form variables and everything would be included in the code-behind. I guess that's why I like ASP.NET MVC so much.

With ViewModels, you can mold the entities any way you want to present data to your View..

So if you need a specific way to upload a file, you could let your post action method in your controller handle the processing for storing images in a database.

Setting the Stage

Uploading an file sounds simple, right?

There are a lot of parts in a lot of places that need to orchestrate the upload. The good news is that everything is taken care of for you when you upload it. To be honest, we only need to add a couple lines of code.

Our goal here is to take an image, upload it through an ASP.NET MVC View, have the controller receive the file or image, and save it in a table.

So let's get started.

What's the Database Look Like?

While this post addresses the technique on how to save the uploaded file to a database table, you can, if you want, easily change the functionality to store it in a directory instead.

For now, we'll focus on the database portion.

The table is simple. I have a Media table with an entity called SiteMedia that contains all of the image attributes. I also have a field called MediaContent that is a varbinary(MAX) that stores the actual image.

When Entity Framework is used, we have a MediaContent property that is a byte array type (byte[]).

public byte[] MediaContent { get; set; } // MediaContent

Now that we have our storage, let's move to our ViewModel to see how we're passing the data to and from the controller.

Examining the ViewModel

Since we'll be uploading new content, our ViewModel will contain the following entities:

public class MediaViewModel
{
    public SiteMedia MediaItem { get; set; }
    public HttpPostedFileBase File { get; set; }
} 

As mentioned above, the SiteMedia entity will have an Id, a Title, and all of the other fields, but most importantly, it will have the MediaContent byte array for the database. 

Why do we have an HttpPostedFileBase in the model?

This is how we transfer the uploaded file to the controller for processing. Since this type is not part of the SiteMedia and it's essential for the functionality of uploading files, we need this object in our ViewModel to transfer the data over to the controller.

Viewing the View

Our View will have a section for our file upload that looks like this:

@using (Html.BeginForm("Add", "Media", FormMethod.Post,
        new
          {
              @id = "mediaAdd",
              @role = "form",
              @enctype = "multipart/form-data"
          }))
{
    @Html.ValidationSummary(true)
    @Html.AntiForgeryToken()
    <div class="row">
        <div class="form-group col-xs-5 col-lg-5">
            @Html.LabelFor(model => model.MediaItem.Title)
            <span class="glyphicon glyphicon-info-sign input-sm" data-title="Please enter a title." data-html="true" data-placement="right"></span>
            @Html.TextBoxFor(model => model.MediaItem.Title, new { @class = "form-control focus" })
            @Html.ValidationMessageFor(model => model.MediaItem.Title)
        </div>
    </div>
    <div class="row">      <div class="form-group col-xs-5 col-lg-2">          @Html.LabelFor(model => model.File, "Media:")             <span class="glyphicon glyphicon-info-sign input-sm" data-title="Select the type of media to upload." data-html="true" data-placement="right"></span>             @Html.TextBoxFor(model => model.File, new { @type = "file", @class = "form-control input-sm" })         </div>         @Html.ValidationMessageFor(model => model.File)     </div> .
.
. <div class="form-group col-md-10">         <p class="text-center">          <input type="submit" value="Add Media" />          <a href="@Url.MediaUrl()">Cancel</a>         </p>     </div> }

A lot going on here. Don't mind the Bootstrap in my forms.

Of course, we are using the MediaViewModel for setting up our parameters, so we'll have a @model MediaViewModel at the top of our View.

Pay particular attention to the beginning form tag. We have an enctype of "multipart/form-data". This is important. You have to place this on your form to successfully upload a file.

The next part to focus on is the second "row" with the TextBoxFor for the HttpPostedFileBase property called File. We change the type of input from a general text box to a "file" type text box. This will give us our text box with a Browse button to find the file we want to upload. Standard HTML functionality.

The View is all set and ready to go. On to the controller.

Posting The File To The Controller

The best part about using a ViewModel is that when you post your data back to the controller, the DefaultModeBinder will provide all of the details of the File to prepare it for an upload.

Our POST action method in our controller will look like this:

[HttpPost, ValidateAntiForgeryToken]
public ActionResult Add(MediaViewModel model)
{
    var uploadedFile = new byte[model.File.InputStream.Length];
    model.File.InputStream.Read(uploadedFile, 0, uploadedFile.Length);

    model.MediaItem.MediaContent = uploadedFile;     model.MediaItem.DateAdded = DateTime.UtcNow;     model.MediaItem.ContentType = model.File.ContentType;
    var unitOfWork = this.ControllerContext.GetUnitOfWork<AdminUnitOfWork>();     unitOfWork.MediaRepository.Add(model.MediaItem);     unitOfWork.MediaRepository.SaveChanges();
    TempData["Message"] = model.MediaItem.Title + " was added to the database.";
    return Redirect(Url.MediaUrl()); }

I've removed a couple of lines for brevity's sake, but the simplicity of it all is included in the first two lines of the method.

var uploadedFile = new byte[model.File.InputStream.Length];
model.File.InputStream.Read(uploadedFile, 0, uploadedFile.Length);

model
.MediaItem.MediaContent = uploadedFile;

The first line sets up a byte array based on the ViewModel File's InputStream Length. Once that's created, we start reading the uploaded file from the stream.

Once completed, we assign the uploadedFile to the MediaContent.

At this point, we are done. If you are using Entity Framework, it will automatically save the data to the database with no fuss and no muss.

Quick Note: If you want to save the file for processing later, you could easily use the model.File.SaveAs() after the file was finished uploading to save it with a full directory path and filename instead of saving it to the database.

Conclusion

This post went over an easy way to upload a file from the client to the server and place that file into a field in a table. We also mentioned a way to optionally save the file to a directory on your server.

If you think this was a convoluted way to upload the file and dirty up your controller, you could easily use a ModelBinder to take care of all of this behind the scenes and just have the ModelBinder deliver a packaged ViewModel to the controller for processing and saving your data.

I posted an article on how to use ModelBinders called ASP.NET MVC ModelBinder: Use ModelBinders for QueryStrings. Check it out for more information on ModelBinders.

Did this make sense? Post your comments below.

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus