Wednesday, October 13, 2004

 

Some notes on file upload - 1

1.
作者:chnking
出处:http://www.csdn.net/Develop/Read_Article.asp?Id=20849

文件上传

一. 在Form中一定要将encType设为"multipart/form-data":
form id="WebForm3" method="post" encType="multipart/form-data" runat="server" >

二. 判断是否有文件上传了:
当用户没有选择任何要上传的文件,即HtmlInputFile控件中的文本框为空时点击了上传按钮后,在服务端得到的File1.PostedFile对象不是null,而是有对象的,所以不能用(File1.PostedFile == null)来判断是否上传了文件,用(File1.PostedFile.ContentLength != 0)来判断比较好

三. 判断上传文件MIMIE类型:
文件上传后可以用File1.PostedFile.ContentType来读取这个文件的MIMIE类型,这个MIMIE类型是系统通过上传文件的后缀名来获得的。

四. 保存上传的文件:

1. 文件可以通过File1.PostedFile.SaveAs(path) //path是服务器上的物理路径,来保存文件。

[code]
if(File1.PostedFile.ContentLength != 0)

{

StringBuilder myStr = new StringBuilder();

myStr.Append("文件名称:" + File1.PostedFile.FileName);

myStr.Append("
");

myStr.Append("文件类型:" + File1.PostedFile.ContentType);

myStr.Append("
");

myStr.Append("文件长度:" + File1.PostedFile.ContentLength.ToString());

myStr.Append("
");

string path = Server.MapPath("./"); //当前路径

string fileName = File1.PostedFile.FileName.Substring(File1.PostedFile.FileName.LastIndexOf('\\')+1);

path += fileName;

if(File.Exists(path) == true)

{

Label1.Text = "服务器上已经有了你正在上传的文件:" + fileName;

return;

}

File1.PostedFile.SaveAs(path);

myStr.Append("保存完毕!");

myStr.Append("
");

Label1.Text = myStr.ToString();

}

else

{

Label1.Text = "你没有选择要上载的文件或者上传的文件长度为0!";

}
[/code]

2. 文件也可以通过二进制的读取后存放到数据库的二进制的字段中:
byte[] fileCont = new byte[File1.PostedFile.ContentLength];
File1.PostedFile.InputStream.Read(fileCont,0, File1.PostedFile.ContentLength);
然后将此字节数组fileCont赋给数据库的二进制字段的参数,写到数据库中。

文件下载

一. 服务端通过Response输出相应的HTTP Response Headers信息,和要下载的文件的数据来把文件发送到客户端,HTTP Response Headers表现在html文件中是下面的形式:
meta http-equiv="Content-Type" content="text/htm ">
http-equiv表示是Headers的名称,content表示这个Headers的值

二. 首先,要输出文件的MIME类型:
Page.Response.AddHeader( "Content-Type", “MIME类型” );

三. 其次,要输出下载的文件的打开位置和文件名:
Page.Response.AddHeader("Content-Disposition", "attachment;filename=" + FileName );
content-disposition 的 HTTP response header 允许指定文档表示的信息。使用这种 header ,你就可以将文档指定成单独打开(而不是在浏览器中打开),还可以根据用户的操作来显示。如果用户要保存文档,你还可以为该文档建议一个文件名。这个建议名称会出现在 Save As 对话框的“文件名”栏中。
打开位置:
attachment ―― 表示作为附件发送到客户端,客户端将单独打开此文件。
inline ―― 表示将在浏览器中打开这个文件。
文件名:
filename ―― 表示发送到客户端文件的文件名。

四. 准备发送到客户端的文件数据:

1. 先将不同类型来源的数据转成byte类型的数组,再通过Response.BinaryWrite方法发送到客户端:

1.1. 读取文件来获得byte数组: string FileName; //生成或获取要发送到客户端的文件名

[code]
string filePath = Server.MapPath("./") + FileName; //假设文件在当前目录下

if(File.Exists(filePath) == false)

{

//服务器上没有这个文件

return;

}

FileStream myFile = File.OpenRead(filePath); //读取文件进入FileStream

byte[] fileCont = new byte[myFile.Length];

myFile.Read(fileCont,0,(int)myFile.Length); //将文件流中的内容转成byte数组
[/code]

1.2. 在数据库的二进制字段中读取: //从url获取图片的id

[code]
string ImageId = Request.QueryString["img"];

//构建查询语句

string sqlText = "SELECT img_data, img_contenttype FROM Image WHERE img_pk = " + ImageId;

SqlConnection connection = new SqlConnection( ConfigurationSettings.AppSettings["DSN"].ToString() );

SqlCommand command = new SqlCommand( sqlText, connection);

connection.Open();

SqlDataReader dr = command.ExecuteReader();

if ( dr.Read())

{

byte[] fileCont = (byte[]) dr["img_data"] ;

}

connection.Close();
[/code]

1.3. 从internet上读取文件:

[code]
HttpWebRequest myWebRequest = (HttpWebRequest)WebRequest.Create( "http://www.via.com/aa.xls ");

HttpWebResponse myWebResponse = (HttpWebResponse)myWebRequest.GetResponse();

Stream readStream = myWebResponse.GetResponseStream();

byte[] bytes = new byte[readStream.Length];

bytes = readStream.Read(bytes,0,readStream.Length);
[/code]

通过上述三种方法获得的文件内容的byte数组就可以用来输出了:
Page.Response.BinaryWrite(fileCont);

Page.Response.End();

2. 直接读取文件输出: string FileName; //生成或获取要发送到客户端的文件名

[/code]
string filePath = Server.MapPath("./") + FileName; //假设文件在当前目录下

if(File.Exists(filePath) == false)

{

//服务器上没有这个文件

return;

}

Page.Response.Clear();

Page.Response.AddHeader( "Content-Type", "image/gif" ); //根据MIME的不同设置

Page.Response.AddHeader("Content-Disposition", "inline;filename=" + filePath);

Page.Response.WriteFile(filePath);

Page.Response.End();
[/code]

2.
By 思归
From http://blog.joycode.com/saucer/posts/16225.aspx

我们在上传大文件时都遇到过这样或那样的问题。设置很大的maxRequestLength值并不能完全解决问题,因为ASP.NET会block直到把整个文件载入内存后,再加以处理。实际上,如果文件很大的话,我们经常会见到Internet Explorer显示 "The page cannot be displayed - Cannot find server or DNS Error",好像是怎么也catch不了这个错误。为什么?因为这是个client side错误,server side端的Application_Error是处理不到的,可以参考这个帖子研究一下产生这个错误的机理。
handling server error when upload file too large
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=OnZOezyuCHA.2380%40TK2MSFTNGP11

解决的方法是利用隐含的HttpWorkerRequest,用它的GetPreloadedEntityBody 和 ReadEntityBody方法从IIS为ASP.NET建立的pipe里分块读取数据

IServiceProvider provider = (IServiceProvider) HttpContext.Current;
HttpWorkerRequest wr = (HttpWorkerRequest) provider.GetService(typeof(HttpWorkerRequest));
byte[] bs = wr.GetPreloadedEntityBody();
....
if (!wr.IsEntireEntityBodyIsPreloaded())
{
int n = 1024;
byte[] bs2 = new byte[n];
while (wr.ReadEntityBody(bs2,n) >0)
{
.....
}
}

Chris Hynes为我们提供了这样的一个方案(用HttpModule),该方案除了允许你上传大文件外,还能实时显示上传进度:

ASP.NET Upload Magic Part 2 http://krystalware.com/blog/archive/2004/02/04/181.aspx

(按:不过看来只有在NetFramework1.1以上才能用)

3.
文件分块多点异步上传的 Web Services 及其客户端(非Web)应用程序调用相关异步执行的 Web Method

By playyuer
From http://blog.csdn.net/playyuer/archive/2004/12/11/213069.aspx

实现支持文件分块多点异步上传的 Web Services 及其客户端(非Web)应用程序调用相关异步执行的 Web Method

本文的客户端应用程序不包括 ASP.Net Web 应用程序!

本文假设 URL: http://localhost/mywebservices/updownload.asmx

共有 4 个程序文件 (Web.Config 就不赘述了)

Server Side:

标题中所提到的 "异步" 其实在服务器端的程序并没有什么特殊的,而主要是通过客户端应用程序
异步调用相关 Web Method 实现的!

1. updownload.asmx ,位于 IIS 的某个 Web 共享目录,代码如下,只有一句话:

<%@ WebService Language="c#" Codebehind="UpDownLoad.asmx.cs" Class="Service1" %>

2. updownload.asmx.cs ,即: updownload.asmx 的 Codebehind ,位于 IIS 的某个 Web 共享目录的 bin 子目录下,代码如下:

/*

本文件位于 Web 共享目录的 bin 子目录下,通过执行如下命令行编译:
csc /t:library updownload.asmx.cs

*/
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using System.IO;
using System;

public class Service1 : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}

//从 Web Method 本身,其实看不出 "同步" 还是 "异步"
[WebMethod(Description = "为了支持多点分块异步上传文件,此方法必须由客户端预先调用,以便在服务器端生成指定 FileName 和 Length 大小的空白文件预定空间! 建议客户端同步调用")]
public string CreateBlankFile(string FileName,int Length) //建议由客户端同步调用
{
FileStream fs = new FileStream(Server.MapPath(".") + "\\" + FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
fs.Write(new byte[Length], 0, Length);
fs.Close();
fs = null;
return FileName + " (" + Length + ") 空白文件已经创建!";
}

[WebMethod(Description = "提供一个用于一次完整上传整个文件的方法! 建议客户端同步调用")]
public string UploadFileBytes(byte[] Bytes,string FileName)
{
return UploadFileChunkBytes(Bytes, 0, FileName);
}

[WebMethod(Description = "提供一个用于一次只上传由 Position 位置起始的, Bytes 字节的 FileName 文件块存入服务器端相应文件的相应字节位置! 建议客户端异步调用")]
// 这里只要多提供一个 Position 参数,余下的再由客户端调用异步的该方法,就轻松达到目的了!
public string UploadFileChunkBytes(byte[] Bytes,int Position,string FileName)
{
try
{
FileStream fs = new FileStream(Server.MapPath(".") + "\\" + FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
//该 Bytes 的字节要写到 服务器端 相应文件的从 Position 开始的字节
fs.Position = Position;
fs.Write(Bytes, 0, Bytes.Length);
fs.Close();
fs = null;
return FileName + " 文件块: 位置[" + Position + "," + (Position + Bytes.Length) + "] 大小(" + Bytes.Length + ") 上传成功!";
}
catch (Exception e)
{
return e.Message;
}
}

[WebMethod]
public byte[] DownloadFileBytes(string FileName)
{
if (File.Exists(FileName))
{
try
{
FileStream fs = File.OpenRead(FileName);
int i = (int) fs.Length;
byte[] ba = new byte[i];
fs.Read(ba,0,i);
fs.Close();
return ba;
}
catch
{
return new byte[0];
}
}
else
{
return new byte[0];
}
}
}


//=======================================================================

Client Side:
3. UpDownloadProxy.cs :
本文件由如下命令生成
% Visual Studio .Net 2003 安装目录下的 %\SDK\v1.1\Bin\wsdl.exe
具体命令行如下:
wsdl.exe /l:CS /out:UpDownloadProxy.cs http://localhost/MyWebServices/updownload.asmx?wsdl
生成的本地的客户端代理类代码里已经为每个 Web Method 生成了可异步和同步执行的方法,例如:
public string HelloWorld() {}
public System.IAsyncResult BeginHelloWorld(...) {}
public string EndHelloWorld(...) {}

下面是该命令行生成的完整的 UpDownloadProxy.cs 代码,就不修改了:
/*

通过执行如下命令行编译,生成 UpDownloadProxy.dll :
csc /t:library UpDownloadProxy.cs

*/

//------------------------------------------------------------------------------
//
// This code was generated by a tool.
// Runtime Version: 1.1.4322.573
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//

//------------------------------------------------------------------------------

//
// 此源代码由 wsdl, Version=1.1.4322.573 自动生成。
//
using System.Diagnostics;
using System.Xml.Serialization;
using System;
using System.Web.Services.Protocols;
using System.ComponentModel;
using System.Web.Services;


///
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(Name="Service1Soap", Namespace="http://tempuri.org/")]
public class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol {

///
public Service1() {
this.Url = "http://localhost/MyWebServices/updownload.asmx";
}

///
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloWorld", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public string HelloWorld() {
object[] results = this.Invoke("HelloWorld", new object[0]);
return ((string)(results[0]));
}

///
public System.IAsyncResult BeginHelloWorld(System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("HelloWorld", new object[0], callback, asyncState);
}

///
public string EndHelloWorld(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((string)(results[0]));
}

///
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/CreateBlankFile", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public string CreateBlankFile(string FileName, int Length) {
object[] results = this.Invoke("CreateBlankFile", new object[] {
FileName,
Length});
return ((string)(results[0]));
}

///
public System.IAsyncResult BeginCreateBlankFile(string FileName, int Length, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("CreateBlankFile", new object[] {
FileName,
Length}, callback, asyncState);
}

///
public string EndCreateBlankFile(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((string)(results[0]));
}

///
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/UploadFileBytes", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public string UploadFileBytes([System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] System.Byte[] Bytes, string FileName) {
object[] results = this.Invoke("UploadFileBytes", new object[] {
Bytes,
FileName});
return ((string)(results[0]));
}

///
public System.IAsyncResult BeginUploadFileBytes(System.Byte[] Bytes, string FileName, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("UploadFileBytes", new object[] {
Bytes,
FileName}, callback, asyncState);
}

///
public string EndUploadFileBytes(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((string)(results[0]));
}

///
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/UploadFileChunkBytes", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public string UploadFileChunkBytes([System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] System.Byte[] Bytes, int Position, string FileName) {
object[] results = this.Invoke("UploadFileChunkBytes", new object[] {
Bytes,
Position,
FileName});
return ((string)(results[0]));
}

