Monday, October 20, 2008

Building a Photo Tagging Application using ASP.NET 2.0, LINQ, and Atlas

Building a Photo Tagging Application using ASP.NET 2.0, LINQ, and Atlas
In this article, I will examines how to build a photo tagging application using ASP.NET 2.0, LINQ and Atlas framework.

Introduction

Republished with Permission - Original Article

Over the last few days I’ve spent some spare time playing around with LINQ and LINQ for SQL (aka DLINQ) – both of which are incredibly cool technologies.

Specifically, I’ve been experimenting with building a photo management application that provides “tagging” support. If you aren’t familiar with photo tagging, you might want to check out FlickR – which implements a tagging mechanism that enables users to easily annotate pictures with textual words that provide a way to easily organize and sort them.

For my photo-browser I wanted to implement a “tag cloud” on every page of the site that lists the most popular tags in use on the left-hand side, and enables users to be able to click a tag within the "tag cloud" to easily filter the pictures by them:

When a user clicks on an individual picture I then wanted them to see a more detailed version of the picture, as well as all the tags it has been annotated with:

I then wanted end-users to be able to click on the “edit tags” link on the page to switch into an edit mode using Ajax, where they can easily create or edit new tags for the picture. Any word can be suggested:

When the tags are saved, I wanted the “tag cloud” to dynamically update with the current list of tags being used across all photos on the site, and size each tag within the cloud based on the tag’s frequency (higher usage frequency produces bigger font sizes for that tag):

It turns out that implementing the above solution is very easy using ASP.NET 2.0, LINQ and Atlas. The below post walks through a simple sample to illustrate the basics. You can also download a completed version of the sample to try out on your own (details on how to-do this are at the end of this post).

Step 1: Creating our Database

I started the application by first creating a database within SQL Server to model albums, photos, and photographers, as well as tag annotations mapped against them. Here is the schema I ended up using for this particular sample (I’ll be adding more properties as I expand the photo management app more in future posts – but this is a basic start):


You’ll notice that I have an “Albums” and “Photos” table to store common metadata associated with pictures. I am also using a Photographers table to store photographer names. The Photos table has foreign-key (FK) relationships against both it and the Albums tables. I then created a “Tags” table to track tag annotations for the photos. The Tags table has a foreign-key relationship to the Photos table, and maintains a separate row for each Tag used on the site. One benefit of this approach is that it makes adding new Tags super fast and easy (you don’t need to worry about duplicate tag-name insert collisions, and you can add and associate new tags in one database round-trip).

In addition to providing referential integrity, the foreign-key from the Tags table to the Photos table also enables cascading deletes – so that if a photo is deleted the tags associated will automatically be deleted too (avoiding cases of “dangling tags” left in the database that are no longer associated with the data they were associated against).

Step 2: Creating our LINQ for SQL data model

Once my database was built, I created a new LINQ-enabled ASP.NET web-site by selecting the LINQ template that is installed by the LINQ May CTP within the “New Website” dialog in Visual Web Developer

(the free IDE for ASP.NET development). This sets up an ASP.NET 2.0 project that has LINQ fully configured, and allows me to easily use LINQ to connect against databases.

I then created a LINQ-enabled model that I could use to interact with the database defined above. The next release of Visual Studio will provide a really nice WYSIWYG designer for creating and mapping this database object model. In the meantime, I just used the command-line “sqlmetal” utility that ships with LINQ to quickly create this. All I had to-do was to open up a command-line console and type the following commands to accomplish this:

>> cd c:\Program Files\LINQ Preview\Bin

>>
>> sqlmetal /database:PhotoDB /pluralize /namespace:PhotoAlbum /code:c:\Projects\PhotoApp\app_code\PhotoDB.cs

This created a LINQ-enabled object model for the “PhotoDB” database on my local system and stored the generated classes within the “PhotoDB.cs” file within the ASP.NET LINQ project. Note that the classes generated are declared as “partial types” – meaning developers can optionally add additional properties/methods to them in separate files (common scenarios: entity validation or helper methods).

Once this LINQ-enabled object model is created, I can then easily use this LINQ-enabled object model anywhere within my ASP.NET application. For example, to create two new Albums in the Albums table I could just write this code:

PhotoDB photoDb = new PhotoDB();
Album album1 = new Album();

album1.AlbumName = "Africa Trip";
Album album2 = new Album();
Album2.AlbumName = "Europe Trip";
photoDb.Albums.Add(album1);
photoDb.Albums.Add(album2);
photoDb.SubmitChanges()
When the "SubmitChanges()" method is called above, the album instances above are saved into the SQL database within the Albums table – without me having to write any raw SQL or data code. The above code is all that needed to be written for this to work.

Step 3: Using LINQ to work with data and Tag Photos in our application

