Learn · Folder uploads

Folder uploads that keep their structure

Browsers can hand your page an entire directory tree — but a standard upload flattens it into bare file names. Here is how to preserve the structure end to end in Web Forms, and recreate it safely on the server.

How the browser delivers a folder

Two mechanisms: the directory picker (<input type="file" webkitdirectory>, every file carries a webkitRelativePath) and drag-and-drop traversal (webkitGetAsEntry() walks the dropped folder recursively; each entry has a fullPath). Both exist only at selection time — the upload request itself carries just a file name, which is where most stacks lose the tree.

The AjaxUploader flow

The control captures the relative path at selection time and transmits it with each upload. Server-side it is sanitized (traversal segments removed, invalid characters neutralized) and stored with the upload, then surfaced on the event you already handle:

<au:AjaxFileUpload ID="Uploader1" runat="server"
    AllowMultiple="true"
    AutoPostBack="true"
    OnFileUploaded="Uploader1_FileUploaded" />
protected void Uploader1_FileUploaded(object sender, FileUploadedEventArgs e)
{
    // e.RelativePath: "photos/2026/beach.jpg" for folder uploads, null for plain files.
    // Already traversal-sanitized - still combine it under a root YOU choose.
    var relative = e.RelativePath ?? Path.GetFileName(e.FileName ?? "file");
    var destination = Path.Combine(Server.MapPath("~/App_Data/uploads"),
                                   relative.Replace('/', '\\'));

    Directory.CreateDirectory(Path.GetDirectoryName(destination));
    new UploadService().CopyFile(e.FileGuid, destination);
}

On the client, task.relativePath is available in every callback (see the Folder Drop demo, which lists each file's preserved path as it completes).

Why sanitizing is not optional

A client-supplied path is attacker-controlled text: without cleaning, ..\..\web.config is a perfectly valid "relative path". AjaxUploader removes ./.. segments, normalizes separators, neutralizes drive-letter colons and invalid characters, and drops oversized input — before the value is ever stored. Your code still decides the root folder; the sanitized path can only ever descend from it.

Details worth knowing

  • e.RelativePath is null for plain (non-folder) selections — a bare file name carries no structure, so nothing is invented.
  • Chunked uploads preserve the path too; it travels in the chunk-complete request.
  • Client-side image transforms (resize, compress, watermark) keep the path across the new file they produce.
  • Requires AjaxUploader.dll File version 5.3.0.808 or later (right-click → Properties → Details).