Ask Ben: Uploading Multiple Files Using ColdFusion

Posted January 8, 2008 at 8:53 AM

Tags: ColdFusion, Ask Ben

After posting a while back about uploading a file and emailing it using ColdFusion, I was asked to put up a demo of how to upload multiple files using ColdFusion. For this demo, I am concentrating only on the upload aspect and not worrying about any emailing functionality as I believe that adding that is easy after the upload is complete.

Before I show the code, I just want to preempt some stuff. For starters, I am using a LOT Of error handling via ColdFusion's CFTry / CFCatch tags. In practice, I don't really use that much error handling. However, I am just trying to instill some good practice here and drive home the fact that when dealing with a third party service (the file system), there is always potential for errors to occur. And, the truth is, I should really use better error handling anyway.

That being said, this code is only partially tested as I could not generate any errors on the file upload. I think the error handling looks good, but again, not fully tested. The best that I could do was to change the NameConflict attribute in the CFFile tag from MakeUnique to Error and upload two files of the same name, which threw the following error:

There was a problem uploading file #2: File overwriting is not permitted in this instance of the CFFile tag.

So, it seems to be working; but, I just want to stress that I couldn't generate a file system error and therefore some of this code is still theoretical.

Additionally, I build the page such that the number of files to be uploaded can be variable. Right now, it is set to have 5 file fields, but that can be set using the REQUEST.FileCount variable. I know that this kind of stuff is handled very Web 2.0 dynamic style these days, but for this demo, I wanted to keep it really simple and straight forward.

