The folks at Developer Express exchange ideas and information just like a lot of you probably do with your co-workers. One of my go to guys is Mehul Harry. I probably drive him a little nuts with questions about code samples and videos, but Mehul generally comes through for me and has a good disposition when imposed upon. Many of you have probably seen some of his videos like the “How to Display Images Direct(ly) From the Database” at http://tv.devexpress.com/ASPxBinaryImageIntro.movie. (They discourage me from doing videos because someone posted on on YouTube and mothers of little children complained.)
In Mehul’s video he demonstrated how to show display an image directly from a database old school by using an <img> tag with the src attribute referring to an .aspx page. You can swap out the .aspx page with an IHttpHandler (an .ashx page) too if you want to. And then Mehul sagely demonstrates how much easier it is just to use an ASPxGridView and an ASPxBinaryImage which will manage all of the plumbing for rendering the image for you. This blog entry expands on Mehul’s video. In the example I will show you how to tweak the video by using GDI+ to add content to the database image before showing it. (The example also uses the Northwind Employees table which has OLE headers stuffed in the Photo column, making it a little harder to show these pictures directly without tweaking.)
To prepare the example create a Web Site project and add an ASPxGridView to the Web page. Use the Chose Data Source option from the grid’s Smart tags menu and add a SqlDataSource that refers to the Employees table. Add whatever columns you’d like, include the FirstName, LastName, and Photo columns at a minimum because they are used in the sample. One minor nuisance is that the Photo column will be returned but a column won’t be added to the grid. The Photo column can be added from the Smart tags menu’s Columns item. Just add a Column and set the FieldName property to Photo. The next step is to select Edit Templates from the ASPxGridView’s Smart tags menu and pick the DataItemTemplate from the Photo column. In the DataItemTemplate drag and drop an ASPxBinaryImage into the template (look at Figure 1).
Figure 1: Drag and drop an ASPxBinaryImage to the DataItemTemplate and bind the Photo column to the ASPxBinaryImage’s Value attribute for basic image display.
Now, if the image weren’t whacked out then binding the ASPxBinaryImage’s Value attribute to the Photo column would be the final step in displaying the image from the database (see Listing 1). The Northwind images have OLE headers in them and it helps to strip these off. Plus, I want to show you how to add content to the image with GDI+.
Listing 1: The ASP.NET shows how to bind an image column to an ASPxBinaryImage control (shown in bold).
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
<%@ Register assembly="DevExpress.Web.ASPxGridView.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.Web.ASPxGridView" tagprefix="dxwgv" %>
<%@ Register assembly="DevExpress.Web.ASPxEditors.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.Web.ASPxEditors" tagprefix="dxe" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<dxwgv:ASPxGridView ID="ASPxGridView1" runat="server"
AutoGenerateColumns="False" DataSourceID="SqlDataSource1"
KeyFieldName="EmployeeID">
<Columns>
<dxwgv:GridViewDataTextColumn FieldName="EmployeeID" ReadOnly="True"
VisibleIndex="0">
<EditFormSettings Visible="False" />
</dxwgv:GridViewDataTextColumn>
<dxwgv:GridViewDataTextColumn FieldName="LastName" VisibleIndex="1">
</dxwgv:GridViewDataTextColumn>
<dxwgv:GridViewDataTextColumn FieldName="FirstName" VisibleIndex="2">
</dxwgv:GridViewDataTextColumn>
<dxwgv:GridViewDataTextColumn FieldName="Photo" VisibleIndex="3">
<DataItemTemplate>
<dxe:ASPxBinaryImage ID="ASPxBinaryImage2" runat="server" Value='<%# Eval("Photo") %>' >
</dxe:ASPxBinaryImage>
</DataItemTemplate>
</dxwgv:GridViewDataTextColumn>
</Columns>
</dxwgv:ASPxGridView>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
SelectCommand="SELECT [EmployeeID], [LastName], [FirstName], [Photo] FROM [Employees]">
</asp:SqlDataSource>
</div>
</form>
</body>
</html>
Now, if you want to tweak the image—let’s say draw the employee’s name on top of the graphic—then replace the simple script block that binds Photo to Value with a function call, and you can pass column values into the function. Here is a replacement tag for the ASPxBinaryImage that will call a method called GetPhoto, passing in the Photo, FirstName, and LastName.
<dxe:ASPxBinaryImage ID="ASPxBinaryImage1" runat="server" Value='<%# GetImage( Eval("Photo"), Eval("FirstName"), Eval("LastName")) %>' >
</dxe:ASPxBinaryImage>
To support the script block statement add a protected method named GetImage that returns a byte[] and accept three arguments: Photo, FirstName, and LastName. Images with OLE headers need the first 78 bytes stripped—according to dozens of other blogs and articles—but most images won’t need this extra bit of effort. Listing 2 contains the implementation of the GetImage method.
Listing 2: The implementation of the code-behind showing the GetImage method.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
public partial class Default2 : System.Web.UI.Page
{
private Font font = new Font("Courier New", 10, FontStyle.Bold);
protected void Page_Load(object sender, EventArgs e)
{
}
protected byte[] GetImage(object photo, object FirstName, object LastName)
{
if (photo == null) return null;
byte[] data = (byte[])Convert.ChangeType(photo, typeof(byte[]));
MemoryStream stream = new MemoryStream(data, 78, data.Length - 78);
System.Drawing.Image image = System.Drawing.Image.FromStream(stream);
Bitmap bitmap = new Bitmap(image.Width, image.Height);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.DrawImage(image, 0, 0);
graphics.FillRectangle(Brushes.Black, 0, image.Height - 22, image.Width, 22);
graphics.DrawString(string.Format("{0} {1}", FirstName.ToString(), LastName.ToString()),
font, Brushes.White, 2, image.Height - 20);
stream.SetLength(0);
bitmap.Save(stream, ImageFormat.Jpeg);
return stream.ToArray();
}
}
In a nutshell GetImage converts the byte array to an image, a Bitmap is used as the canvas to create the GDI+ object, and support the custom drawing. The Employee name is drawn in a black box on the image and it is re-converted to a byte[] which is what the ASPxBinaryImage expects. If you are using Northwind images with an OLE header then strip the first 78 bytes out of the array; this step is shown in the creation of the MemoryStream. If the image displays fine all by itself then modify the MemoryStream constructor as follows: MemoryStream stream = new MemoryStream(data).
You can use this technique to create completely custom graphics and drawings too. Simply create a bitmap, create a Graphics object from the Bitmap and draw away. When you are finished stuff the output in the HttpResponse stream or return (as demonstrated above) as part of a binding statement for an ASPxBinaryImage.