Building a Simple Photo Gallery in ASP.NET MVC Framework

image I decided to create a simple photo gallery in the ASP.NET MVC framework. The fun part is that this level of application is really the new “Hello World”. It takes less time to build than the “Hello World” did back in the day.

In this post, I’ll walk you through the process of creating this simple photo gallery with the MVC framework.

First, let’s talk a little about what the ASP.NET MVC framework is. It’s a web framework built on .NET with the principles of the MVC architecture behind it.

The MVC Architecture

MVC architecture divides the responsibilities of an application into three main components – models, views, and controllers.

image“Models” are responsible for the the data access. The data is often times in a database but it doesn’t have to be. The model could be over an XML file or whatever other data store that you happen to use. By default the ASP.NET MVC framework uses the Entity Framework. However, it can work with any data access type that returns a set of objects that the view can access. Most of the time, this will be an ORM such as the Entity Framework, NHibernate or SubSonic. In our demo below we’re actually going to just be reading in an XML file from the disk.

“Views” are responsible for the actual user interface. Typically this is HTML but it could be XML, JSON or any other number types of display/service response. Most of the time, these displays/responses are built based on model data.

“Controllers” are responsible for the actual logic. It handles the end user interaction, manipulates the data in the model and decides which view to return to the user. Simple enough? 

Creating the ASP.NET MVC Framework Project

image I started out creating an ASP.NET MVC Web Application called PhotoGalleryMVC. There are a couple of very important things to notice in an ASP.NET MVC framework project.

First, look at the Global.asax and it’s code behind. It’s got a really important method called RegisterRoutes where you define your routes.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default", 
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = "" }
    );

}

These routes define what happens when your application receives a request. The controller is a class and the action is a method on that class. The parts after that are parameters to the method. We’ll see more with this in a few moments.

The next thing is to notice the controllers. The default method that you get in the helper class is as follows:

public ActionResult Index()
{
    ViewData["Title"] = "Home Page";
    ViewData["Message"] = "Welcome to ASP.NET MVC!";

    return View();
}

This is the default action for the controller. It’s simply setting some properties on the View and then returning it. Notice that we’re not instantiating a copy of the view and setting properties directly on it. Instead, we’re staying with the very loosely coupled method of using a ViewDataDictionary called ViewData. This is a dictionary of items that both the view and the controller have access to.

Creating the ImageModel

The first thing I want to create is a way to get the images in the first place. Rather than creating a database, we’re going to simply use an XML file as our storage for our information about our images.

Create a folder called Images under the root of the project. This will be where we put the images.

As a file ImagesMetaData.xml in the images directory following the format below. Feel free to substitute your own data in for the data I have below…

<?xml version="1.0" encoding="utf-8" ?>
<images>
  <image>
    <filename>IMG_3717.JPG</filename>
    <description>Paul playing Guitar Hero.</description>
  </image>
  <image>
    <filename>IMG_3720.JPG</filename>
    <description>Phizzpop Signin.</description>
  </image>
</images>

Add a class called Image under the model folder. For now this will be really simple.

namespace PhotoGalleryMVC.Models
{
    public class Image
    {
        public Image(string path, string description)
        {
            Path = path;
            Description = description;
        }
        public string Path { get; set; }
        public string Description { get; set; }
    }
}

All this class provides for now is a holder for the image path and description. We’ll do more with this class in the future.

The next thing that we need to do is create a way to get those images from the disk. This will be in a class called ImageModel. To make this really simple, we will inherit from a generic list of Image. This gives us a lot of functionality already. What we need to add is a constructor that will retrieve the images from the disk.

namespace PhotoGalleryMVC.Models
{
    public class ImageModel : List<Image>
    {
        public ImageModel()
        {
            string imagesDir = HttpContext.Current.Server.MapPath("~/images/");
            XDocument imageMetaData = XDocument.Load(imagesDir + @"/ImageMetaData.xml");
            var images = from image in imageMetaData.Descendants("image")
                         select new Image(image.Element("filename").Value,
                         image.Element("description").Value);
            this.AddRange(images.ToList<Image>());
        }
    }
}

All this model is doing is reading in the XML file and creating a list of images based on that metadata.

Creating the Controller

The next step is to create the controller. Again, for the moment, this will be extremely simple. We’ll do more with it in the future.

namespace PhotoGalleryMVC.Controllers
{
    public class ImageController : Controller
   
{
        public ActionResult Index()
        {
            return View(new ImageModel());
        }
    }
}

Notice that this is slightly different than the default controller as it’s passing in the ImageModel. We’ll have to create the View to accept it here in just a moment.

Creating the View

Now we need to add a folder to the hold our images view in the Views folder. Now to create the view in the Images view folder, right-click on the folder and select Add View. Name the view Index.

Now that we have our view, modify it’s declaration to accept the ImageModel class.

namespace PhotoGalleryMVC.Views.Image
{
    public partial class Index : ViewPage<ImageModel>
    {
    }
}

