Working on the online dating service exactodate.com, I had to tackle the problem of permitting users to upload image files during the registration and user-management processes. Having worked with existing content of this nature in the past, I was surprised how much work is involved in something as simple as uploading, saving, and showing images in Web applications.
It involves many issues: permitting file uploads, saving or rendering the image, storing the image in a database, retrieving and re-binding an image at a later time, cleaning up images written to disk, and ensuring that an image contains something you’d actually permit to be on your Web site without human intervention. In truth, I haven’t found a satisfactory answer for all of these problems. However, this article covers the mechanics of permitting image uploads, saving images, and rendering images, and it discusses problems with this whole process that don’t seem to have solutions. Finally, it wraps up with a call to action.
Some of you may be experts on this subject and may know solutions that don’t seem readily available. The call to action is your chance to show the rest of us how much you know. If you have a solution to the difficult challenges mentioned at the end of the article, send me an email. I will post that information in a follow-up article.
Creating the User InterfaceCreating the user interface for uploading images is comparatively straightforward. It requires the following three steps:
- Change the <form> tag to include an encoding type of “multipart/form-data.”
- Add an HtmlInputFile control from the HTML toolbox tab.
- Change the Accept attribute to accept only image files.
1. Modifying the Form TagAccording to RFC (Request For Comment) 2388, the default encoding type is “application/x-www-form-urlencoded.” This encoding type is inefficient for non-ASCII characters and large quantities of binary data. However, I have successfully uploaded images to Microsoft Internet Explorer (IE) with the default encoding type. To be safe, modify the <form> tag for the .aspx page to look something like this:
<form id=”Form1″ enctype=”multipart/form-data” method=”post” runat=”server”>
I found references to the Opera browser reporting an error when uploading files with the default encoding, but using the default encoding seemed to work okay with .jpg files in IE and Mozilla FireFox.
2. Adding the Input ControlA basic HTML input control with type=file will add a textbox and button pair that are pre-defined to display the Open Dialog and load the file when the button is clicked. The easiest way to add this control is to add the File Field control from the HTML tab of Visual Studio’s toolbox (see Figure 1).
Figure 1: The Input Field (or HtmlInputFile) Control Shown in an HTML Table
3. Changing the Accept AttributeTo make sure this control is accessible from the code-behind, ensure that the runat=”server” attribute is present, provide an ID—in the example to follow, “File1″—and set the accept attribute to accept images only. For example, “image/*” changes the file type default and is designed to permit uploading image files only. To be sure, you will add some code-behind checks to verify the file type and size.
Saving the Uploaded ImageClicking Browse displays the open file dialog (or some equivalent), and selecting a file and clicking Open stores that data in a temporary cache on the client. The image will be posted back to the server along with the form’s data. The posted file is accessible through the HtmlInputFile control’s PostedFile property. However, before you save, render, or store the uploaded file, you may want to perform some sanity checking on it. Listing 1 demonstrates how to check the file size and content type using assertions, and how to save the uploaded file.
Listing 1: Check the File Size and Type, and Save the File to Disk
Debug.Assert(File1.PostedFile != null);
Debug.Assert(File1.PostedFile.ContentType == “image/pjpeg”);
Debug.Assert(File1.PostedFile.ContentLength < 15000,
“file is too big”);
The checks are shown as assertions, and you would want to combine design time assertions with runtime conditional checks, generally performing identical checks. (Remember: Assertions are design-time checks and are for developers only.)
You can derive a file name anyway you’d like. One technique I used was to use the Request.ApplicationPath concatenated to a fixed sub-path, the SessionID for a unique folder, and a GUID to try to ensure uniqueness between users.
In Listing 1, if the file is larger than approximately 15k or is not a .jpeg file, then the image will not be uploaded and the assertion failure will be displayed in the Output window.
Rendering the Uploaded ImageAn easy way to render the image is to dynamically create a Web control Image and insert it somewhere in the page. You control the attributes and style in advance (or dynamically), and the URL is the same path you used to store the image. Knowing the scheme used to write the image files, you can use System.IO capabilities to re-read the image file—bypassing storing the filenames in Session information, or you could use the Session cache—and create an image for each of the files. Listing 2 shows a hypothetical image-rendering method based on the known-path technique.
Listing 2: Rendering All of the Images Stored in a Known Location
private void DrawImages()
const string path = “c:temp”;
if( !Directory.Exists(path)) return;
string files = Directory.GetFiles(path);
for( int i=0; i<files.Length; i++)
System.Web.UI.WebControls.Image img = new System.Web.UI.WebControls.Image();
img.ImageUrl = files[i];
img.EnableViewState = true;
img.CssClass = “MyClass”; //optional
img.AlternateText = “photo ” + (i+1).ToString();
In this example, the path is static. But you could use any scheme you’d like for managing images, including the ApplicationPath//SessionID/GUID technique described previously. The rest of the code simply sets the useful properties of the image. If you have a style sheet defined, set the CssClass property, and you can use <div> tags or an HTML table to manage layout. Figure 2 shows my smiling face loaded three times on the present Registration page for exactodate.com using an HTML table to manage layout.
Figure 2: Uploaded Images Using the HtmlInputFile Control and an HTML Table to Easily Manage Layout
FrustrationsThe steps demonstrated in this article are easy, but some loose ends bug me. The first is that I didn’t show how to load this image in a database using ADO.NET, but the article would be too long if I opened that can of worms. Try using the image data type and converting the image to a byte array (in SQL Server) to persist the image, but you could leave the image on the file system and simply store the path to each file. But there are more problematic issues than these.
I haven’t found a way to clean up all of these images. They are necessary for display, so it’s not easy to delete the files when they are stored in a database. One could delete them when the session times out, but this can happen after the user has closed the browser and doesn’t seem to be a wholly reliable means of cleaning up the saved files.
It also would be nice to simply upload the image right into the page. This can be done with code like image.Save(Response.OutputStream, image.RawFormat) where image is an object like a Bitmap. Unfortunately, this is a destructive statement and replaces all of your other content. You would think it would be possible to insert the image non-destructively. You also can write the image to a .ascx control dynamically and then load the control dynamically as well, but this approach seems like even more overhead than just saving the image.
Finally, I’d like to bind an image field directly to a control without an URL—which implies saving the file to disk again—but this also seems impossible at present. The assumption seems to be that images are always files persisted to disk. One can set the text property of a TextBox right from code, so why not the value of an image?
It seems like something as fundamental as image management is based on an older static-content mode. This makes image management clunky at best.
A Call to ArmsBy using the HtmlInputFile control and the HttpPostedFile property, you can upload and display user-added content relatively easily. What isn’t so clear is how to store and render that content without creating external file-management problems.
This article demonstrated how to get the basic job done. Now I unabashedly am asking for your feedback to help all of us find a solution whose time has come. If you have solutions to the questions posed in the Frustrations section, send them to me, and I will try to get that data updated into this article as a set of workable, if not best, practices.
About the Author
Paul Kimmel has written several books on object-oriented programming and .NET. Check out his upcoming book UML DeMystified from McGraw-Hill/Osborne (Fall 2005). Paul is also the founder and chief architect for Software Conceptions, Inc., founded in 1990. He is available to help design and build software worldwide. You may contact him for consulting opportunities or technology questions at email@example.com.
If you are interested in joining, sponsoring a meeting, or posting a job, check out www.glugnet.org, the Web page of the Greater Lansing area Users Group for .NET.
Copyright © 2005 by Paul T. Kimmel. All Rights Reserved.