It seemed like a simple request from a customer with an ASP.NET Web site:
come up with a way to let their users upload images to a SQL Server database.
Oh, and limit the files to a certain size, to keep traffic and database sizes
reasonable. Oh, and show a friendly error message if the file was too large.
Well, in the end, it did turn out to be simple – but it also meant I had to
touch on a batch of different parts of ASP.NET. So let me walk through what I
came up with, and perhaps you can find something that will help in your own
future plans.
Uploading and Storing the File
The first task was fairly easy. The customer’s requirements called for
storing the uploaded file, together with its content type and size, into a SQL
Server table. To handle this part, I wrote a stored procedure:
CREATE PROC procInsertFile
@File image,
@ContentType varchar(50),
@ByteSize int
AS
INSERT INTO UploadedFiles([File], ContentType, ByteSize)
VALUES(@File, @ContentType, @ByteSize)
Uploading the file is easy too. You may not have ever used it, but the HTML
standard includes the <INPUT type=”file”> tag. This tag is rendered as a
textbox and a browse button; you can use the browse button to select a file,
whose name appears in the textbox. When you submit the form, the contents of the
specified file are sent along as part of the HTTP request. Even though it’s not
in the Visual Studio toolbox, you can still use this tag in your ASP.NET Web
Forms by hand-editing the HTML. Here’s the HTML for a page that contains just
this tag and a submit button, as shown in Figure 1.
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="UploadForm.aspx.vb"
Inherits="UploadProject.UploadForm"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<title>UploadForm</title>
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<P>
<input id="UploadFile" runat="server" type="file">
<asp:Button id="btnUpload" runat="server"
Text="Upload"></asp:Button></P>
</form>
</body>
</HTML>
I also added a SqlConnection and a SqlCommand to my Web Form. The
SqlConnectin points to the appropriate database and the SqlCommand wraps the
procInsertFile stored procedure. I named the SqlCommand cmdInsertFile. Even
though the file upload control isn’t in the Toolbox, ASP.NET still includes
methods for working with it. Here’s the code that gets triggered when the user
clicks the Submit button:
Imports System.IO
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
If IsPostBack Then
' Get the uploaded data
Dim upfile As HttpPostedFile = _
UploadFile.PostedFile
' Make sure there's actually content uploaded
If upfile.ContentLength <> Nothing Then
' Load the data into a byte array
Dim StreamObject As Stream
Dim FileLength As Integer = _
upfile.ContentLength
Dim FileByteArray(FileLength) As Byte
StreamObject = upfile.InputStream
StreamObject.Read(FileByteArray, 0, _
FileLength)
' Store the fileLength stream in
' the SQL Server database
cmdInsertFile.Parameters("@File").Value = _
FileByteArray
cmdInsertFile.Parameters("@ContentType").Value = _
upfile.ContentType
cmdInsertFile.Parameters("@ByteSize").Value = _
FileLength
SqlConnection1.Open()
cmdInsertFile.ExecuteNonQuery()
SqlConnection1.Close()
End If
End If
End Sub
The key to this code is the HttpPostedFile class, which gives you direct
access to the contents of the uploaded file. It also has handy properties for
things like the file size and its content type. All that the code does is grab
this data and stuff it into a byte array, which can then be used as a parameter
to the stored procedure. With this much code written, the first requirement is
satisfied: the file gets to the SQL Server database.
Limiting the File Size
So far, so good. But at this point, the user can upload images of any
arbitrary size. This is not good for either network traffic or database size,
and raises the possibility of a nasty denial of service attack. So, the next
step is to limit the uploaded file size to a reasonable value. The customer
chose 128K as their maximum image size.
Your first thought might be to check the ContentLength property of the
uploaded file to see if it’s within the expected size, and to bail out of the
procedure if it’s too large. Unfortunately, this is only half of a solution.
While that check would prevent the file from getting to the SQL Server database,
it still requires the entire file to be uploaded first, potentially clogging the
network. Fortunately, ASP.NET provides a better solution
It turns out that you can add a tag to the Web.Config file to specify a
maximum size for uploaded files, as follows:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<httpRuntime
maxRequestLength="128"/>
...
The maxRequestLength attribute of the httpRuntime tag specifies, in KB, the
largest HTTP request that the application will accept. The default is 4096 KB,
which is a little large for most applications. Be careful about setting this too
low, though, as it applies to every request, not just uploaded files. If you’re
moving a lot of data around in ViewState, for example, you could run afoul of a
too-low setting.
With this change to the Web.Config file, large files won’t even be accepted
by the server. But the user experience could use some work. Figure 2 shows what
happens if the user tries to upload a large image with this setting in
place.
ASP.NET rejects the request in the rudest way possible, telling the client
that there’s no such page.
Telling the User What Happened
To get a better error message across to the user, you need to turn to another
corner of the application. If your first impulse is to put a Try/Catch block in
the page load to catch the error, think again: ASP.NET doesn’t load the page at
all in this situation. You have to move up the processing chain to the error
event in the global.asax.vb file, which is called for every error in the
application.
The strategy I settled on was to catch the error, and then redirect the user
back to the original page with an error message. That way, they’ll get a chance
to try again with a different file. To start the process, I added some code to
the global.asax.vb file:
Sub Application_Error(ByVal sender As Object, _
ByVal e As EventArgs)
' Fires when an error occurs
' Check to see whether we came
' from the upload form
If Path.GetFileName(Request.Path) = _
"UploadForm.aspx" Then
' Get the error details
Dim appException As System.Exception = _
Server.GetLastError()
Dim checkException As HttpException = _
CType(appException, HttpException)
' Verify the expected error
If checkException.GetHttpCode = 400 And _
checkException.ErrorCode = -2147467259 Then
' Error 400 = bad request, user
' tried to upload a file that's too large
Session("ImageTooLarge") = True
Server.ClearError()
' Go to the original target page
Response.Redirect("UploadForm.aspx")
End If
End If
' For other errors, just accept the default processing
End Sub
The next step was to add a label control to the original upload form. The
control’s name is lblTooLarge, its text is a warning that the image is too
large, and its Visible property is set to False. Then I modified the Page_Load
procedure of the upload form:
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
If Not IsPostBack Then
' Check to see whether we were redirected
' from the error page
If Session("ImageTooLarge") = True Then
lblTooLarge.Visible = True
Exit Sub
End If
Else
' Get the uploaded data
Dim upfile As HttpPostedFile = _
UploadFile.PostedFile
' Remaining code unchanged ...
End If
End Sub
If the user tries to upload a file, the error gets caught in the global.asax
file. At that point, the code retrieves the error details and verifies that this
is the error that happened; I don’t assume that it’s the only possible error in
the application! If it is, the code sets a flag in the session state and hands
control back to the upload form. The upload form checks for the flag and makes
the label visible so that the user will know what they did wrong.
ASP.NET Comes Through Again
Although I didn’t know how to put together every piece of this puzzle when I
was hit with the original requirements, I wasn’t surprised to find that ASP.NET
could handle them all. If you work with ASP.NET you’ve probably found, as I
have, that it is an extremely well thought-out framework that covers most of
what you’d like to do with a Web application. The flip side to that, of course,
is that there’s a lot to learn. Hopefully you can take at least some of the
snippets from this example and use them in your own future applications.
Mike Gunderloy is the author of over 20 books and numerous articles on
development topics, and the lead developer for Larkware. Check out his latest book, Coder to Developer from Sybex. When
he’s not writing code, Mike putters in the garden on his farm in eastern
Washington state.