.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; }} }}