Often times, just uploading a file is not enough since one normally has to upload information related to the file being uploaded as well. This could be achieved in a two step process where the first step is the "additional" information required and the second step is uploading of the actual file or vise-versa.
After publishing the "Uploading files from a web browser" article I got a number of requests for this particular need. So here is the solution for the same.
The primary functionality is provided for in the form of a few objects that work together to simplify the whole process for the end user of these objects.
Click here to see a Demo project in action. This demo basically shows you a data entry screen where one of the form fields is the file to be uploaded. After the file and other data has been uploaded, you see a screen that shows you the data that was just uploaded and a link to download the files that were just uploaded. That way, you can cofirm that the files did infact get uploaded.

Using the classes presented here it is possible to upload multiple files along with form data. The classes make it really simple to get at the files as well as form fields after the Request has been parsed out.
HTTP Header and Content
For those interested in some more details, the following is a sample of an HTTP Header and Content that is sent out using the on-line demo:
The Actual HTTP Header and Content
POST http://www.matlus.com/scripts/multifileupload.dll/Upload HTTP/1.0
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Referer: http://www.matlus.com/scripts/multifileupload.dll
Accept-Language: en-us
Content-Type: multipart/form-data; boundary=---------------------------7d12dc371306a8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: www.matlus.com
Content-Length: 43374
Proxy-Connection: Keep-Alive
Pragma: no-cache
-----------------------------7d12dc371306a8
Content-Disposition: form-data; name="txtPersonName"
Shiv Kumar
-----------------------------7d12dc371306a8
Content-Disposition: form-data; name="txtDescription"
This is a test file
-----------------------------7d12dc371306a8
Content-Disposition: form-data; name="DocumentBlob"; filename="C:\Documents and Settings\Administrator\Desktop\table.bmp"
Content-Type: image/bmp
BMŽ?
What's important to note here is the boundary you can see in the Content-Type HTTP header. This same boundary is used (and should be used) to delimit each file or form field in the content section as you can see from the sample content above. When using multipart/form-data, it doesn't matter if the content is form fields or files. They are all treated the same. The way a file is determined is from the Content-Type section if present in the body of the Content. For example:
-----------------------------7d12dc371306a8
Content-Disposition: form-data; name="DocumentBlob"; filename="C:\Documents and Settings\Administrator\Desktop\table.bmp"
Content-Type: image/bmp
BMŽ?
Notice from above, that the Content-Type is image/bmp. This indicates that hte file uploaded was a bitmap image. You don't see the actual content of the bitmap here, but in reality, there is tons of "data" in place of BMŽ?
The project for the demo is available for download at the end of this article along with the custom objects required to do the job.
The Helper objects
In all, there are three objects that do the job. The main object is THTTPFile. This object holds a single file and some properties of this file. The class for this object looks like this:
THTTPFile = class(TObject)
private
FFieldName: string;
FContentType: string;
FFileName: string;
FFileData: TStream;
procedure SetFileData(const Value: TStream);
public
constructor Create;
destructor Destroy;override;
procedure SaveToFile(SaveAsFile: string);
procedure SaveToStream(Stream: TStream);
property FieldName: string read FFieldName write FFieldName;
property ContentType: string read FContentType write FContentType;
property FileName: string read FFileName write FFileName;
property FileData: TStream read FFileData write SetFileData;
end;
The next object is THTTPFiles. This object basically holds a list of THTTPFile objects. It is derived from TObjectList and so has the ability "own objects and so takes care of freeing objects when it gets destroyed. This object was introduced in Delphi 5. The class for this object looks like this:
THTTPFiles = class(TObjectList)
private
FOwnsObjects: Boolean;
protected
function GetItem(Index: Integer): THTTPFile;
procedure SetItem(Index: Integer; AObject: THTTPFile);
public
function Add(AObject: THTTPFile): Integer;
function Remove(AObject: THTTPFile): Integer;
function IndexOf(AObject: THTTPFile): Integer;
procedure Insert(Index: Integer; AObject: THTTPFile);
property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;
property Items[Index: Integer]: THTTPFile read GetItem write SetItem; default;
end;
The final object is called TMsMultipartFormParser. (Ms is the prefix I use for all my objects/components etc.). This object's primary function is to parse the form data that comes in and add to the collection of HTTPFiles for each file that was downloaded. In addition to managing the files, it also exposes the property called ContentFields. Yes, that's right, ContentFields. You can treat this property similar to the Request object's property. What the TMsMultipartFormParser does really is, if it detects the form field to be a file, then it adds to the Files list, If it finds it to be a form field then it adds it to the ContentFields list.
The class for TMsMultipartFormParser looks like this:
TMsMultipartFormParser = class(TObject)
private
FHTTPFiles: THTTPFiles;
FContentFields: TStrings;
public
constructor Create;
destructor Destroy;override;
procedure Parse(Request: TWebRequest);
property Files: THTTPFiles read FHTTPFiles;
property ContentFields: TStrings read FContentFields;
end;
Using the Multipart Form Parser object
The demo project is a good example of using this object. In particular, the action that accepts the file (and form), which is called waUpload with a pathinfo of /Upload. The code for this action looks like this:
procedure TWebModule1.WebModule1waUploadAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
i: Integer;
begin
with TMsMultipartFormParser.Create do
begin
Parse(Request);
for i := 0 to Files.Count -1 do
Files[i].SaveToFile(Request.PathTranslated '\' Files[i].FileName);
Response.Content :=
'<html>'#13#10
' <head>'#13#10
' <title>Testing and Development</title>'#13#10
'<link rel="stylesheet" type="text/css" href="http://www.matlus.com/home-styles.css" />'#13#10
' </head>'#13#10
'<body>'#13#10
'<table class="formbackground" cellpadding="2" cellspacing="2">'#13#10
'<caption class="formcaption"><b>Upload File(s) along with Form fields</b><caption>'#13#10
' <tr>'#13#10
' <td class="fieldnames">Person Name</td>'#13#10
' <td><input name="txtPersonName" type="text" value="' ContentFields.Values['txtPersonName'] '" style="width: 200px;"></td>'#13#10
' </tr>'#13#10
' <tr>'#13#10
' <td class="fieldnames">Description of File being uploaded</td>'#13#10
' <td><input name="txtDescription" type="text" value="' ContentFields.Values['txtDescription'] '" style="width: 200px;"></td>'#13#10
' </tr>'#13#10
' <tr>'#13#10
' <td class="fieldnames">Name of File Uploaded</td>'#13#10
' <td><input name="txtDescription" type="text" value="' Files[0].FileName '" style="width: 300px;"></td>'#13#10
' </tr>'#13#10;
{ For each file that was uploaded, create a link }
for i := 0 to Files.Count -1 do
Response.Content := Response.Content
' <tr>'#13#10
' <td class="fieldnames">Click Here to download the file</td>'#13#10
' <td><a class="fieldlink" href="ftp://' Request.Host '/Upload/' Files[i].FileName '">Download File</a></td>'#13#10
' </tr>'#13#10;
Response.Content := Response.Content
'</table>'#13#10
'</body>' #13#10
'</html>'#13#10;
Free;
end;
end;
Request.Content contains only the first 48K of data. The "additional" data (additional data being the data over and above 48K) is still only available from the web server. Secondly, all of the data is in the form of a steam (sequential bytes of data) where
Request.Content contains the first 48K, and the web server has the rest.
The Web server's "socket" is holding on to this additional data and once the multi-part parser reads it, its "converted" into files and fields as the case may be (as per the multipart/form-data protocol). For all of the data to be "valid" it has to be read in as a sequential array of bytes (that is Content Additional data and in that sequence) and *then* broken up into its constituent parts.
The rule of thumb here is if the Request.Content-Type is multipart/form-data don't touch Request.Content. Rely totally on the multipart parser for any data that might have been sent down the pike.
(As per the note above) It goes without saying, that the first thing (and probably the only thing) you should do with the Content property is to parse it. That's exactly what we do here. We create an instance of TMsMultipartFormParser and parse the Request. As shown below
with TMsMultipartFormParser.Create do
begin
Parse(Request);
for i := 0 to Files.Count -1 do
Files[i].SaveToFile(Request.PathTranslated '\' Files[i].FileName);
The rest of the code shows you how to use the Files property to save files that were uploaded to the ISAPI, and how to use the ContentFields property to extract the form fields. The Files property, which is a THTTPFiles object, which in turn is a list of THTTPFiles objects, also has a SaveToStreammethod that you can use if you want to save the files that are uploaded to a blob file in a database or any other TStream derived object.

Please note that the ISAPI (in other words IUSER_<machinename>) needs to have write access for the folder that it will save the files to. This permission needs to be given using NTFS (use Windows Explorer for this) and not the InternetService Manager. Using this object it is simple enough to save the stream to a database blob field directly using the
SaveToStream method of the THTTPFile object.
In the demo, the PathInfo of the action that receives the Form's post action is /Upload. Therefore, the path to which the files are being saved is a folder under my web server's root folder with the path /Upload or C:\inetpub\wwwroot\upload. ISAPI applications have write access to this folder.
The ftp server, on the other hand has a virtual folder called /Upload and it points to this physical path. I had to do all this to get the demo to work and so this is sprcific to this demo only. You may have/need your own setup/configuration for this. An FTP server is not required to upload files per se. We use HTTP to upload files to the web server.