.Net Framework 2.0給我們提供了新的文件上傳工具FileUpload,針對多文件上傳的需求該控件表現仍不能讓人滿意,本文介紹了一個提供針對該需求的多文件上傳的控件MultiFileUpload。

我的解決方案如下:在客戶端使用JavaScript控制上傳單元的數量,點擊"Add upload”後觸發Uploading事件,程序在服務器端通過Files屬性(Request.Files)獲取用戶端所有上傳的文件的HttpPostedFile集合。該控件具備如下特性:
- 允許一次上傳多個附件
- 允許用戶添加/刪除上傳單元的個數
- 允許用戶設置上傳文件的最大數目
- 檢查上傳文件的重復性
主要實現途徑
上傳單元數目控制的實現
該部分主要借助JavaScript中的parentElement, createElement, insertAdjacentElement, appendChild, removeChild等屬性和方法實現,添加的一個文件項的代碼單元為:

<div>

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

<input type=”button” value=”Delete” onclick=”removeItem()”/>

</div>
把元素放在div裡的目的是為了方便刪除操作(只需要remove掉div就可以),以下是幾個主要的JS函數介紹:
- get_container() 獲取用於添加元素的容器的引用
- get_addButton() 獲取"Add Upload"按鈕的應用
- get_delButton() 創建"delete"按鈕,聲明該按鈕的click事件(刪除上傳單元)處理程序
- get_fileUpload() 創建input type="file",聲明其change事件(驗證文件的重復性)處理程序
- addItem() 添加一個上傳單元
- removeItem() 移除一個上傳單元
上傳文件重復性檢查的實現
checkFileExist() 文件的重復性檢查,在每次input type="file"的值發生改變時觸發。檢測方式為遍歷容器中所有的input type="file",當檢測到重復文件存在時,會給出重復性提示,同時將相應的input type=file清空.這裡因為intput的value屬性是只讀,為了清除其內部內容,采用的方案為先移除該元素,然後在相同位置添加該元素


function checkFileExist()...{var filename=event.srcElement.value; var duplicate = 0;var elems = get_container().getElementsByTagName("input");for(var i=0;i<elems.length; i++)...{if(elems[i].type == "file" && elems[i].value == filename)...{duplicate++;if(duplicate>1)...{alert("不能重復選擇文件。"); var offset = event.srcElement.nextSibling; var parent = event.srcElement.parentElement;parent.removeChild(event.srcElement);offset.insertAdjacentElement("beforeBegin", get_fileUpload()); return; }} }}
自定義控件的設計
該控件提供如下公共屬性和事件:
- Files屬性: 獲取客戶端上傳文件的集合
- MaxFilesCount屬性: 允許用戶添加的上傳單元數目的上限
- Uploading事件: 當用戶向服務器發送上傳文件時觸發
控件主要重寫了以下方法:
- CreateChildControls, 控件使用Button類型的m_btnUpload來承載用戶上傳文件的Uploading事件,在該方法中將m_btnUpload添加到控件的Control集合
- RenderContent,將控件工作需要的Html元素和JScript代碼發送到客戶端
為了方便維護和使用,JScript代碼被封裝到Resource文件中。
使用舉例
ASPX頁面


<%...@ Register Namespace="MultiFileUpload" Assembly="MultiFileUpload" TagPrefix="ASP" %>

…

<ASP:MultiFileUpload ID="mfu1" runat="server" OnUploading="mfu1_Uploading" MaxFilesCount="3" />

…

.CS文件

protected void mfu1_Uploading(object sender, EventArgs e)

...{

for(int i=0;i<mfu1.Files.Count;i++)


...{

Label1.Text += Request.Files[i].FileName + "<br/>";

}

}
代碼
MultiFileUpload.CS

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Text;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;


namespace MultiFileUpload