LINQ makes working with data a real joy. It automatically handles relationships, hierarchy, and tracking changes within our model – eliminating tons of data access code. For example, to create a new album and a photo within it, I could extend our previous code-sample like this:

PhotoDB photoDb = new PhotoDB();

Photographer photographer1 = new Photographer();
photographer1.PhotographerName = "Scott Guthrie";
Photo photo1 = new Photo();
photo1.Description = "Picture of Lion";
photo1.Url = "http://someurl";
photo1.Thumbnail = "http://thumbnailurl";
photo1.Photographer = photographer1;
Album album1 = new Album();
album1.AlbumName = "Africa Trip";
album1.Photos.Add(photo1);
photoDb.Albums.Add(album1);

This is all of the code needed to add a new Photographer, Photo and
Album into the database, setup the FK relationship between the
Photographer and Photo, and setup the Photo and the Album FK
relationship (notice how this relationship is expressed by adding the
photo into the Album’s photo collection, and by setting the
Photographer property on Photo). Because these properties and
collections are strongly-typed, we get full compile-time checking and
intellisense of our syntax and data relationships.
C# has also added new syntax support that can be used to make object initialization even terser/cleaner than what I did above. Specifically, it now allows developers to declare properties (and collections) using syntax like below if you prefer:

photoDb.SubmitChanges();

PhotoDB photoDb = new PhotoDB();

Photographer scott = new Photographer()
{
PhotographerName = "Scott Guthrie"
};
photoDb.Albums.Add(new Album()
{
AlbumName = "South Africa", Photos =
{
new Photo()
{
Description = "Lion Close Up", Photographer = scott, Url = "http://url1",
Thumbnail = "http://thumb1",
}
, new Photo()
{
Description = "Zebras at Dusk", Photographer = scott, Url =
" http://url2", Thumbnail = " http://thumb2%22
}
}
);

This is syntactically equivalent to the code before – except that we
are now able to compact more functionality in fewer lines of code. In
the example above, I’m now adding two new photos to the new South
Africa album (with me as the photographer for both pictures).

Because
we setup FK relationships between the Photo table and the Tags table,
we get automatic association linking between them with LINQ (this is
expressed via the “Tags” property on Photos and the corresponding
“Photo” property on each Tag). For example, I could use the below code
to fetch one of our newly created Photo’s above from the database and
associate three new Tags to it:
photoDb.SubmitChanges();

PhotoDB photoDB = new PhotoDB();

Photo photo = photoDB.Photos.Single(p = > p.Description == "Lion Close Up");
photo.Tags.Add(new Tag()
{
Name = "Lion"
}
);
photo.Tags.Add(new Tag()
{
Name = "AndersH"<BR>}
);
photo.Tags.Add(new Tag()
{
Name = "ScottGu"
}
);

I could then use the below code to retrieve a Photo and output its tags within a page:

PhotoDB photoDB = new PhotoDB();
Photo photo = photoDB.Photos.Single(p = > p.Description == "Lion Close Up");

foreach (Tag tag in photo.Tags)
{
Response.Write("Tag : " + tag.Name);

I could also then write this code to easily retrieve all Photos that are tagged with a specific tag-name, and output the Photo description and photographer name for each of them:


PhotoDB photoDb = new PhotoDB();

string tagName = "Lion";
var photos = from photo in photoDb.Photos where photo.Tags.Any(t = > t.Name
== tagName)select photo;
foreach (Photo photo in photos)
{
Response.Write("Photo: " + photo.Description + " by: " +
photo.Photographer.PhotographerName);

I do not need to write any extra data code to make the above code work.
LINQ handles all of the SQL statement execution for me. This provides

Step 4: Adding "Tag Cloud" UI to our Application

After defining the database and creating the LINQ-enabled object-model above, I focused on the UI of the site.

To maintain a consistent layout and look and feel across the site, I first created an ASP.NET master page that I called “Site.Master”. Within this file I defined the basic layout structure that I wanted all pages to have, and used an external stylesheet to define CSS rules.

I then downloaded and added into my project a cool, free “Cloud Control” that Rama Krishna Vavilala built and published (with full source-code) in a nice article here. It encapsulates all of the functionality needed to render a list of weighted cloud tags within an ASP.NET page. It also supports standard ASP.NET databinding – which means I can easily bind a result from a LINQ query to it to output the correct weighted tags for our application.

My final Site.Master template to accomplish this ended up looking like this:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inherits="Site" %>

<%@ Register Namespace="VRK.Controls" TagPrefix="vrk" Assembly="VRK.Controls" %>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<link href="StyleSheet.css" rel="stylesheet" type="text/css" />
podcast directory
Bloglisting.net - The internets fastest growing blog directory

RSS Feed

Blog Directory & Search engine

Followers

About Me