Hi John:
Yes, of course the <input type=file> doesn't upload till post time -- but there are different ways
to upload: including using the ADOStream ActiveX objects with an IHttpHandler listening to it.
This way allows for multiple uploading of files and because it is asynch. you don't have to wait
till it ends before you can continue typing...
And even if I were using a plain input type=file, we don't always know how many files to upload --
sometimes one, sometime 10 -- so this causes 2 problems:
a) unknown number of Type=File buttons to generate for a form.
b) User has to wait a long time at the end to ensure mail went out successfully....
There are more 'desktop feeling' ways of skinning that cat -- and they involve uploading to a dir
before the email has been finished: In case you have not looked at this type of asynch uploading
before i attach the following two pieces:
a) IHttpHandler that i am still fidlling with so it could have bugs but the basics are there
b) the JS to inject to a page to prepare the Adostream object for binary upload via an xml doc
Cheers,
Sky
//IHTTPHANDLER
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.IO;
namespace XAct.Web.HttpHandlers {
namespace Controls{
/// <summary>
/// Visible Listener Control used to upload files to.
/// </summary>
/// <remarks>You should NOT use this control -- use a proper IHttpHandler
/// instead -- such as HttpHandler_FileUploader...
///
/// It is provided only for people who prefer working in a visual IDE --
/// but it forces you to have a whole aspx page dedicated to hosting it,
/// whereas the IHttpHandler method does not, and therefore is much cleaner.
///
/// Also, both the Control and IHttpHandler share common code, so there is
/// no reason not to go directly with IHttpHandler...
/// </remarks>
/// <summary>
/// IHttpHandler to handle uploads via HttpFile control, or via ADOStream ActiveX methods.
/// </summary>
public class FileUploader : System.Web.IHttpHandler {
//================================================
//FIELDS
//================================================
private const string _PathFilter = "XAct.Uploader.aspx";
//================================================
//PROPERTIES
//================================================
/// <summary>Override of IHttpHandler.IsReusable to set it to true always.</summary>
public bool IsReusable {get { return true; }}//Must be true!
//================================================
//PUBLIC METHODS:
//================================================
/// <summary>
/// Called by Net framework to process request.
/// </summary>
/// <param name="hc"></param>
public void ProcessRequest(HttpContext hc) {
//NB: this will never be called if Install() hasn't been called before:
bool tSuccess = _FileUploader.Process(hc,string.Empty);
}
/// <summary>
/// Static function that modifies web.config xml file as needed to install this IHttpHandler.
/// </summary>
/// <returns>True if successful.</returns>
public static bool Install(bool qThrowExceptionIfUnsuccessful){
System.Type tType = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
return XAct.Web.Controls.Tools.InstallHandler(tType,_PathFilter,true);
}
/// <summary>
/// Shared Class used by both the FileUploader Control and IHttpHandler.
/// </summary>
public class _FileUploader{
/// <summary>
/// Processes request and saves uploaded file.
/// </summary>
/// <param name="hc">Current HttpContext.</param>
/// <param name="qUploadDir">Directory where Uploaded files are to be saved.</param>
/// <returns>True if upload and save was successful.</returns>
public static bool Process(System.Web.HttpContext hc, string qUploadDir){
//Clean up any old files in the upload directory.
_CleanUploadDir(qUploadDir);
if (hc.Request.Params["mode"]=="BINARY"){
return _Process_Upload_ADOStream(hc,qUploadDir);
}
else{
return _Process_Upload_Normal(hc,qUploadDir);
}
}
/// <summary>
/// Private Method. Used when Client sends file via Form and HtmlFile controls. (Traditional)
/// </summary>
/// <param name="hc">Current HttpContext</param>
/// <param name="qUploadDir">Directory where Uploaded files are to be saved.</param>
/// <returns>True if upload and save was successful.</returns>
private static bool _Process_Upload_Normal(System.Web.HttpContext hc, string qUploadDir){
bool tResult;
string tDir = string.Empty;
if (qUploadDir == string.Empty){
qUploadDir = System.Configuration.ConfigurationSettings.AppSettings["XActUploadDirectory"];
}
if ((qUploadDir == null)||(qUploadDir == string.Empty)){
tResult = false;
//oReportStatusMsg.InnerText = "Error: No Upload directory specified.";
}
try {
tDir = hc.Server.MapPath(qUploadDir);
System.IO.Directory.CreateDirectory(tDir);
}catch{
tResult = false;
//oReportStatusMsg.InnerText = "Could not gain Server.MapPath to upload directory specified: " +
qUploadDir;
}
//Loop through each file that has been posted:
foreach (System.Web.HttpPostedFile oFile in hc.Request.Files){
// Allocate a byte buffer for reading of the file
byte[] myData = new byte[oFile.ContentLength];
// Read uploaded file from the Stream
oFile.InputStream.Read(myData, 0, myData.Length);
//Make the save path:
string tPath = tDir +"/" + System.IO.Path.GetFileName(oFile.FileName);
// Create a file
FileStream newFile = new FileStream(tPath, FileMode.Create);
// Write data to the file
newFile.Write(myData, 0, myData.Length);
// Close file
newFile.Close();
}
tResult = true;
return tResult;
}//Method:End
/// <summary>
/// Private Method. Used when Client sends file via ADOStream/BinaryXML technique.
/// </summary>
/// <param name="hc">Current HttpContext</param>
/// <param name="qUploadDir">Directory where Uploaded files are to be saved.</param>
/// <returns>True if upload and save was successful.</returns>
/// <remarks>
/// This is my preferred technique as it doesn't
/// require a Post/Refresh on each file upload.
/// But it does require JS code to be injected into the Client's page
/// in order to create the XML and send it to this IHttpHandler...
/// </remarks>
private static bool _Process_Upload_ADOStream(System.Web.HttpContext hc, string qUploadDir){
//Get the incoming stream:
System.IO.Stream tStreamIn = hc.Request.InputStream;
//Just in case it has been switched at some point?
hc.Response.Expires = 0;
hc.Response.ContentType = "text/xml";
//Create a Doc to read the XML stream that is being sent:
System.Xml.XmlDocument oXDocIn = new XmlDocument();
//Create a Doc to write the answer to:
System.Xml.XmlDocument oXMLReport = new XmlDocument();
//Structure we want to send back is:
//<report>
// <status>True</status>
// <status_msg>Doing Fine...</status_msg>
//</report>
System.Xml.XmlNode oReportRoot=
oXMLReport.CreateElement("report");oXMLReport.AppendChild(oReportRoot);
System.Xml.XmlNode oReportStatus=
oXMLReport.CreateElement("status");oReportRoot.AppendChild(oReportStatus);
System.Xml.XmlNode oReportStatusMsg=
oXMLReport.CreateElement("status_msg");oReportRoot.AppendChild(oReportStatusMsg);
//Load the incoming stream text all in one go into a new doc to parse it:
oXDocIn.LoadXml(new StreamReader(tStreamIn).ReadToEnd());
//Start counters, etc.
int tCountTotal=0;
int tCountSuccessful=0;
int tBufferTransferred=0;
bool tResult =true;
string tDir=string.Empty;
string tStatusMsg = string.Empty;
//Get the Upload directory from the config file if it was not given:
if (!_GetUploadDirectory(ref qUploadDir)){
tResult = false;
oReportStatusMsg.InnerText = "- Error: No Upload directory specified.";
//We normally would get out here since we are obviously not doing
//to well...but we want this error message to go out as an xml doc.
//so we need to finish up right to the end, skipping what we would
//have done if tResult were still True...
}
if (tResult){
try {
tDir = hc.Server.MapPath(qUploadDir);
}
catch {
//There was an error.
//The qUploadDir was not a relative path
//and therefore could not be converted by MapPath...
tResult = false;
oReportStatusMsg.InnerText = "- Error: Could not Server.MapPath the upload directory specified: " +
qUploadDir;
}
}
if (tResult){
try {
if (!System.IO.Directory.Exists(tDir)){
//In case the directory doesn't exist, let's see
//if it can be created.
//Might fail if App hasn't been given the right to create
//new subdirectories by itself...
System.IO.Directory.CreateDirectory(tDir);
}
}catch{
//There was an error.
//Program probably doesn't have enough rights to create sub directories...
tResult = false;
oReportStatusMsg.InnerText = "- Error: Could not gain Server.MapPath to upload directory specified:
" + qUploadDir;
}
}
//We are now ready to proceed and process the incoming xml
//file.
//We are expecting an incoming xml file with the following format:
//<root>
// <file>
// <filename>MyFile.dat</filename>
// <charbuff>....</charbuffer>
// </file>
// <file>
// <filename>MyFile.dat</filename>
// <charbuff>....</charbuffer>
// </file>
// ...etc...
//</root>
if (tResult==true){
//Are there any FILE subnodes?
//if not - get out.
tCountTotal = oXDocIn.DocumentElement.ChildNodes.Count;
tBufferTransferred = 0;
if (tCountTotal==0){
tResult = false;
oReportStatusMsg.InnerText = "- Error: No Child Nodes (file names/info) found.";
}
}
if (tResult){
//Loop through each FILE node:
foreach (System.Xml.XmlNode oInfo in oXDocIn.DocumentElement.ChildNodes){
tStatusMsg = string.Empty;
//Each node should have atleast 2 subnodes:
//1) filename,
//2) charbuffer
if (oInfo.ChildNodes.Count >1){
//Get first node (FILENAME):
string tFileName = System.IO.Path.GetFileName(oInfo.ChildNodes[0].InnerText);
//Get second node (base64 BYTEBUFFER):
System.Byte[] tBuffer= System.Convert.FromBase64String(oInfo.ChildNodes[1].InnerText);
//Now that we have both parts:
//make an outgoing stream, write to it and save it to the directory
//then close the outgoing file stream:
System.IO.FileStream tStreamOut=null;
try {
string tPath = tDir +"/" + tFileName;
tStreamOut = System.IO.File.OpenWrite(tPath);
tStreamOut.Write(tBuffer,0,tBuffer.Length);
tCountSuccessful +=1;
tBufferTransferred +=tBuffer.Length;
}catch (System.Exception E){
tResult = false;
oReportStatusMsg.InnerText = "Error: " + E.Message;
if (tStreamOut != null){tStreamOut.Close();}
break;
}
finally{
if (tStreamOut != null){tStreamOut.Close();}
}
}
}//End Foreach
}
//Report string is splittable by spaces:
if (tResult){
oReportStatusMsg.InnerText = "+ Success: " + tCountTotal + " " + "Uploaded." + " (" +
tBufferTransferred + "bytes.)";
}
oReportStatus.InnerText = (tResult)?"1":"0";
//Write the xml of the whole outgoing xml doc to the outgoing stream:
hc.Response.Write(oXMLReport.OuterXml);
//Done.
return tResult;
}//Method:End
private static bool _GetUploadDirectory(ref string qUploadDir){
if (qUploadDir == string.Empty){
qUploadDir = System.Configuration.ConfigurationSettings.AppSettings["XActUploadDirectory"];
}
if ((qUploadDir == null)||(qUploadDir == string.Empty)){
return false;
}
return true;
}
/// <summary>
/// Deletes all files in a directory that are older than 12 hours.
/// </summary>
/// <param name="qUploadDir"></param>
/// <returns>-1 if Error, otherwise count of files actually deleted.</returns>
/// <remarks>
/// It goes without saying that this method should be used with EXTREME care!
/// Because it is so dangerous, it comes with two criteria built into it:
/// a) the directory name given must include the word 'upload' somewhere in in,
/// b) If the directory has files in it that are older than 7 days, then it's most
/// probably NOT a current/working upload directory, so nothing is deleted.
/// This might seem like a pain -- but the option of deleting your whole base virtual directory
/// by accident is a lot worse...
/// </remarks>
private static int _CleanUploadDir(string qUploadDir){
int tResult =-1;
if (!_GetUploadDirectory(ref qUploadDir)){
return tResult;
}
if (qUploadDir.ToUpper().IndexOf("UPLOAD")<-1){
return tResult;
}
System.DateTime tCheck = System.DateTime.Now.Subtract(new TimeSpan(-12,0,0));
System.DateTime tCheckWeekAgo = System.DateTime.Now.Subtract(new TimeSpan(-7,0,0,0));
System.DateTime tLastWriteTime = System.IO.Directory.GetLastWriteTime(qUploadDir);
if (tLastWriteTime < tCheckWeekAgo){
//Directory remained untouched for over a week.
//Not ok to continue.
return tResult;
}
//Time to look:
ArrayList tDeleteMe = new ArrayList();
string[] tFiles = System.IO.Directory.GetFiles(qUploadDir);
foreach (string tFileName in tFiles){
tLastWriteTime = System.IO.Directory.GetLastWriteTime(tFileName);
if (tLastWriteTime < tCheckWeekAgo){
//File has been there over a week.
//Alarm!
//We're not in a valid directory!!!
return tResult;
}
if (tLastWriteTime < tCheck){
//File has been there over [n] hours. Ok to delete at end:
tDeleteMe.Add(tFileName);
}
}
//Ok. Sounds like it's ok to delete after all:
//Set the counter:
tResult = 0;
foreach (string tFileName in tDeleteMe){
System.IO.File.Delete(tFileName);
tResult +=1;
}
//Get out:
return tResult;
}//Method:End
}
}
}//Namespace:End
JAVASCRIPT TO INJECT INTO PAGE:
function FileUploader(){
this._Name = "FileUploader";
this.LocalPath;
this.LocalFileName;
this.LastFilePath;
this.LastStatus;
this.LastStatusMsg;
this.Img;
this.ServerPath = "./XAct/Upload/";
}
FileUploader.prototype.Send=function(qFileName,qImgObject){
var oC = this;
oC.LocalPath = qFileName;
oC.LocalFileName = oC.LocalPath.substr(oC.LocalPath.lastIndexOf("\\")+1);
oC.Img = qImgObject;
//Allows for asynch going...nice
window.setTimeout(function(){oC._SendII()},100);
}
FileUploader.prototype._SendII=function(){
var oC = this;
var oDoc,oRoot,oFileInfo,oFileName, oFileBuffer,tStream,xmlhttp;
// create ADO-stream Object
try {
tStream = new ActiveXObject("ADODB.Stream");
oDoc = new ActiveXObject("MSXML.DOMDocument");
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e){
alert ("Error uploading File:\n" + e.message);
return;
}
oRoot = oDoc.createElement("root");oDoc.appendChild(oRoot);
// specify namespaces datatypes
oDoc.documentElement.setAttribute("xmlns:dt", "urn:schemas-microsoft-com:datatypes");
oFileInfo = oDoc.createElement("file");oRoot.appendChild(oFileInfo);
oFileName = oDoc.createElement("name");oFileInfo.appendChild(oFileName);
oFileBuffer = oDoc.createElement("buffer");oFileInfo.appendChild(oFileBuffer);
oFileBuffer.dataType = "bin.base64";
//Get the LocalPath:
oFileName.text = oC.LocalPath;
//Get the File contents:
try {
tStream.Type = 1; // 1=adTypeBinary
tStream.Open();
tStream.LoadFromFile(oC.LocalPath);
oFileBuffer.nodeTypedValue = tStream.Read(-1); // -1=adReadAll
tStream.Close();
}catch (e){
alert ("Error encoding file: " + "\n" + e.message);
return;
}
// send XML documento to Web server
xmlhttp.open("POST","./XAct.Uploader.aspx",false);
xmlhttp.send(oDoc);
oC.LastFilePath = oC.LocalPath;
oC.LocalPath = "";
oReturned = new ActiveXObject("MSXML.DOMDocument");
oReturned.loadXML(xmlhttp.responseText);
var oRoot = oReturned.documentElement;
if (!oRoot){
oC.LastStatus = 0;
oC.LastStatusMsg = "Incorrect XML";
}else{
oC.LastStatus = oRoot.childNodes[0].text;
oC.LastStatusMsg = oRoot.childNodes[1].text;
}
if (!oC.LastStatus){
alert (oC.LastStatusMsg);
}
if (oC.Img){
oC.Img.src = oC.ServerPath + oC.LocalFileName;
}
}
/*
function DoSend(qFileName,qCanvas){
var tObj = document.all[qFileName];
if (tObj){qFileName = tObj.value;}
var tObj = document.all[qCanvas];
if (tObj){qCanvas =tObj}
if (!qCanvas){qCanvas = document.body;}
oImg = document.createElement("IMG");
qCanvas.appendChild(oImg);
FU.Send(qFileName,oImg);
}
*/