Saving Uploaded Files to a Folder or to SQL Server in Classic ASP
Disk or database? The trade-offs, plus working ASP code for each.
Which one should you pick?
The short version: store files on disk and metadata in the database, unless you have a specific reason not to. The long version is the trade-off table.
| Filesystem | SQL Server BLOB | |
|---|---|---|
| Read/write speed for large files | Fast — the OS is built for this | Slower; every byte passes through the DB engine |
| Memory cost in ASP | Low — stream to disk | High — typically buffered |
| Backup consistency | Two things to back up, and they can drift | One backup, always consistent |
| Transactional integrity | No — a rolled-back insert leaves an orphan file | Yes — the file rolls back too |
| Access control | Enforced by folder permissions, easy to get wrong | Enforced by your query |
| Execute-permission risk | Real — must be configured away | None — nothing to execute |
| Web farm friendly | Needs a UNC share or shared storage | Works out of the box |
| Database size | Stays small | Grows fast; backups get slow |
Rule of thumb: files under ~256 KB that must be transactional (signatures, small documents) do well in the database. Anything media-sized belongs on disk. If files must survive a rolled-back transaction cleanly and you are on SQL Server 2008 or later, FILESTREAM gives you both.
Saving to a folder
The simplest form — let the uploader write files directly as they arrive:
<%
Dim uploader
Set uploader = new AspUploader
uploader.Name = "myuploader"
uploader.MultipleFilesUpload = true
uploader.SaveDirectory = "savefiles" ' relative to this page
uploader.Render()
%>
More often you want control over the name and a database row per file:
<%
Dim fso, destDir, list, i, mvcfile, ext, storedName
Set fso = Server.CreateObject("Scripting.FileSystemObject")
destDir = Server.MapPath("uploads")
If Not fso.FolderExists(destDir) Then fso.CreateFolder destDir
If Request.Form("myuploader") & "" <> "" Then
list = Split(Request.Form("myuploader"), "/")
For i = 0 To UBound(list)
Set mvcfile = uploader.GetUploadedFile(list(i))
ext = ""
If InStrRev(mvcfile.FileName, ".") > 0 Then
ext = LCase(Mid(mvcfile.FileName, InStrRev(mvcfile.FileName, ".")))
End If
' GUID-based storage name: no collisions, no traversal, no odd characters
storedName = list(i) & ext
mvcfile.MoveTo destDir & "\" & storedName
' record the original name for display
SaveFileRow storedName, mvcfile.FileName, mvcfile.FileSize
Next
End If
%>
Spread files across subfolders
NTFS slows down noticeably once a single folder holds hundreds of thousands of entries, and so does every backup tool you own. Shard by date or by the first characters of the GUID:
<%
Dim sub1
sub1 = Year(Now()) & "\" & Right("0" & Month(Now()), 2) ' uploads\2026\08
If Not fso.FolderExists(destDir & "\" & Year(Now())) Then fso.CreateFolder destDir & "\" & Year(Now())
If Not fso.FolderExists(destDir & "\" & sub1) Then fso.CreateFolder destDir & "\" & sub1
mvcfile.MoveTo destDir & "\" & sub1 & "\" & storedName
%>
Saving into SQL Server
Use VARBINARY(MAX). IMAGE has been deprecated since SQL Server 2005:
CREATE TABLE Uploads (
UploadId INT IDENTITY PRIMARY KEY,
FileName NVARCHAR(260) NOT NULL,
ContentType NVARCHAR(100) NULL,
FileSize BIGINT NOT NULL,
FileData VARBINARY(MAX) NOT NULL,
UploadedUtc DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
);
Read the temp file with ADODB.Stream and pass the bytes as a parameter — never build
a SQL string containing binary data:
<%
Dim stm, cn, cmd, prm, bytes, tempPath
tempPath = mvcfile.GetTempFilePath()
Set stm = Server.CreateObject("ADODB.Stream")
stm.Type = 1 ' adTypeBinary
stm.Open
stm.LoadFromFile tempPath
bytes = stm.Read
stm.Close
Set cn = Server.CreateObject("ADODB.Connection")
cn.Open Application("ConnString")
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = cn
cmd.CommandText = "INSERT INTO Uploads (FileName, FileSize, FileData) VALUES (?, ?, ?)"
cmd.Parameters.Append cmd.CreateParameter("@n", 202, 1, 260, mvcfile.FileName) ' adVarWChar
cmd.Parameters.Append cmd.CreateParameter("@s", 20, 1, , mvcfile.FileSize) ' adBigInt
Set prm = cmd.CreateParameter("@d", 205, 1, LenB(bytes)) ' adLongVarBinary
prm.AppendChunk bytes
cmd.Parameters.Append prm
cmd.Execute
cn.Close
%>
stm.Read pulls the whole file into memory. For anything above a few megabytes, read
and AppendChunk in slices — or reconsider whether this file belongs in the
database at all.
Serving a file back out of the database
<%
Dim rs
Set rs = cn.Execute("SELECT FileName, FileData FROM Uploads WHERE UploadId = " & CLng(Request.QueryString("id")))
If Not rs.EOF Then
Dim q
q = Chr(34)
Response.Clear
Response.ContentType = "application/octet-stream"
Response.AddHeader "Content-Disposition", "attachment; filename=" & q & rs("FileName") & q
Response.AddHeader "X-Content-Type-Options", "nosniff"
Response.BinaryWrite rs("FileData")
Response.End
End If
%>
Note CLng() on the query string — concatenating raw input into SQL is how upload
galleries become SQL injection demos.
The hybrid most applications end up with
Metadata in the database, bytes on disk, and a serving script that joins them:
- Store the file on disk under a generated name, outside the webroot.
- Insert a row with the original name, size, type, owner, and the stored path.
- Serve through
download.asp?id=123, which checks authorisation, then streams the file.
<%
' download.asp - authorised streaming from outside the webroot
Dim rs, stm, q
q = Chr(34)
If Session("UserID") = "" Then Response.Status = "403 Forbidden" : Response.End
Set rs = cn.Execute("SELECT StoredPath, FileName FROM Uploads WHERE UploadId=" & CLng(Request.QueryString("id")) & _
" AND OwnerId=" & CLng(Session("UserID")))
If rs.EOF Then Response.Status = "404 Not Found" : Response.End
Set stm = Server.CreateObject("ADODB.Stream")
stm.Type = 1
stm.Open
stm.LoadFromFile rs("StoredPath") ' e.g. D:\filestore\2026\08\{guid}.pdf
Response.ContentType = "application/octet-stream"
Response.AddHeader "Content-Disposition", "attachment; filename=" & q & rs("FileName") & q
Response.BinaryWrite stm.Read
stm.Close
%>
This gets you database-grade access control with filesystem-grade throughput, and there is no URL that maps directly to an uploaded file — which closes the biggest upload security hole by construction.
Frequently asked questions
Should I store uploaded files in the database or on disk?
Disk for anything media-sized; database for small files that must be transactional. Metadata belongs in the database either way.
How do I avoid overwriting files with the same name?
Store under a generated name — a GUID plus the validated extension — and keep the original name in the database as display text.
Can Classic ASP write files outside the webroot?
Yes. Use an absolute path rather than Server.MapPath, and grant the application pool
identity Modify on that folder.
Skip the plumbing
ASP Uploader does everything on this page out of the box: multi-file selection, a real progress bar, client and server validation, chunked large-file transfer, and no COM component to register. Drop the folder on your server and add one include line.