ASP.NET Dynamic Data and Displaying Images with a Custom Field Template

Awhile back, I posted about creating an image handler to render images stored in a database.  Someone pinged me and asked how you could use that with ASP.NET Dynamic Data web sites.  Hmm… I haven’t looked at Dynamic Data very deeply yet, so I decided to give it a shot.  I was surprised at how easy it was to do because the Dynamic Data infrastructure is so modular… it’s easy to find where to plug into it.

Setting Up the Project

First, make sure you have Visual Studio 2008 SP 1 installed, that is how you get the Dynamic Data project template we are about to use.  Open up Visual Studio 2008 and choose File / New / Web Site and choose “Dynamic Data Web Site” as the project template.

image

In the Server Explorer pane, click the “Connect to Database” button and connect to the AdventureWorks database.  If you don’t already have the AdventureWorks sample database, you can download AdventureWorks sample database from Codeplex for free.  The settings I used look like this:

image

Now that you have a new database connection created, add a new “LINQ to SQL” project item to the web project (File / New / File / LINQ to SQL Classes) and name it “AdvWorks.dbml”.

image

That will open up the LINQ to SQL design surface.  Open the AdventureWorks database in the Server Explorer pane and drag the Product, ProductPhoto, and ProductProductPhoto tables to the design surface.  The result looks like this:

image

The last step to set up the Dynamic Data project is to configure the DataContext.  Open up Global.asax and find the commented line in the RegisterRoutes method:

 //model.RegisterContext(typeof(YourDataContextType), new ContextConfiguration() { ScaffoldAllTables = false });

Uncomment it and replace it with:

 model.RegisterContext(typeof(AdvWorksDataContext), new ContextConfiguration() { ScaffoldAllTables = true });

This tells the Dynamic Data infrastructure to use your LINQ to SQL generated types, and to scaffold all of the tables.  That’s it… just hit F5 to run the project, and you should see a web page.  Click on the “ProductPhotos” link and you will see something like this:

image

Just by configuring your LINQ to SQL context and changing 1 line of code, you now have a ready-made web application to handle CRUD operations.  Wouldn’t it be nice, though, if we could actually show the picture here instead of just text?

Adding an Image Handler

The next thing we need to do is figure out how we are going to display the images.  Remember that we are emitting HTML to the client, and when the client browser comes across the HTML tag <img> it will request the image based on the URL in the src attribute.  So we can’t directly emit the image to the client here in our DataGrid, we instead need an HttpHandler that can handle the subsequent request and emit the image.  Add a new “Generic Handler” named AdvWorksProductImageHandler.ashx to the project:

 <%@ WebHandler Language="C#" Class="AdvWorksProductImageHandler" %>

using System;
using System.Web;
using System.Drawing.Imaging;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Collections.Generic;

public class AdvWorksProductImageHandler : IHttpHandler 
{
    public void ProcessRequest (HttpContext context) 
    {        
        HttpContext.Current.Response.ContentType = "image/png";
        int imageID = default(int);
        if (int.TryParse(HttpContext.Current.Request.QueryString["PictureID"], out imageID))
        {
            AdvWorksDataContext ctx = new AdvWorksDataContext();
            byte[] imageBytes = (from p in ctx.ProductPhotos
                             where p.ProductPhotoID == imageID
                             select p.ThumbNailPhoto).Single().ToArray();
                             
            MemoryStream mem = new MemoryStream(imageBytes);
            System.Drawing.Image image = System.Drawing.Image.FromStream(mem);
            image.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Png);
        }        
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}

Notice that it uses our LINQ to SQL types that we generated earlier, making this much easier than writing lots of ADO.NET code.  I left out the error handling here for brevity, make sure to handle the case when the image ID is not found.

Adding a Custom ASP.NET Dynamic Data Field Template

This is the part of ASP.NET Dynamic Data that blew me away by how modular its architecture is.  Once we have the image handler added to our project, we need to tell the ASP.NET Dynamic Data infrastructure how to use it.  Under the DynamicData folder in our project, there’s a folder called “FieldTemplates”.  Add a new ASP.NET User Control called “Picture.ascx”.  Change the markup to look like this (note that I changed the class name to “PictureField” in my example, you might decide to leave it as “DynamicData_FieldTemplates_Picture”).  We are simply adding an ASP.NET Image control and setting its OnDataBinding property.

 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Picture.ascx.cs" Inherits="PictureField" %>
<asp:Image runat="server" ID="Picture" OnDataBinding="MyDataBinding" />

In the code-behind for our control, we add the method for “MyDataBinding” method.

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class PictureField :  System.Web.DynamicData.FieldTemplateUserControl 
{    
    protected void MyDataBinding(object sender, EventArgs e)
    {
        Picture.ImageUrl = "~/AdvWorksProductImageHandler.ashx?PictureID=" + FieldValue.ToString();
    }
}

Now that we have a control to display our image, the final piece of the puzzle is to tell ASP.NET Dynamic Data how to use our new control.  We can do this by creating a partial class that corresponds to a type in our LINQ to SQL generated classes.  In the App_Code folder in your project, add a new class “ProductPhoto” and change it to be a partial class.

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

public partial class ProductPhoto
{
    [UIHint("Picture")]
    public int Picture
    {
        get
        {
            return this.ProductPhotoID;
        }
    }
}

We added a property “Picture” of type integer that will simply return the ProductPhotoID value.  We also decorated it with the System.ComponentModel.DataAnnotations.UIHintAttribute.  That tells ASP.NET Dynamic Data that, whenever it tries to render the ProductPhoto class, there is a property called ProductPhoto.Picture that is represented by our Picture.ascx custom field template.  It’s simple if you think about it… we are adding a new property into our LINQ to SQL class and telling ASP.NET Dynamic Data how to render that property.  The result is shown here:

image

 

For More Information

Visual Studio 2008 Service Pack 1 Installer

Using LINQ to SQL, Part 1 – ScottGu’s blog

ASP.NET Dynamic Data Home Page – includes samples and videos to help you get started

SQL Server Sample Databases (including Adventureworks)

System.ComponentModel.DataAnnotations.UIHintAttribute

AdvWorksDynDataSample.zip