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.

FilesystemSQL Server BLOB
Read/write speed for large filesFast — the OS is built for thisSlower; every byte passes through the DB engine
Memory cost in ASPLow — stream to diskHigh — typically buffered
Backup consistencyTwo things to back up, and they can driftOne backup, always consistent
Transactional integrityNo — a rolled-back insert leaves an orphan fileYes — the file rolls back too
Access controlEnforced by folder permissions, easy to get wrongEnforced by your query
Execute-permission riskReal — must be configured awayNone — nothing to execute
Web farm friendlyNeeds a UNC share or shared storageWorks out of the box
Database sizeStays smallGrows 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:

  1. Store the file on disk under a generated name, outside the webroot.
  2. Insert a row with the original name, size, type, owner, and the stored path.
  3. 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.

Download the free trial Try the live demo Pricing