最近做个上传整个文件夹的功能,这个东西看似简单,但以前没接触过,费了我好大力气。最终勉勉强强可以实现了。
我看这个功能采用一个activex控件会省事很多。
步骤:点击浏览,选择文件夹---然后上传
选择文件夹我采用自制的activex控件,这步在前几篇文章中提到过了。
选择了文件夹后,返回了所有文件路径和空文件夹路径,我需要采用自动生成input表单来传递上传文件信息,空文件夹采用后台创建而不是上传了。
表单自动生成代码:<div id=MyZone></div>
WshShell=new ActiveXObject("WScript.Shell");
createinputs();
//自动创建表单
function createinputs()
{
var i=1;
var formhtml="";
for(i;i<=filecnt+1;i++)
{
//创建表单代码
formhtml+="<input type='file' size='1' name='uploadfile"+i+"'><br>";//创建表单代码
}
document.all.MyZone.innerHTML=formhtml;
s = setInterval("setfile(start)",10);
}
//表单赋值代码(模拟复制粘贴)
function setfile(j)
{
if (start==(filecnt+1))
{
window.clipboardData.setData('text',"end");
eval("document.all.uploadfile"+j).focus();
WshShell.sendKeys("^a");
WshShell.sendKeys("^v");
window.clipboardData.setData('text',document.all.TextBox1.Text);
eval("document.all.TextBox1").focus();
WshShell.sendKeys("^a");
WshShell.sendKeys("^v");
}
else
{
//alert(document.all.backserverstr.value);
var filearr = document.all.backserverstr.value.split("*");
//alert(start);
//alert(filearr[j-1]);
window.clipboardData.setData('text',filearr[j-1]);
eval("document.all.uploadfile"+j).focus();
//WshShell.sendKeys("中国");//filearr[j-1]);
WshShell.sendKeys("^a");//Ctrl + A 操作
WshShell.sendKeys("^v");//Ctrl + V 操作(sendKeys对于中文赋值操作显得无力,所以只能模拟键盘操作)
}
start++;
if (start==(filecnt+1+1)){clearInterval(s);document.all.submitbtn.click();start=1;}
}
表单生成后要给其赋值,因为WshShell=new ActiveXObject("WScript.Shell");对象不支持中文路径,所以采用模拟键盘输入的方法,也就是复制粘贴的方法,呵呵,挺搞笑的,这是网上一位老兄想出来。其实可以下载一个dll来支持中文,麻烦,又要做activex,我可不想再整了。
还有一个问题是,这些表单显示在页面上不太美观,要模拟键盘赋值所以不能设置为不可见,我试图找一种遮罩的东西,但没有找到,希望哪位仁兄给点建议,具体点。后来瞎整,把<div id=MyZone></div>放到一个table里面,奇迹般的不显示了,我也不深究了,因为为这个功能我花了太多时间了,进度紧得很,时间不允许了。
还有一个问题是,要将form中属性增加encType="multipart/form-data" ,这样才能提交多个文件。
大文件问题,asp.net单个文件上传的大小默认是4m,要修改web.config中的<httpRuntime executionTimeout="500" maxRequestLength="2000000" />。
加了这个还是不能传太大的(没有验证),可以修改iis上传后处理的方式,即实现IHttpModule接口。
/// <summary>
/// iis接受上传分块文件
/// 此类继承IHttpModule接口,改变iis接受文件的方式,从而可以接受大文件上传
/// 使用此类只需要修改【1】web.config:
/// <system.web>
/// <!--
/// 大文件上传处理:分块上传
/// -->
/// <httpModules>
/// <add name="HttpUploadModule" type="WebGeoDBSys.HttpUploadModule, WebGeoDBSys"/>
/// </httpModules>
/// <!-->
/// 设置文件上传的大小限制,默认是4M
/// -->
/// <httpRuntime executionTimeout="500" maxRequestLength="2000000" />
/// </summary>
/// 【2】在页面的html中form中修改属性enctype="multipart/form-data"
///
public class HttpUploadModule : IHttpModule
{
public HttpUploadModule()
{
}
public void Init(HttpApplication application)
{
//订阅事件
application.BeginRequest += new EventHandler(this.Application_BeginRequest);
}
public void Dispose()
{
}
private void Application_BeginRequest(Object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
HttpWorkerRequest request = GetWorkerRequest(app.Context);
Encoding encoding = app.Context.Request.ContentEncoding;
int bytesRead = 0; // 已读数据大小
int read; // 当前读取的块的大小
int count = 8192; // 分块大小
byte[] buffer; // 保存所有上传的数据
if (request != null)
{
// 返回 HTTP 请求正文已被读取的部分。
byte[] tempBuff = request.GetPreloadedEntityBody(); //要上传的文件
// 如果是附件上传
if (tempBuff != null && IsUploadRequest(app.Request)) //判断是不是附件上传
{
// 获取上传大小
//
long length = long.Parse(request.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentLength));
//文件不能太大,只能在这个范围左右
if (length < 250000000)
{
buffer = new byte[length];
count = tempBuff.Length; // 分块大小
// 将已上传数据复制过去
//
Buffer.BlockCopy(tempBuff, //源数据
0, //从0开始读
buffer, //目标容器
bytesRead, //指定存储的开始位置
count); //要复制的字节数。
// 开始记录已上传大小
bytesRead = tempBuff.Length;
// 循环分块读取,直到所有数据读取结束
while (request.IsClientConnected() && !request.IsEntireEntityBodyIsPreloaded() && bytesRead < length)
{
// 如果最后一块大小小于分块大小,则重新分块
if (bytesRead + count > length)
{
count = (int)(length - bytesRead);
tempBuff = new byte[count];
}
// 分块读取
read = request.ReadEntityBody(tempBuff, count);
// 复制已读数据块
Buffer.BlockCopy(tempBuff, 0, buffer, bytesRead, read);
// 记录已上传大小
bytesRead += read;
}
if (request.IsClientConnected() &&
!request.IsEntireEntityBodyIsPreloaded())
{
// 传入已上传完的数据
InjectTextParts(request, buffer);
}
}
}
}
}
HttpWorkerRequest GetWorkerRequest(HttpContext context)
{
IServiceProvider provider = (IServiceProvider)HttpContext.Current;
return (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));
}
///<summary>
/// 传入已上传完的数据
///</summary>
///<param name="request"></param>
///<param name="textParts"></param>
void InjectTextParts(HttpWorkerRequest request, byte[] textParts)
{
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
Type type = request.GetType();
while ((type != null) && (type.FullName != "System.Web.Hosting.ISAPIWorkerRequest"))
{
type = type.BaseType;
}
if (type != null)
{
type.GetField("_contentAvailLength", bindingFlags).SetValue(request, textParts.Length);
type.GetField("_contentTotalLength", bindingFlags).SetValue(request, textParts.Length);
type.GetField("_preloadedContent", bindingFlags).SetValue(request, textParts);
type.GetField("_preloadedContentRead", bindingFlags).SetValue(request, true);
}
}
private static bool StringStartsWithAnotherIgnoreCase(string s1, string s2)
{
return (string.Compare(s1, 0, s2, 0, s2.Length, true, System.Globalization.CultureInfo.InvariantCulture) == 0);
}
///<summary>
/// 是否为附件上传
/// 判断的根据是ContentType中有无multipart/form-data
///</summary>
///<param name="request"></param>
///<returns></returns>
bool IsUploadRequest(HttpRequest request)
{
return StringStartsWithAnotherIgnoreCase(request.ContentType, "multipart/form-data");
}
}
没想到这么麻烦。
以前采用的ftp路径直接访问,然后拷贝文件到该目录的方法虽然不太友好,但挺实用的。
我认为做个ftp 上传的activex组件应该会更加方便点。
待以后再去修改吧。