In this post I am going to explain how to use the latest bits of the .NET framework (.NET 3.5 SP1) to display images from a database so the user sees them with a *.JPG extension. This is something that should be simple, yet, takes a little bit of work! The scenario I am using is as follows:

  • MSFT SQL 2005 Database
  • .NET 3.5 SP1 Entity Framework
  • .NET 2.5 SP1 Routing Engine
  • ASP.NET Web Forms
  • IIS 7 on a Vista PC (But works on Server 2008)

OK so to paint the picture, by the end of this we will be able to access an image from a URL that looks like this: http://yoursite.com/Image/imgID-1-T.jpg. This will return the image with ID of 1 and it will be a thumbnail image, but I will show how to display either the thumbnail or full size version, or what ever you want!

Displaying the images in an ASP.NET Webform

Rendering the image to a web page can be quite a cool thing to do, the code below takes a few different pieces of a pie and puts them nicely together! I will start off by accessing the data, then altering the images (if needed) and finally rendering the images to the page.

Note: As this page will be accessed via a routing rule that passes in the name of the JPG being accessed through, the JPG has to be named with the format of ImageType-ImageID-ImageSize.

For this example, I have created a folder called _Images. Inside that folder I have an aspx page called image.aspx. The actual ASPX page is standard as shown below:

   1: <%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Image.aspx.vb" Inherits="Example.Image" EnableTheming="false" StylesheetTheme="" %>
   2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3: <html xmlns="http://www.w3.org/1999/xhtml" >
   4: <head id="Head1" runat="server">
   5:     <title>Image</title>
   6: </head>
   7: <body>
   8:  
   9: </body>
  10: </html>

Database

The database in this example is a simple one table set-up with two columns in it, the table is called Images.

  • ImageID - Int field to hold the image ID
  • Image - Image field

Here is the SQL for the table:

   1: CREATE TABLE [dbo].[Images](
   2:     [ImageID] [int] IDENTITY(1,1) NOT NULL,
   3:     [Image] [image] NOT NULL,
   4:  CONSTRAINT [PK_Images] PRIMARY KEY CLUSTERED 
   5: (
   6:     [ImageID] ASC
   7: )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
   8: IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
   9: ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
  10:  
  11: GO

 

Code Behind

My data-source is the Entity Framework (EF), but you could use any source to populate the code. First off I need to reference the System.Data namespaces that will allow me to work with the EF, also I have added a reference to the System.Drawing namespace that I will use to resize the images.

   1: Imports System.Data.Objects
   2: Imports System.Data.Objects.DataClasses
   3: Imports System.Drawing.Image

The Process of generating the image

In this section I will explain how the code below works and what is happening through out.

Here is how the code below works:

  1. First we need to create the EF context that we will use to get the images from the data classes. This is the ImageData definition.
  2. The first thing we need to do in the Page_Load is to split up the name of the file. I will explain later how the file is routed to this page, but as we are using the routing rules, we need to get this name from the HttpContect. ImageName is set in the routing rules to pass the name of the image through to this page (see the Routing the Images using a *.JPG File section). 
  3. Now that we have access to the data and the image ID, we need to get the image out of the database. This is done using a lambda query on the context we opened above. We pass this to an instance of the Images class (this is an EF generated class).
  4. As we are not rendering an aspx text page, we need to change the Content Type of the output, this is now "image/jpeg"
  5. The next part of the process is to resize the image. As part of the image naming process (e.g. <img scr="/image/ImgID-1-T.jpg">) you need to make sure you have the "-" in the same order as I have and that you are using a size character you have defined in the code below.
    Notice there are three options, T and S being the resize options and the final is just rendering the full size image.
   1: Dim ImageData as new ImageEntities
   2: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
   3:     'Split the File Name out to get the information we need
   4:     Dim ImageInfo As String() = Context.Items("imagename").ToString.Split("-")
   5:     
   6:     'In this example I am only accessing one data source, you could access many,
   7:     'add a Select Case with the Image Type and select from other areas
   8:     Dim ImageType As String = ImageInfo(0) 
   9:     
  10:     'We need the ID of the image
  11:     Dim ImageID As Integer = ImageInfo(1)
  12:     
  13:     'We need to know what size to change it to
  14:     Dim ImageSize As String = ImageInfo(2)
  15:     
  16:     Dim Images As ImageModel.Images
  17:     Images = ShoppingData.Images.FirstOrDefault( _
  18:                         Function(i As Images) i.ImageID = ImageID)
  19:     
  20:     'Cast the Image data from the database to a Byte()
  21:     Dim aryContent As Byte() = DirectCast(Images.Image, Byte())
  22:     
  23:     'Create a new Memory Stream to hold the contents of the 
  24:     'image in ready for the Drawing.Image
  25:     Dim imgStream As New IO.MemoryStream
  26:     
  27:     Dim dummyCallBack As GetThumbnailImageAbort
  28:     dummyCallBack = New System.Drawing.Image.GetThumbnailImageAbort( _
  29:                                                         AddressOf ThumbnailCallback)
  30:  
  31:     imgStream.Write(aryContent, 0, aryContent.Length)
  32:  
  33:     Dim Img As System.Drawing.Image
  34:     Img = Drawing.Image.FromStream(imgStream)
  35:     
  36:     'Set the MIME tyoe
  37:     Response.ContentType = "image/jpeg"
  38:     'Resize the image if we need to
  39:     If ImageSize = "T" Then
  40:         'This will keep the image 100px high and change the width to keep the 
  41:         'ratio correct
  42:         Dim ratio = Img.Width / Img.Height
  43:         Dim height = 100
  44:         Dim width = 100 * ratio
  45:         Dim thumbnail As Drawing.Image = Img.GetThumbnailImage( _ 
  46:                                             width, height, dummyCallBack, IntPtr.Zero)
  47:         thumbnail.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)
  48:     ElseIf ImageSize = "S" Then
  49:         'This will keep the image 100px wide and change the height to keep the 
  50:         'ratio correct
  51:         Dim ratio = Img.Height / Img.Width
  52:         Dim height = 100 * ratio
  53:         Dim width = 100
  54:         Dim thumbnail As Drawing.Image = Img.GetThumbnailImage(width, height, _
  55:                                                            dummyCallBack, IntPtr.Zero)
  56:         thumbnail.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)
  57:     Else
  58:         Img.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)
  59:     End If
  60:     Response.End()
  61: End Sub