///
public System.IAsyncResult BeginUploadFileChunkBytes(System.Byte[] Bytes, int Position, string FileName, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("UploadFileChunkBytes", new object[] {
Bytes,
Position,
FileName}, callback, asyncState);
}

///
public string EndUploadFileChunkBytes(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((string)(results[0]));
}

///
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/DownloadFileBytes", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
[return: System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")]
public System.Byte[] DownloadFileBytes(string FileName) {
object[] results = this.Invoke("DownloadFileBytes", new object[] {
FileName});
return ((System.Byte[])(results[0]));
}

///
public System.IAsyncResult BeginDownloadFileBytes(string FileName, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("DownloadFileBytes", new object[] {
FileName}, callback, asyncState);
}

///
public System.Byte[] EndDownloadFileBytes(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((System.Byte[])(results[0]));
}
}

//=======================================================================
4. UpDownloadClient.cs :
该程序才是真正实现文件分块多点异步上传的核心代码:

/*

通过执行如下命令行编译:
csc updownloadClient.cs /r:updownloadproxy.dll

*/
using System;
using System.IO;

public class Class1
{
static void Main(string[] args)
{
//Download(ServerSidepath, ClientSidePath)
Download(@"e:\test.jpg", @"f:\test_local.jpg");
System.Console.WriteLine("down End");

System.Console.WriteLine("同步 up file exec ...");
UploadFile(@"e:\Northwind.mdb");
System.Console.WriteLine("同步 up file End\n");

System.Console.WriteLine("异步 up chunks exec ...");
UploadFileChunks(@"e:\test.rar", 64);
System.Console.ReadLine();
}

public static void UploadFile(string LocalFileName)
{
Service1 xx = new Service1();
FileStream fs = new FileStream(LocalFileName, FileMode.Open); //Client Side Path
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
//调用 "同步执行" 的本地 Web Sevices 代理类的 方法,相当于同步调用了 Web Method !
xx.UploadFileBytes(buffer, System.IO.Path.GetFileName(LocalFileName));
}

//指定要上传的本地文件的路径,及每次上传文件块的大小
public static void UploadFileChunks(string LocalFileName,int ChunkSize)
{
Service1 xx = new Service1();
string filename = System.IO.Path.GetFileName(LocalFileName);

FileStream fs = new FileStream(LocalFileName, FileMode.Open); //Client Side Path
//fs = File.OpenRead(LocalFileName);

int r = (int) fs.Length; //用于记录剩余还未上传的字节数,初值是文件的大小

//调用 "同步执行" 的本地 Web Sevices 代理类的 方法,相当于同步调用了 Web Method !
//预定服务器端空间
xx.CreateBlankFile(filename,r);
int size = ChunkSize * 1024;
int k = 0; //用于记录已经上传的字节数
i++; //用于记录上传的文件块数
while (r >= size)
{
byte[] buffer = new byte[size];
fs.Read(buffer,0,buffer.Length);
//调用 "异步执行" 的本地 Web Sevices 代理类的 方法,相当于异步调用了 Web Method !
//该 buffer 的字节要写到 服务器端 相应文件的从 Position = k 开始的字节
xx.BeginUploadFileChunkBytes(buffer,k,filename,new AsyncCallback(UploadFileChunkCallback),xx);
k += size;
r -= size;
i++;
}
if (r > 0) //剩余的零头
{
byte[] buffer = new byte[r];
fs.Read(buffer,0,buffer.Length);
//调用 "异步执行" 的本地 Web Sevices 代理类的 方法,相当于异步调用了 Web Method !
//该 buffer 的字节要写到 服务器端 相应文件的从 Position = k 开始的字节
xx.BeginUploadFileChunkBytes(buffer,k,filename,new AsyncCallback(UploadFileChunkCallback),xx);
i++;
}
fs.Close();

}

private static int i = -1; //用于记录上传的文件块数

private static void UploadFileChunkCallback(IAsyncResult ar)
{
Service1 x = (Service1) ar.AsyncState;
Console.WriteLine(x.EndUploadFileChunkBytes(ar));
if ( --i == 0)
{
Console.WriteLine("异步 up all chunks end");
}
}

public static void Download(string ServerSideFileName,string LocalFileName)
{
Service1 xx = new Service1();
byte[] ba = xx.DownloadFileBytes(ServerSideFileName); //Server Side Path

FileStream fs = new FileStream(LocalFileName, FileMode.Create); //Client Side Path
fs.Write(ba,0,ba.Length);
fs.Close();
}
}


//===========================================================================
至此我们通过纯手工的方式完成了任务,之所以不用 VS 就是为了让码子简洁明了!
Microshaoft .Night 就是这么平易近人! (PMPMP to MS)
通过 Web Sevices 上传文件非常简单,甚至比传统的 http Web 上床还简单!
同时较轻松地就实现了文件分块多点异步上传:
Server 端代码没啥特殊的!
Client 端代码稍微复杂些!

(按:WSE2.0已经有这个功能了)



<< Home

This page is powered by Blogger. Isn't yours?