Description
This article shows an example of how to display images stored in a database in a virtual fashion using a Web Service in conjunction with an HTTP Handler.
Note: This example requires installation of Web Services Enhancements 1.0.
Introduction
There are certain occasions when you need to retrieve a binary file (image, Excel Spreadsheet, etc) from a database and return it to a user via a web page. You can do this easily enough by creating an ASP.NET page that queries the database and returns the file in the output stream to the browser. But what if you encounter a situation where the server that hosts your ASP.NET page has no direct visibility into the database where the binary files are stored.
For instance, an organization may adopt a distributed architecture where servers in the DMZ have no direct connectivity to vital data stores. In this model, all applications on the DMZ servers make calls to internal application servers, which in turn contain and execute business logic and data store access. In this type of architecture .NET gives you some tools which make it easy to retrieve binaries from the database via business objects on the Application Servers and then transport the binaries back to the DMZ Server applications. The example in this article shows how to fulfill the following requirement in an environment such as the one just described.
Create an application that will allow us to display images that exist in one of the back-end databases on the website by simply referencing them via a url. The images need to accessible as if they were physically located on the DMZ Web Server.
This example satisfies this requirement through the use of a web service on the application server and an HTTP Handler on the front-end DMZ server. The web service, when called, retrieves the binary from the database and returns it to the HTTP Handler as a DIME Attachment. The HTTP Handler then takes the attachment and streams it back to the browser.
Click the following link for quick overview of DIME (Direct Internet Messaging Encapsulation)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwse/html/wsedime.asp
Click the following link for quick overview of HTTP Handers.
http://www.15seconds.com/issue/020417.htm
The Code
Setting Up the Table
The first thing that needs to be done is to set up a table in a database to hold the image files. The following script creates a table that simply holds the name of the file and the actual file binary.
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[BinaryFiles]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [dbo].[BinaryFiles]
CREATE TABLE [dbo].[BinaryFiles] ( [Name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL , [BinaryFile] [image] NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
Now that the table has been created we need to load with images. In the code download there is a Windows App that you can use to do this.
Creating the Web Service
The next step is to create the web service that is used to retrieve the image from the database. In setting up this web service to use DIME (Direct Internet Messaging Encapsulation), you must first reference the Microsoft.Web.Services assembly and then add following to the web.config.
<webServices>
<soapExtensionTypes>
<add type= "Microsoft.Web.Services.WebServicesExtension, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
</soapExtensionTypes>
</webServices>
The web service contains two methods. One private method for retrieving the image from the database and one public Webmethod for returning the retrieved file as a DIME Attachment. Here is the code.
private byte[] _RetrieveImage(string image_name)
{
//Create the byteArray that will be returned
byte[] byteArray;
//Put together the connection string
string cn = "Persist Security Info=False;Integrated Security=false;User ID=foo;Pwd=foo;database=DevDB;foo;Connect Timeout=30";
try
{
//Create the sql string
string SqlString = "select binaryfile from dbo.binaryfiles where name = '" + image_name + "'";
//Create a new command object
SqlCommand sc = new SqlCommand();
//Set the connection
sc.Connection = new SqlConnection(cn);
//Open the connection
sc.Connection.Open();
//Set the command type
sc.CommandType = CommandType.Text;
//Set the SQL String
sc.CommandText = SqlString;
//Retrieve the image
byteArray = (byte[])sc.ExecuteScalar();
//Clean up the command object
sc.Connection.Close();
sc.Dispose();
return byteArray;
}
catch (Exception Ex)
{
throw Ex;
}
}
[WebMethod]
public string GetImage( string image_name)
{
//Get the binary data from the database
byte [] byteArray = _RetrieveImage(image_name);
//Create a memory stream to read the bytes returned
//from the database
MemoryStream mstream = new MemoryStream(byteArray,0,byteArray.Length);
// Get the SoapContext
SoapContext myContext = HttpSoapContext.ResponseContext;
//Create a new DimeAttachment.
DimeAttachment dAttachment = new DimeAttachment("0", TypeFormatEnum.MediaType,mstream);
//Get a guid for the attachement
dAttachment.Id = Guid.NewGuid().ToString();
//Add the Attachment object to the SoapContext object.
myContext.Attachments.Add(dAttachment);
//Return the attachment id.
return dAttachment.Id;
}
Creating the HTTP Handler Client
The requirements specify the images need to be accessible as if they were physically located on the server. To accomplish this, we will create a web application that uses an HTTP Handler to examine in-coming GET Requests. Based on the request file type, the HTTP Handler will invoke the web service, which will return the correct file from the database. Once the HTTP Handler has received the file from the web service, it will then transfer the file to the output stream.
To configure the web application to use DIME, the System.Web.Services and Microsoft.Web.Services assemblies must be referenced .The following must also be added to the Web.Config for this application to use the handler code.
<httpHandlers>
<add verb="GET" path="*.jpg,*.jpeg,*.gif" type="Imgs.JPGHandler,Imgs"/>
</httpHandlers>
Note: I named my Namespace Imgs, the handler is in a class called JPGHandler, and the assembly that is built for this project is also called Imgs.
If you look at the statement above you will notice that the handler will be invoked on all GET requests for files ending in .jpg, .jpg, or .gif. Since ASP.NET HTTP Handlers are part of the ASP.NET Process Model, the web server needs to be configured to send requests for files with these extensions to the aspnet_isapi.dll for processing.
To do this open up the IIS Service Manager, go to the application folder that this project is housed in, right click on the application folder, select Properties.
Now click the Configuration button and then use the Add feature on the Configuration Screen to add mappings for these file extensions. Note: Make sure the 'Verify that file exists' option is unchecked.
Now that the application has been configured to use the HTTP Handler and process image extensions through the handler, a proxy class needs to be created to handle communication between the handler and the web service. The proxy for this project was generated using the WSDL tool.
wsdl http://localhost/DimeService/DimeService.asmx
Before the code generated by the wsdl tool can be compiled, the DimeServiceWS class needs to have the class it inherits from changed from System.Web.Services.Protocols.SoapHttpClientProtocol to Microsoft.Web.Services.WebServicesClientProtocol.
Handler Code
using System;
using System.Web;
using Microsoft.Web.Services;
namespace Imgs
{
///
/// Summary description for JPGHandler.
///
public class JPGHandler :IHttpHandler
{
// Override the ProcessRequest method.
public void ProcessRequest(HttpContext context)
{
//Instantiate the proxy class
DimeServiceWS ws = new DimeServiceWS();
//Get the Raw URL
string filename = context.Request.RawUrl;
//Parse out the requested file name from the Raw URL
int marker = filename.LastIndexOf("/");
filename = filename.Substring((marker +1) ,filename.Length - (marker +1));
//Parse out the file extenstion from the filename
marker = filename.LastIndexOf(".");
string fileExtension = filename.Substring((marker +1) ,filename.Length - (marker +1));
fileExtension = fileExtension.ToUpper();
//Call the web service and get the Dime ID
string imageid = ws.GetImage(filename);
// Check if response message contains any attachments.
if (ws.ResponseSoapContext.Attachments.Count > 0)
{
//Set the content type and image format based
//on the file's extension
System.Drawing.Imaging.ImageFormat iformat;
switch (fileExtension.ToUpper())
{
case "JPEG":
case "JPG":
iformat = System.Drawing.Imaging.ImageFormat.Jpeg;
context.Response.ContentType = "image/JPEG";
break;
case "BMP":
iformat = System.Drawing.Imaging.ImageFormat.Bmp;
context.Response.ContentType = "image/BMP";
break;
case "GIF":
iformat = System.Drawing.Imaging.ImageFormat.Gif;
context.Response.ContentType = "image/GIF";
break;
default:
iformat = System.Drawing.Imaging.ImageFormat.Jpeg;
context.Response.ContentType = "image/JPEG";
break;
}
//Retrieve the image from the stream
System.Drawing.Image locimg = System.Drawing.Image.FromStream
ws.ResponseSoapContext.Attachments[0].Stream);
//Save the image to the output stream
locimg.Save(context.Response.OutputStream,iformat);
}
}
// Override the IsReusable property.
public bool IsReusable
{
get { return false; }
}
}
}
In looking at this code you can see that retrieving the file from the web service is pretty simple and straightforward process. Also note, you can have multiple attachments sent back via DIME.
Retrieving the Images
With the web service and HTTP Handler in place, images can now be accessed via url as if they were on the web server.
Summary
As you can see this solution is pretty straightforward. Through the use of a webservice, DIME, and an HTTP Handler you can easily provide virtual access to an object located in a remote data store. Though this example focuses on retrieving images, the methodology involved can be applied to many situations.