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


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 object 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";







When the “SubmitChanges()” method is called above, the album instances above are saved into the SQL Server database within the Albums table – without me having to write any raw SQL or data access 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";






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 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",



                            } );




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 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" } );

  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 an incredibly flexible and elegant way to perform data access.


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="" >

    <head runat="server">

    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />




    <form id="form1" runat="server">


        <div class="header">

            <h1>Scott's Photo Sample</h1>



        <div class="tagcloud">


            <div class="subheader">

                <h2>Filter Tags</h2>



            <vrk:Cloud ID="Cloud1" runat="server"





                       DataTitleFormatString="{0} photos"

                       DataWeightField="Weight" >






        <asp:contentplaceholder id="MainContent" runat="server">







The below Site.Master code-behind file is then used to obtain a unique list of tags from the database (it uses the “data-projection” feature of LINQ to fetch a sequence of custom shaped types containing the Tag name and usage count).  It then databinds this sequence to Rama’s control above like so:


using System;

using System.Query;

using PhotoAlbum;


public partial class Site : System.Web.UI.MasterPage {


    void PopulateTagCloud() {


        PhotoDB photoDb = new PhotoDB();


        Cloud1.DataSource = from tag in photoDb.Tags

                            group tag by tag.Name into g

                            orderby g.Key

                            select new {

                                Name = g.Key,

                                Weight = g.Count()






    protected void Page_Load(object sender, EventArgs e) {





And now if I create an empty page based on the Site.Master above and hit it, I’ll automatically have the weighted tag-cloud added to the left-hand side of it:



Step 5: Browsing Photos By Tag


The next page I added to the site was one named “PhotoListing.aspx”.  The tag-cloud control used above creates a hyperlink for each tag that links to this page and passes the tag name as an argument to it when you click a tag.


The PhotoListing.aspx page I created is based on the Site.Master template above and uses a templated DataList control to output the pictures in a two-column layout:


<asp:Content ID="C1" ContentPlaceHolderID="MainContent" Runat="server">


    <div class="photolisting">


        <asp:DataList ID="PhotoList" RepeatColumns="2" runat="server">       



                <div class="photo">


                    <div class="photoframe">

                        <a href='PhotoDetail.aspx?photoid=<%# Eval("PhotoId") %>'>

                            <img src='<%# Eval("Thumbnail") %>' />     




                    <span class="description">

                        <%# Eval("Description") %>












Below is the entire code-behind for the page:


using System;

using System.Query;

using PhotoAlbum;


public partial class PhotoListing : System.Web.UI.Page  {


    void PopulatePhotoList(string tagName) {


        PhotoDB photoDb = new PhotoDB();


        if (tagName == null) {

            PhotoList.DataSource = photoDb.Photos;


        else {


            PhotoList.DataSource = from photo in photoDb.Photos

                                   where photo.Tags.Any(t => t.Name == tagName)

                                   select photo;






    protected void Page_Load(object sender, EventArgs e) {

        PopulatePhotoList( Request.QueryString["tag"] );




When a user clicks on the “Cats” tag in the tag-cloud, they’ll then see this list of photos rendered:



Step 6: Photo Details and Ajax Editing


The PhotoListing.aspx page above links each thumbnail image to a PhotoDetails.aspx page that shows the picture full-size, as well as lists out all of its tags.  Users visiting the site can also optionally edit the tags using an inline Ajax-editing UI experience.




To implement the Ajax-UI I used the Atlas UpdatePanel control, and then nested an ASP.NET MultiView control within it.  The Multiview control is a built-in control introduced in ASP.NET 2.0, and allows you to provide multiple “view” containers that can contain any HTML + server controls you want.  You can then dynamically switch between the views within your code-behind page however you want.  If the Multi-View control is nested within an Atlas UpdatePanel, then these view-switches will happen via Ajax callbacks (so no full-page refresh).


For the tag editing experience above I defined both “readView” and “editView” views within the Multiview control, and added “edit”, “cancel” and “save” link-buttons within them like so:


<atlas:UpdatePanel ID="p1" runat="server">




        <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0">


            <asp:View ID="readView" runat="server">



                <asp:Label id="lblTags" runat="server" />


                <span class="photoBtn">

                    <asp:LinkButton ID="btnEdit" runat="server" OnClick="btnEdit_Click">[Edit Tags]</asp:LinkButton>





            <asp:View ID="editView" runat="server">



                <asp:TextBox ID="txtTags" runat="server" />


                <span class="photoBtn">

                    <asp:LinkButton ID="btnSave" runat="server" OnClick="btnSave_Click">[Save Tags]</asp:LinkButton>

                    <asp:LinkButton ID="LinkButton1" runat="server" OnClick="btnCancel_Click">[Cancel]</asp:LinkButton>











I then wired-up event-handlers for these 3 link-buttons in my code-behind like so:


    protected void btnEdit_Click(object sender, EventArgs e) {




    protected void btnCancel_Click(object sender, EventArgs e) {




    protected void btnSave_Click(object sender, EventArgs e) {




The “save” event-handler above in turn calls the UpdatePhoto method and passes in the editView’s <asp:textbox> value as arguments.  This method is defined within the code-behind like so:


    void UpdatePhoto(string tagString) {


        PhotoDB photoDb = new PhotoDB();

        Photo photo = photoDb.Photos.Single(p => p.PhotoId == photoId);



        photo.Tags = photoDb.TagWith(tagString, ' ');





The above method retrieves the specified Photo from the database, removes its current tags, and then uses the below “TagWith” helper method to create a new collection of tag instances to associate with the picture:


    public EntitySet<Tag> TagWith (string tagNames, char separator) {


        EntitySet<Tag> tags = new EntitySet<Tag>();


        tagNames = tagNames.Trim();


        foreach (string tagName in tagNames.Split(separator))

           tags.Add(new Tag { Name = tagName });


        return tags;



And with that I now have an editable Ajax-enabled editing experience for viewing and dynamically adding new Tags to my photos. 




Hopefully the above post provides a good walkthrough of some of the really cool things you can do with LINQ, LINQ for SQL, ASP.NET 2.0 and Atlas.


You can download the completed sample here.  Please review the “readme.txt” file in the root of the project to learn how to set it up and run it.


To learn more about using LINQ and LINQ for SQL with ASP.NET 2.0, please review these past three blog posts of mine as well:


Using LINQ with ASP.NET

Using DLINQ with ASP.NET

Using DLINQ with Stored Procedures


Also make sure to check out the LINQ web-site here to download LINQ and start learning more about it (note: you need to install the May LINQ CTP build to run the sample).


Last but most importantly: I want to say a huge thank-you to Anders Hejlsberg and Matt Warren – who are not only responsible for the insanely cool technology in LINQ + DLINQ, but also very kindly spent some of their valuable time over the last few days educating me on how to best approach the app above, and in making some invaluable coding suggestions. 


Hope this helps,







  • This is really great. A full example with updates and inserts :)

    I'm still trying to figure out how you get the "SubmitChanges" method to appear. There must be a difference somewhere which makes your project files work, when this method does not seem to appear when you try to write this project from scratch using the "LINQ ASP.NET" project type...

  • Scott,

    Have you tried working with any of these LINQ samples in VB.NET?

    The problem I find with all of them is that VS constantly tries to re-parse the SqlMetal-generated file in the App_Code folder. This makes it almost impossible to work as references are constantly appearing and disappearing, and getting it to build is game of chance :)

    Any idea how to stop it constantly parsing? (50% of the times it parses it it fails with a bunch of errors in the Error List panel)

  • That was a bad ass tutorial. I was really suprised to see the new syntax structure in C# 3.0 (I assume) - i'm excited to dive into that that when it's available (reminds me of oop javascript).

    Since LINQ is in CTP is it worth taking a look at? When do you estimate a version 1.0 release?

    hmm..I wonder if the nhibernate community will switch over to LINQ once it's v1 comes out.

    ok, thanks again.

  • Leyendo el blog de Scott Gu (que se los recomiendo!), me encontre con una aplicacion que utiliza LINQ, DLINQ y Atlas....

  • Very cool sample! Thanks for the post!

  • Hi Boris,

    The intellisense engine currently struggles at times with completing a few of the statements (although compilation fully works).

    The SubmitChanges() method is one I have also noticed does not always complete -- even though it is there and works on the DataContext object.

    I haven't tried the sample yet in VB - although in theory it should work fine. Can you send me details via email about the design-time scenario you are seeing failures with? I'll then loop you in with the LINQ team who can take a look at.

    Hope this helps,


  • Hi Kevin,

    I'd recommend spending a little time playing with LINQ over the next few months to get a feel for it and think about some of the possibilities it provides (which are almost endless).

    We will support a go-live program of it before it RTM's -- so it won't be too long now before you can safely deploy projects with it.



  • Good article. This sample is the first time I saw LINQ and I'm quite impressed. Looking forward to it in C# 3.0 and VS2005 tie-in.

    btw, there are two typos in the download readme.txt. "PhotosDB" should be PhotoDB. and "Populate Database" is missing the "L".

  • Scott, how does Linq relate to typed datasets? Will it replace that? What kind of 'pattern' is it, Active Record?

  • Thanks for the sample!

    I'm waiting for the next reason to iron out the bugs with installing templates, etc.. so - after that I hope to really dive in, the LINQ is a going to be a great feature.

    Thanks again.

  • Hi Michiel,

    LINQ will actually support LINQ over DataSets -- which means you'll be able to use LINQ with DataSets directly if you like as well.

    Hope this helps,


  • Cannot run the application. I always get the error of

    Message="An error has occurred while establishing a connection to the server. When connecting to SQL Server 2005, this failure may be caused by the fact that under the default settings SQL Server does not allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)"
    Source=".Net SqlClient Data Provider"

    This error was happend in both SQL SERVER 2000 and 2005. For SQL 2005, I enabled remote connection (TCP/IP and name pipe) and Sql server browser service even the application and the sql server run in the same computer. I verified the connection in VS 2005 server explorer by adding data connection and there was no problem. I have no idea what I should do next to test this great applicationa. Cannot wait for it any more!

  • Hi Eric,

    Can you send me email ( with more details about this error. Also -- have you verified that the connection-string setting in your web.config file is pointing to the right database?



  • cool article

    and my question about Linq: How to Linq support data transaction? Need i modify the code to meet it?


  • Scott,

    Thanks for your quick reply. I got it work now after change the data source to "macinchinename\SQLEXPRESS". "local" or machinename only does not work. How is that?


  • Whenever I see some new technology that "does all of the work" for me with regards to SQL, I get nervous about performance and scalability. What is it doing behind the scenes? Am I going to roll this out and then have to ditch it all two months later and write stores procs, parameterized queries, etc.? Let's see some SQL profiler views and explain the tradeoffs between dynamic SQL and stored procs.

  • Eric,

    You might want to test if you can connect to the SQL Server using plain old ADO.NET commands first. Sounds like not just DLINQ will not work, but any data connection will give you a connection error.


  • Hi Paul,

    One nice thing about LINQ is that within the debugger, you can just hover over a DLINQ query to see the actual SQL syntax query it will generate back to the database. That gives you the ability to see exactly what is going on within the app.

    You also have the ability to escape out and write your own custom SQL syntax and/or call SPROCs. The SQL queries that DLINQ generates are really good in my experience -- and knowing that you can always use these later escape valves is comforting if you ever want to-do something totally custom.

    Hope this helps,


  • Hi xmren,

    DLINQ supports the .NET transaction model, and you can use it to scope transactions both to a single server or multiple servers.

    You can also mix and match commands from ADO.NET and DLINQ into a single transaction - which is super flexible.

    Hope this helps,


  • Hi, looks great, but I have one question. I have tried to convert DB (sqlmetal) with generated aspnet tables and SPs (user,membership,..) and I am getting a lot of errors trying to do it, will it work in the final linq release?

  • Hi Nindza,

    That is odd -- you should be able to run SqlMetal against that database. If you want to send me email ( with details of the error and the database schema, I can have someone take a look.



  • Hello again, I thought i posted this but it never came up. Because LINQ works with the username and password given to it, you have to give that user permission to read and write to the tables directly, of course there are plenty of things you can do as a developer to limit the security threat of exposing those credentials, but lets face it, most people don't, instead they rely on a low level account that can only execute the stored procedures, personally i find that the best solution, LINQ provides us with a way to use those stored procedures but then it kind of defeats the purpose. This is great for those developing small online websites and apps but anything enterprise level I don't see how this would cut it. Maybe if in the database you could limit the use of sprocs, but that would only work for SQL server. LINQ has a use I guess, I wouldn't use it, if you wanted to optimize the query you'd have to recompile your program, where's the separation? I'm being totally random in my response but this one isn't going to take off mate, sorry.

  • Hi Chris,

    You need to specify a user permission when using SqlMetal to generate the LINQ object model over the data. This username needs permission to access the meta-data of the tables.

    However, SqlMetal is a design-time only step, and at runtime you don't need to have a username with these permissions. Instead, you can run using the same account you'd use from ADO.NET or any other data library to access the database.

    What this means is that you can run using the same security permissions you use today -- and don't need to adjust them.

    Hope this helps,


  • Just to put a bit more C# 2.0 into your code, in your PhotoListing.aspx codeFile write:
    void PopulatePhotoList(string? tagName) {
    PhotoDB photoDb = new PhotoDB();
    if (tagName.HasValue) {
    PhotoList.DataSource = photoDb.Photos;
    } else {
    PhotoList.DataSource = from photo in photoDb.Photos
    where photo.Tags.Any(t => t.Name == tagName)
    select photo;
    Nice article - didn't answer my question about MasterPage and Atlas scriptManager though.

  • Scott, how does DLINQ fit into the regular updates to table structures, SPROC's, etc? Does the SQLMetal command have to be run each time a column is CRUD'ed? Thanks for all your work!! Great resources!!

  • Hi SushiBob,

    When you add/remove columns to your table you can optionally go ahead and update your DLINQ data models with them (or -- if you don't need to expose them, then you don't have to). You can do this either by running SQLMetal again, or by using the DLINQ designer. I'm going to-do a new post later this weekend about the DLINQ designer and some of the cool things you can do with that -- stay tuned!



  • In my view it is an insanly great technology and a fairly elgegant way to introduce the simplicity and ease that this technology would bring to developers.

  • Hello Scott, I got the exact same error as Eric but I do not understand what he said about changing "the data source to 'macinchinename\SQLEXPRESS'." to get it working. Does this mean I must enter the command like so: sqlmetal /database: mymachinename\SQLEXPRESS\PhotoDB /pluralize /namespace:PhotoAlbum /code:c:\Projects\PhotoApp\app_code\PhotoDB.cs
    Thanks a lot!

  • Hi Dave,

    Are you using SQLExpress or regular SQL Server?



  • Hi,
    Can we also make photo upload using Atlas

Comments have been disabled for this content.