What this does is set up our view based on a generic ViewPage with ImageModel as it’s base.

And lastly we need to add the HTMLish stuff to do the actual display.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
AutoEventWireup="true" CodeBehind="Index.aspx.cs"
Inherits="PhotoGalleryMVC.Views.Image.Index" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <% foreach (var image in ViewData.Model) { %> <span class="image"> <a href="images/<%= image.Path %>"><img src="images/<%= image.Path %>" /></a> <span class="description"><%= image.Description %></span> </span> <% }%> </asp:Content>

If you’ve ever done ASP Classic or PHP, this HTMLish stuff shouldn’t look too odd. If you strip out the HTML code, you’ve got a normal foreach loop written in C#. The bad news about this approach is that there’s a lot less controls, such as the datagrid and such, available to you. The good news is that you’ve got absolute control over the HTML that is produced.

You should notice, however, that we’re able to leverage master pages as we do in ASP.NET 2. This is great because it allows us to define our look and feel in a master page. There’s a great amount of flexibility and power in that.

Last step is that we need to add a tab on the main navigation to get to the images page. We do that in the /views/shared/site.master

<ul id="menu">
    <li><%= Html.ActionLink("Home", "Index", "Home")%></li>
    <li><%= Html.ActionLink("Images", "Index", "Image")%></li>
    <li><%= Html.ActionLink("About Us", "About", "Home")%></li>
</ul>

Even though we’ve got few controls at out disposal, there are some interesting helpers such as this Html.ActionLink. This returns a link that points to the appropriate controller and action without us having to divine what that link should be based on the current routes. 

At this point, the application runs and shows really big pictures (assuming that you’ve put a few in the images folder in the first place).

Adding a New Picture

Now that we’ve got a few manually placed a few of the pictures in the folders and gotten them to display on the view, we need a way for the user to add their own pictures to the site. We’re going to do this one in reverse order where we create the view and work backwards from there.

Step one is that we need a new view and a way to get to it from the images page. We can accomplish that with a simple Html.ActionLink in the Image Index view.

    <p><%= Html.ActionLink("Add your own image", "Upload", "Image")%></p>

Now we need to create the view for the New action. Simply right click on the View folder and select Add|View. Name this view “Upload”.

In the view, we need to create a form that will do the post.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
AutoEventWireup="true" CodeBehind="Upload.aspx.cs"
Inherits="PhotoGalleryMVC.Views.Image.Upload" %>

<asp:ContentID=”Content1″ContentPlaceHolderID=”MainContent”runat=”server”>
<
form method=”post” action=”<%=Url.Action(“save”) %>enctype=”multipart/form-data”>
    <
input type=”file” name=”file” />
    <input type=”submit” value=”submit” /> <br />
    <input type=”text” name=”description” />
</form>
</
asp:Content>

It’s not the world’s prettiest form but it’s functional. Notice the action on the form tag. It’s using another helper called Url.Action. This maps to the same controller but a different action.

Now we need to add the upload and save action to the controller. The Upload action is very simple. It simply returns the Upload view. The Save is a little more complicated as it has to do the actual logic of getting the files and descriptions and putting those on the model.

namespace PhotoGalleryMVC.Controllers
{
    public class ImageController : Controller
    {
        public ActionResult Index()
        {
            return View(new ImageModel());
        }

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

        public ActionResult Save()
        {
            foreach (string name in Request.Files)
            {
                var file = Request.Files[name];

                string fileName = System.IO.Path.GetFileName(file.FileName);
                Image image = new Image(fileName, Request["description"]);

                ImageModel model = new ImageModel();
                model.Add(image, file);
            }
            return RedirectToAction("index");
        }
    }
}

The important part here is that this controller is not actually doing the logic of saving out to the disk. This is important because it gives us the flexibility to alter the model switch from file based storage to a database and so on. This separation is key to the success of the architecture.

Last thing to do is alter the model to actually save out to the disk.

public void Add(Image image, HttpPostedFileBase file)
{
    string imagesDir = HttpContext.Current.Server.MapPath("~/images/");
    file.SaveAs(imagesDir + image.Path);

    this.Add(image);
    XElement xml = new XElement("images",
            from i in this
            orderby image.Path
            select new XElement("image",
                      new XElement("filename", i.Path),
                      new XElement("description", i.Description))
            );

    XDocument doc = new XDocument(xml);

    doc.Save(imagesDir + "/ImageMetaData.xml");
}

The LINQ makes creating the XML document really simple.

There are a lot of optimizations that could be done here such as storing off the model in memory and the like so that we’re not constantly reading/writing to the disk and the like. That’s not the point of this exercise. The point here is to work with the MVC framework.

At this point we’ve got a functioning image gallery with uploads and a view.

In my next post, I’ll alter this to serve up thumbnails and give a nicer user experience.

Leave a Reply

Your email address will not be published. Required fields are marked *