That being said, here is the code:

 Launch code in new window » Download code as text file »

  • <!---
  • Set the number of files that can uploaded in a single
  • form submission.
  • --->
  • <cfset REQUEST.FileCount = 5 />
  •  
  • <!--- Set the destination folder for uploads. --->
  • <cfset REQUEST.UploadPath = ExpandPath( "./uploads/" ) />
  •  
  •  
  •  
  • <!--- Param the appropriate number of file fields. --->
  • <cfloop
  • index="intFileIndex"
  • from="1"
  • to="#REQUEST.FileCount#"
  • step="1">
  •  
  • <!--- Param file value. --->
  • <cfparam
  • name="FORM.file#intFileIndex#"
  • type="string"
  • default=""
  • />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Param upload flag. --->
  • <cftry>
  • <cfparam
  • name="FORM.submitted"
  • type="numeric"
  • default="0"
  • />
  •  
  • <cfcatch>
  • <cfset FORM.submitted = 0 />
  • </cfcatch>
  • </cftry>
  •  
  •  
  • <!--- Set up an array to hold errors. --->
  • <cfset arrErrors = ArrayNew( 1 ) />
  •  
  •  
  • <!--- Check to see if the form has been submitted. --->
  • <cfif FORM.submitted>
  •  
  • <!---
  • Here is where we would validate the data; however,
  • in this example, there really isn't anything to
  • validate. In order to validate something, we are going
  • to require at least one file to be uploaded!
  • --->
  •  
  •  
  • <!---
  • Since we are going to require at least one file, I am
  • going to start off with an error statement. Then, I am
  • gonna let the form tell me to DELETE IT.
  • --->
  • <cfset ArrayAppend(
  • arrErrors,
  • "Please select at least one file to upload"
  • ) />
  •  
  •  
  • <!--- Loop over the files looking for a valid one. --->
  • <cfloop
  • index="intFileIndex"
  • from="1"
  • to="#REQUEST.FileCount#"
  • step="1">
  •  
  • <cfif Len( FORM[ "file#intFileIndex#" ] )>
  •  
  • <!--- Clear the errors array. --->
  • <cfset ArrayClear( arrErrors ) />
  •  
  • <!--- Break out of loop. --->
  • <cfbreak />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • Check to see if there were any form validation
  • errors. If there are no errors, then we can continue
  • to process the form. Otherwise, we are going to skip
  • this and just let the page render again.
  • --->
  • <cfif NOT ArrayLen( arrErrors )>
  •  
  • <!---
  • Create an array to hold the list of uploaded
  • files.
  • --->
  • <cfset arrUploaded = ArrayNew( 1 ) />
  •  
  •  
  • <!---
  • Loop over the form fields and upload the files
  • that are valid (have a length).
  • --->
  • <cfloop
  • index="intFileIndex"
  • from="1"
  • to="#REQUEST.FileCount#"
  • step="1">
  •  
  • <!--- Check to see if file has a length. --->
  • <cfif Len( FORM[ "file#intFileIndex#" ] )>
  •  
  • <!---
  • When uploading, remember to use a CFTry /
  • CFCatch as complications might be encountered.
  • --->
  • <cftry>
  • <cffile
  • action="upload"
  • destination="#REQUEST.UploadPath#"
  • filefield="file#intFileIndex#"
  • nameconflict="makeunique"
  • />
  •  
  • <!---
  • Store this file name in the uploaded file
  • array so we can reference it later.
  • --->
  • <cfset ArrayAppend(
  • arrUploaded,
  • (CFFILE.ServerDirectory & "\" & CFFILE.ServerFile)
  • ) />
  •  
  •  
  • <!--- Catch upload errors. --->
  • <cfcatch>
  •  
  • <!--- Store the error. --->
  • <cfset ArrayAppend(
  • arrErrors,
  • "There was a problem uploading file ###intFileIndex#: #CFCATCH.Message#"
  • ) />
  •  
  • <!---
  • Break out of the upload loop as we
  • don't want to deal with any more
  • files than we have to.
  • --->
  • <cfbreak />
  •  
  • </cfcatch>
  • </cftry>
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Check to see if we have any form errors. --->
  • <cfif ArrayLen( arrErrors )>
  •  
  •  
  • <!---
  • We encountered an error somewhere in the upload
  • process. As such, we want to clean up the server
  • a bit by deleteing any files that were
  • successfully uploaded as part of this process.
  • --->
  • <cfloop
  • index="intFileIndex"
  • from="1"
  • to="#ArrayLen( arrUploaded )#"
  • step="1">
  •  
  • <!--- Try to delete this file. --->
  • <cftry>
  • <cffile
  • action="delete"
  • file="#arrUploaded[ intFileIndex ]#"
  • />
  •  
  • <cfcatch>
  • <!--- File could not be deleted. --->
  • </cfcatch>
  • </cftry>
  •  
  • </cfloop>
  •  
  •  
  • <cfelse>
  •  
  •  
  • <!---
  • !! SUCCESS !!
  • The files were properly uploaded and processed.
  • Here is where you might forward someone to some
  • sort of success / confirmation page.
  • --->
  •  
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!--- Set the content type and reset the output buffer. --->
  • <cfcontent
  • type="text/html"
  • reset="true"
  • />
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>Multiple File Uploads</title>
  • </head>
  • <body>
  •  
  • <cfoutput>
  •  
  • <h1>
  • Multiple File Upload ColdFusion Example
  • </h1>
  •  
  •  
  • <!--- Check to see if we have any errors to display. --->
  • <cfif ArrayLen( arrErrors )>
  •  
  • <p>
  • Please review the following errors:
  • </p>
  •  
  • <ul>
  • <cfloop
  • index="intError"
  • from="1"
  • to="#ArrayLen( arrErrors )#"
  • step="1">
  •  
  • <li>
  • #arrErrors[ intError ]#
  • </li>
  •  
  • </cfloop>
  • </ul>
  •  
  • </cfif>
  •  
  •  
  • <form
  • action="#CGI.script_name#"
  • method="post"
  • enctype="multipart/form-data">
  •  
  • <!--- Submission flag. --->
  • <input type="hidden" name="submitted" value="1" />
  •  
  •  
  • <!---
  • Loop over the number of files we are going to
  • allow for the upload.
  • --->
  • <cfloop
  • index="intFileIndex"
  • from="1"
  • to="#REQUEST.FileCount#"
  • step="1">
  •  
  • <label for="file#intFileIndex#">
  • File #intFileIndex#:
  • </label>
  •  
  • <input
  • type="file"
  • name="file#intFileIndex#"
  • id="file#intFileIndex#"
  • />
  •  
  • <br />
  •  
  • </cfloop>
  •  
  •  
  • <input type="submit" value="Upload Files" />
  •  
  • </form>
  •  
  • </cfoutput>
  •  
  • </body>
  • </html>

Hope that helps. Please let me know if there are any follow up questions.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Other Searches  |  Print Page


You Might Also Be Interested In:




Reader Comments

Jan 8, 2008 at 11:06 AM // reply »
18 Comments

Just wondering why you add the step="1" attribute on all your cfloops?


Jan 8, 2008 at 11:11 AM // reply »
7,572 Comments

@Duncan,

There is no technical reason for this. I know that the loop increment defaults to 1. However, since many of my posts are meant to help people learn, I believe that it is best to spell everything out explicitly so there is no mystery and people can concentrate on the real task at hand.


Jan 8, 2008 at 11:02 PM // reply »
24 Comments

A nice addition for the beginners out there might be to use the result attribute and show'em how it can be used to interact with the db.

Great post!

Will


Feb 6, 2008 at 7:04 AM // reply »
1 Comments

thank you for this, very helpful


Sep 17, 2008 at 9:11 AM // reply »
2 Comments

Thanks Ben. Have been wondering how to do this for a little while now. Nice tutorial.


May 21, 2009 at 1:57 PM // reply »
1 Comments

Greetings -

I tried this code to upload multiple files, which worked great. However, while the files are uploaded to the right directory, I wanted to upload the name of files to my database.

Here is my databse code I added to this script:


INSERT INTO Uploads (user_ID, File_Name, Date_Published)
VALUES (#Form.ClientID#, 'file#intFileIndex#', #Now()#)

Issue 1 -
------------
The upload runs successfully, but I am not getting the right filenames populated on the database. Exp.: I have photo-1.jpg photo-2.jpg photo-3.jpg - On the "upload" directory, I get the right names: photo-1.jpg photo-2.jpg photo-3.jpg But on the database I get: file1 file2 file3 - instead I'd like to see the right files on the database as follows: photo-1.jpg photo-2.jpg photo-3.jpg

Issue 2 -
------------
2) I set the script to show 5 uploads:
as follows: <cfset REQUEST.FileCount = 5 />

But once they upload 5 photos, they can come back and upload more, I tested, I expected to see an error message that says "you have reached max limit which is 5 files/photos"

Issue 3 -
------------
Could we specify the file size. i.e. only up to 1MB per file can be uploaded

Please note, I am not too concerned about Issue 2 or 3, but the issue 1 is very critical.

Thank you so much for any input you can provide.

Regards,

Karim
MoroccoIT@gmail.com


May 22, 2009 at 3:53 AM // reply »
7 Comments

Karim:
Issue 1. Replace 'file#intFileIndex#' with '#cffile.ServerFile#' - this is the name that CFFile saves the file as on the server.

Issue 3. After the file uploads, you could use CFFile.fileSize to check if the size is GT 1Mb.


Aug 26, 2009 at 2:57 PM // reply »
31 Comments

Thanks for the tutorial Ben. The tips you gave made my day go quicker.


Sep 2, 2009 at 10:12 AM // reply »
7,572 Comments

@Brian,

Awesome my man; glad to help out.


Sep 29, 2009 at 10:14 AM // reply »
29 Comments

I discovered a problem with the CF file upload thing: file parameters with the same parameter name. They all upload to CF, you just can't retrieve them as easily.

<input type="file" name="file0"/>
<input type="file" name="file1"/>

This works fine.

<input type="file" name="file"/>
<input type="file" name="file"/>

This only retrieves the last file.

You have to do some undocumented CF to get it to work. Basically, retrieve the temporary CF files from disk yourself.
........................................

<cfsavecontent variable="fileNumberPattern">.*neotmp(\d+).tmp.*</cfsavecontent>
<cfset lastFileNumber=reReplace(form["file"], fileNumberPattern, "\1", "all")/>
<div>lastFileNumber: <cfdump var="#lastFileNumber#"/></div>

<cfset filesCount=listValueCountNoCase(form.fieldNames, "file")/>
<div>filesCount: <cfdump var="#filesCount#"/></div>

<cfloop index="fileIndex" from="0" to="#(filesCount - 1)#">
<cfset filePath=(getTempDirectory() & "neotmp" & (lastFileNumber - fileIndex) & ".tmp")/>
<div>filePath: <cfdump var="#filePath#"/></div>
<cffile action="readBinary" file="#filePath#" variable="m_file"/>
<div>file: <cfdump var="#m_file#"/></div>
<cffile action="delete" file="#filePath#"/>
</cfloop>
........................................


Sep 29, 2009 at 10:19 AM // reply »
7,572 Comments

@Alex,

Parsing temp files seems like a lot of work to do for the sake of naming files the same thing; I would suggest just having file inputs with different names. After all, that is really the intent of form fields - to provide unique sets of data. I am not sure that you should think of files as a single group.... although, that is how we can look at checkboxes, so I guess it's 6 of one, half a dozen of the other.


Sep 29, 2009 at 10:28 AM // reply »
29 Comments

@Ben

> naming files the same thing

This is my usual strategy for JavaScript add/delete/rearrange.

<fieldset class="item">
<input name="title" type="text"/>
<input name="description" type="text"/>
<input name="file" type="text"/>
</fieldset>

That way, you don't have to fuss with renumbering all the other items when you add/delete/rearrange.

When the page is submitted to the server (for real or via XHR), you assume title[1], description[1], and file[1] all go together. You can count how many items you have by counting the titles. In fact, it makes enumerating your values much easier because there's none of that "title#index#" complexity.

The next question would be how do you do all the labels and IDs. Well, these just need to be unique, not sequential. So make them unique.


Sep 29, 2009 at 10:33 AM // reply »
7,572 Comments

@Alex,

I am not sure how it is possible to separate the Title and Description fields in your form post? If either the Title or the Description contain a comma, doesn't that mess everything up?

Perhaps this is just a ColdFusion thing, but when you post a form with duplicate names, they get concatenated into a list. For example, checkbox with the same name (ids) would become:

ids: 1,2,7,9

... if 4 different checkboxes were checked.

Does your language actually break them out into separate array items?


Sep 29, 2009 at 10:56 AM // reply »
29 Comments

> concatenated into a list

Yes. That is why the form and url scopes are stupid.

Granted, the good thing about the form and url scopes are their ease of retrieving values. Assuming you only want the first value or there are no commas. Which is probably 99% of the time.

But they break even the simplest checkbox lists with same name, different values, values containing commas.

The query and body should have never been stuck in _just_ a simple struct with a list of values.
It made it nice to do url["foo"] and url["foo"][1], but only if there's no commas. It wouldn't have hurt to provided more advanced objects as well.

I use simple scripts to parse the query and body if needs be. You can get an value array (by index) or a struct (by name, array of values).


Oct 1, 2009 at 8:24 AM // reply »
7,572 Comments

@Alex,

Out of curiosity, where are you grabbing the form data from? I have looked in the GetHTTPRequestData(), but it looks like it doesn't store the data there when file uploads are involved. Where are you getting the raw data?


Oct 1, 2009 at 10:07 AM // reply »
29 Comments

@Ben

> where are you grabbing the form data from?

You're right, ColdFusion/Java intercepts the file data.

For multiple file uploads with the same form field name, you gotta do an end run around.

The form scope will give you the filename of the last uploaded file.

<cfsavecontent variable="fileNumberPattern">.*neotmp(\d+).tmp.*</cfsavecontent>
<cfset lastFileNumber=reReplace(form["file"], fileNumberPattern, "\1", "all")/>
<div>lastFileNumber: <cfdump var="#lastFileNumber#"/></div>

Use the form scope to find how many files were uploaded.

<cfset filesCount=listValueCountNoCase(form.fieldNames, "file")/>
<div>filesCount: <cfdump var="#filesCount#"/></div>

Loop through uploaded files (via count), grab data for each file from its temporary file, and delete its temporary file.

I changed this code to put file data in an array.

<cfset files=[]/>
<cfloop index="fileIndex" from="0" to="#(filesCount - 1)#">
<cfset filePath=(getTempDirectory() & "neotmp" & (lastFileNumber - fileIndex) & ".tmp")/>
<div>filePath: <cfdump var="#filePath#"/></div>
<cffile action="readBinary" file="#filePath#" variable="m_file"/>
<div>file: <cfdump var="#m_file#"/></div>
<cfset arrayAppend(m_file)/>
<cffile action="delete" file="#filePath#"/>
</cfloop>

I think you can use getHttpRequestData() to get the original filename and extension, I can't remember.


Dec 15, 2009 at 10:36 AM // reply »
5 Comments

Hi Ben.

After googling for about a week, I'm unable to locate a good demo like this one but also be able to add, update, and delete or CRUD images to a database (MySQL) and have CF8 crop the images too. Next, display the images related to the ID.

Thanks,

Barry


Dec 15, 2009 at 12:38 PM // reply »
29 Comments

@Barry

> crud images

MySQL blob data type
<http://dev.mysql.com/doc/refman/5.0/en/blob.html>

ColdFusion MySQL data source, enable BLOB
<http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_h-im_10.html>

> crop images

ColdFusion ImageCrop function
<http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_h-im_10.html>


Dec 15, 2009 at 12:50 PM // reply »
5 Comments

Thanks Alex.

But what I was looking for is a complete demo.

Barry.


Dec 15, 2009 at 6:37 PM // reply »
7,572 Comments

@Barry,

Perhaps you could combine this demo with the following one (which demonstrates BLOB data type usage):

http://www.bennadel.com/blog/1274-Ask-Ben-Streaming-Binary-Data-From-The-Database-BLOB-To-The-User-Using-ColdFusion.htm


Dec 18, 2009 at 12:28 PM // reply »
2 Comments

Hey Ben, I think Alex touched on a topic that I'm actually interested in digging into right now. My question is how do I determine the original filename? I've looked into GetHTTPRequestData() and form field itself and can't seem to find it.

What I'm doing is uploading the file and directly inserting it into blob database. It just irks me to have to cfupload the file to some tmp path, readbinary it before putting it into the database. especially now since i've figured out that CF has already written the file to the hard drive as the tmp file.

I figure that instead of cfuploading, I can just readbinary the tmp file and insert that data into the db directly. i just need the orig filename to finish off the puzzle.

thanks


Dec 18, 2009 at 4:22 PM // reply »
7,572 Comments

@Yuliang,

I don't know enough about how ColdFusion handles the binary form post to answer that. Personally, I think CFFILE is so easy, I would only avoid it if entire necessary.

Just in case you were not away (not making any assumptions), you can get the original file name from CFFILE after the upload is complete (CFFILE.clientFile).


Dec 18, 2009 at 4:29 PM // reply »
2 Comments

yea. it's not a huge deal to do the cffile. my only concern is that i'd have to keep a temp folder around and deleting the file after the blob insert. it's not as clean as I would like. but that's just me being anal about these things heh.


Dec 18, 2009 at 4:31 PM // reply »
7,572 Comments

@Yuliang,

Understandable - it certainly adds an extra step. But, I think you'll find it's an extra step that makes everything else (minus the additional step) easier.


Dec 18, 2009 at 7:13 PM // reply »
29 Comments

The only other issue with cffile is that you cannot retrieve the original mime type.

This becomes a problem when you go to serve the uploaded files. Especially out of a database. Serving a file requires a mime type, e.g., image/jpeg.

You need to add a file extension to mime type lookup. E.g., file.jpg > image/jpeg.

This isn't really the best solution. Clients may have more or more recent mime types than the server. Clients may have a different mime type for the same extension.

The cffile tag is really a poor implementation. Shortsighted, overly complicated, and incomplete.


Dec 22, 2009 at 8:54 AM // reply »
7,572 Comments

@Alex,

I am not sure I understand - you can get the mimetype from the uploaded file from either the CFFILE hidden scope or from the Result reference.

I don't think there's anyway you can call the CFFile tag "overly complicated". It really makes uploading super straightforward? How would you go about making it easier?


Dec 22, 2009 at 9:48 AM // reply »
29 Comments

@Ben

> mime type

Sorry. I should've clarified.

You can't get mime type for multiple file uploads with the same field name. Which is my whole gripe.

All your left with is a bunch of binary temp files on the server. You can get the file names from getHttpRequestData().

Why support _some_ of HTTP? It's not that large of a protocol. Just support the whole thing.

> cffile complicated

It sure is.

You have to know that a particular field is a file in order to process it correctly.

There's no way to do a for loop on the fields.


for each field in fields
switch field.type
case file
case text
end
end


Dec 22, 2009 at 10:01 AM // reply »
7,572 Comments

@Alex,

It sounds like there's no doubt that the functionality of CFFILE could be expanded. After all, if it's not letting you do what you want it to do, then clearly there is room for improvement.

However, I honestly believe your use case (multiple files with the same form field name) is an outlier case. I imagine that is why this problem has not been addressed by the server software.

Not trying to downplay your requirements - just that they might be more on the unique side of things.


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 21, 2010 at 3:59 PM
Exploring ColdFusion Component Runtime Class Properties And Serialization
@Elliott, according to Ben's experiment, serializeJSON() doesn't access the private data by default - it doesn't even access the getHair() method - so trying to clone a Girl.cfc via serializeJSON/des ... read »
Mar 21, 2010 at 3:49 PM
Ask Ben: Javascript String Replace Method
I'm confused a bit by what you are asking, but if had this sentence: The color, red, is in the style statement; style: red;. and wanted to remove all or change all of the commas, colons, and semi-c ... read »
Mar 21, 2010 at 3:13 PM
Ask Ben: Javascript String Replace Method
I am trying to make a java program to count the number of times that these punctuation marks occur in a body of text: , : ; . ! - ' " ? / \ I am using this piece to ferret out the commas: numcommas ... read »
Mar 21, 2010 at 11:13 AM
A New Wrist Pain
@chiropractor suwanee, Spoken like someone trying to sell something. Other than for minor, temporary relief from some back pain, chiropractic treatment is nothing but placebo effect and quackery. ... read »
Mar 21, 2010 at 6:32 AM
ColdFusion CFPOP - My First Look
Apologies... The field name in the db for C. is "BounceCode" It stores the code / message which is returned in the email. Sorry for the confusion. ... read »
Mar 21, 2010 at 6:29 AM
ColdFusion CFPOP - My First Look
@Jose Galdamez, Hi Ben and Jose 1st of all.. big thanks to Jose for his Skype chat a few weeks back. Your time was much appreciated. I have come up with a rather unelegant solution to my problem a ... read »
Mar 21, 2010 at 3:42 AM
A New Wrist Pain
Chiropractic treatment is one of the best methods for treating numerous health problems naturally. After years of experience being a chiropractor, I have found that it is a powerful way to solve many ... read »
Mar 20, 2010 at 12:07 PM
Drawing On The iPhone Canvas With jQuery And ColdFusion
Simply awesome. Saved my day. ... read »