The above code will now render an image, but will have to go via the routing engine. If you want to skip that step change the Context.Items code to a Request.QueryString and pass through some parameters that way.

Routing the Images using a *.JPG File

To route the images with a certain path and extension I used the example Chris Cavanagh gave on his blog. His example was in C#, so I converted it and have the VB example below. First off, I added a new class file to my project and put the routing functions in that. Then I referenced those classes from the global.asax.

PageRouter Class

The PageRouter class will handle all the setting of page routing rules. The first thing we have to do is reference some of the namespaces we are going to use in the class:

   1: Imports System
   2: Imports System.Web
   3: Imports System.Web.UI
   4: Imports System.Web.UI.HtmlControls
   5: Imports System.Web.UI.WebControls
   6: Imports System.Web.UI.WebControls.WebParts
   7: Imports System.Web.Routing
   8: Imports System.Web.Compilation

The first class we want to create is a page handler class that will have our custom IRouteHandler and will do the registering of our rules. For more information about what happens in this class, please read Chris' blog.

   1: Public Class WebFormRouteHandler(Of T As {IHttpHandler, New})
   2:         Implements IRouteHandler
   3:         Private _virtualpath
   4:         Public Property VirtualPath() As String
   5:             Get
   6:                 Return _virtualpath
   7:             End Get
   8:             Set(ByVal value As String)
   9:                 _virtualpath = value
  10:             End Set
  11:         End Property
  12:  
  13:         Public Sub New(ByVal virtualPath As String)
  14:             _virtualpath = virtualPath
  15:         End Sub
  16:  
  17: #Region "IRouteHandler Members"
  18:         Public Function GetHttpHandler(ByVal requestContext As System.Web.Routing.RequestContext) _
  19:         As System.Web.IHttpHandler Implements System.Web.Routing.IRouteHandler.GetHttpHandler
  20:             For Each value In requestContext.RouteData.Values
  21:                 requestContext.HttpContext.Items(value.Key) = value.Value
  22:             Next
  23:             If (VirtualPath IsNot Nothing) Then
  24:                 Return (DirectCast(BuildManager.CreateInstanceFromVirtualPath(VirtualPath, GetType(T)), IHttpHandler))
  25:             Else
  26:                 Return New T()
  27:             End If
  28:         End Function
  29: #End Region
  30:  
  31: End Class

Once that is done we have to create our PageRouter class, we will reference this later in the Global.asax. This class will hold all of the routing rules we want to use in our project. This project calls on the WebFormRouteHandler to register the pages.

   1: Public Class PageRouter
   2:     Public Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
   3:             ' Note: Change the URL to "{controller}.mvc/{action}/{id}" to enable 
   4:             ' automatic support on IIS6 and IIS7 classic mode 
   5:             routes.Add(New Route("Image/{imagename}.jpg", New WebFormRouteHandler(Of Page)("~/_Images/Image.aspx")))
   6:     End Sub
   7: End Class

The code above will catch any requests coming for images at http://yoursite.com/image/imgID-1-T.jpg and send them to our image page. You will notice this is where we set the name of the context item, {imagename}.

Global.asax Code

Finally we need to fire the PageRouter class and register the routes. When the application starts, I want to set all then routing rules, so in the global.asax I add the following code to the Application_Start subroutine. Please note, you will have to reference the System.Web namespace (Imports System.Web.Routing).

   1: Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
   2:     PageRouter.RegisterRoutes(RouteTable.Routes)
   3: End Sub

Finally

Hopefully that explains how to render a database image as a *.JPG using ASP.NET SP1! There are some cool things we can now do straight out of the box, and from what I have seen of SP1 I can't wait for it to be released!