...{

[DefaultProperty("Text")]

[ToolboxData("<{0}:MultiFileUpload runat=server></{0}:MultiFileUpload>")]

public class MultiFileUpload : WebControl, INamingContainer


...{

private Button m_btnUpload = null;


protected override void OnInit(EventArgs e)


...{

m_btnUpload = new Button();

m_btnUpload.Text = "Upload";

m_btnUpload.Click += new EventHandler(m_btnUpload_Click);

base.OnInit(e);

}



/**//// <summary>

/// Occures when user clicks the upload button to upload the files.

/// </summary>

public event EventHandler Uploading;


protected override void CreateChildControls()


...{

Controls.Add(m_btnUpload);

Page.Form.Enctype = "multipart/form-data";

base.CreateChildControls();

}


void m_btnUpload_Click(object sender, EventArgs e)


...{

if (null != Uploading)


...{

Uploading(this, e);

}

}



"Properties"#region "PropertIEs"



/**//// <summary>

/// Gets the file collection uploaded by the clIEnt.

/// </summary>

public HttpFileCollection Files


...{

get


...{

return (HttpContext.Current.Request.Files == null) ? null : HttpContext.Current.Request.Files;

}

}



/**//// <summary>

/// Gets or sets the max number of files uploaded by the clIEnt at one time.

/// </summary>

[Category("Appearance")]

public int MaxFilesCount


...{

get


...{

return (null == ViewState["MaxFiles"]) ? 5 : (int)VIEwState["MaxFiles"];

}

set


...{

VIEwState["MaxFiles"] = value;

}

}

#endregion



"Render UI elements"#region "Render UI elements"


protected override void RenderContents(HtmlTextWriter output)


...{

output.WriteBeginTag("div");

output.WriteAttribute("id", "fuPanel");

output.Write(''>'');

output.RenderBeginTag(HtmlTextWriterTag.Div);

WriteInputFile(output);

WriteDeleteButton(output);

output.RenderEndTag();

output.WriteEndTag("div");

output.WriteBeginTag("script");

output.WriteAttribute("type", "text/Javascript");

output.Write(''>'');

output.Write(GetJavaScripts());

output.WriteEndTag("script");

// Render the post back button.

if (null != m_btnUpload)


...{

m_btnUpload.RenderControl(output);

}

}


private void WriteInputFile(HtmlTextWriter output)


...{

output.WriteBeginTag("input");

output.WriteAttribute("type", "file");

output.WriteAttribute("name", "file");

output.WriteAttribute("onchange", "checkFileExist()");

output.Write(''>'');

output.WriteEndTag("input");

}


private void WriteDeleteButton(HtmlTextWriter output)


...{

output.WriteBeginTag("input");

output.WriteAttribute("type", "button");

output.WriteAttribute("id", "add");

output.WriteAttribute("value", "Add upload");

output.WriteAttribute("onclick", "addItem()");

output.Write(''>'');

output.WriteEndTag("input");

}


private string GetJavaScripts()


...{

// Replace the max count of files.

return Resource.JScript.Replace("{$MAX_FILES_COUNT}", MaxFilesCount.ToString());

}


#endregion

}

}
JScript.JS


var maxFiles = ...{$MAX_FILES_COUNT};


function get_container()...{return document.getElementById("fuPanel"); }


function get_addButton()...{return document.getElementById("add");}


function get_delButton()...{var delButton = document.createElement("<input>"); delButton.type = "button";delButton.name = "delete";delButton.value = "Delete";delButton.onclick = removeItem;return delButton;}


function get_fileUpload()...{var fileUpload = document.createElement("<input>");fileUpload.type = "file";fileUpload.name = "file"; fileUpload.onchange=checkFileExist; return fileUpload;}


function addItem()...{if(get_container().getElementsByTagName("div").length>=maxFiles)...{alert("超出最大文件限制.");return;}var div = document.createElement("<div>");div.appendChild(get_fileUpload());div.appendChild(get_delButton());get_container().firstChild.insertAdjacentElement("beforeBegin", div);}


function removeItem()...{get_container().removeChild(event.srcElement.parentElement);}


function checkFileExist()...{var filename=event.srcElement.value; var duplicate = 0;var elems = get_container().getElementsByTagName("input");for(var i=0;i<elems.length; i++)...{if(elems[i].type == "file" && elems[i].value == filename)...{duplicate++;if(duplicate>1)...{alert("不能重復選擇文件。"); var offset = event.srcElement.nextSibling; var parent = event.srcElement.parentElement;parent.removeChild(event.srcElement);offset.insertAdjacentElement("beforeBegin", get_fileUpload()); return; }} }}