百度空间 | 百度首页 
 
查看文章
 
学习Asp.net2.0上传文件组件编写笔记(一)
2006年11月27日 星期一 21:33

学习Asp.net2.0上传文件组件编写笔记

                                          by    widebright                         http://hi.baidu.com/widebright     

     Asp.Net本身提供的文件上传控件有一些确定,比如不能上传大文件、不能获取进度条。不能捕获文件超时错误等。所以很多人都写了自

定义的上传组件,还听说有人拿这种组件来卖,卖到600多块钱一个。因为前段时间在做文件上传,所以就也找时间又看了一下这方面的东西。
     在网上找资料,大都是采用这个方案:利用隐含的HttpWorkerRequest,用它的GetPreloadedEntityBody 和 ReadEntityBody方法从IIS为

ASP.NET建立的pipe里分块读取数据。Chris Hynes为我们提供了这样的一个方案(用HttpModule),该方案除了允许你上传大文件外,还能实时

显示上传进度。
     关于这个方案的追详细讨论是在微软的Asp.net论坛上看到的,众多高手讨论,最后得出结果。这个13页的帖子的地址为:

http://forums.asp.net/1/1338742/ShowThread.aspx

     其他有参考价值的资料有:
1。Wu.Country@侠缘 写的asp.net上传组件,开源的,有详细代码,也是这种方案,http://wucountry.cnblogs.com/    Wu.Country@侠缘写

的文章 “SunriseUpload.0.9.1的源码分析(七)” 也是关于原理的。
2。宝玉 (http://webuc.net/dotey/)写的asp上传文件组件源代码,也看了他的几篇文章,也是文件上传方面的,抱歉忘记网,自己上网找

应该可以找到的。
3。因为这个采用的是写自定义httpModule来做到到的,所以还看了以下学习httpModule 的资料:
    ASP_NET中的Http Handles - [橫渡] - CSDNBlog
    HttpModule和HttpHandler(续) - 武子的专栏 - CSDNBlog

    MSDN Library > .NET Development > ASP.NET > Building ASP.NET Applications > ASP.NET Web Applications > Extending ASP.NET

Processing with HTTP Modules > How to: Create Custom HTTP Modules
   
    了解ASP_NET底层架构 - 枫的专栏 - CSDNBlog
4 。因为上传文件是通过http协议,所以看还要看“RFC 1867 (rfc1867) - Form-based File Upload in HTML”,上传文件的协议就是在这个

文档上描述的。


    
我自己写的上传组件实现代码。通过写自定义的httpmodule,调用GetPreloadedEntityBody 和
ReadEntityBody方法截获文件上传数据,自己处理content,把文件写入硬盘。仅仅是学习,没有实现进度条,采用vb.net,在Asp.net2.0 windows xp上测试通过,本机测试是上传文件速率只有6.3M/s,比较低效率。懒得写原理说明了,我的注释写的很详细,因为我也是学着玩的。和前人的代码没有多大分别,我增加了一点,可以避免上传大文件时的超时错误,是通过.Net反射来做到的,还有就是以前的代码有上传文件太大的错误,也是通过反射来避免。

以下就是代码:

////////////自定义HttpModule////////////////
Imports System
Imports System.Text
Imports System.Web
Imports System.Reflection        '使用了反射
          

Public Class widebrightHttpModule
    Implements IHttpModule

    Private parser As ContentBlockParser = New ContentBlockParser

    Public Sub Dispose() Implements System.Web.IHttpModule.Dispose

    End Sub

    Public Sub Init(ByVal application As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
        AddHandler application.BeginRequest, _
            AddressOf Me.WB_BeginRequest
        AddHandler application.EndRequest, _
            AddressOf Me.WB_EndRequest
        AddHandler application.Error, AddressOf Me.WB_RequestError

    End Sub

    Private Sub WB_BeginRequest(ByVal source As Object, ByVal e As EventArgs)
        ' Create HttpApplication and HttpContext objects to access
        ' request and response properties.
        Dim application As HttpApplication = CType(source, HttpApplication)
        Dim context As HttpContext = application.Context
        context.Response.Write _
           ("<h1><font color=red>HelloWorldModule: " & "Beginning of Request</font></h1><hr>")


        Dim work_request As HttpWorkerRequest = GetWorkerRequest()

        'Try

        'content_type获得数据是这样的: multipart/form-data; boundary=---------------------------7d6242291005ae
        Dim content_type As String = application.Request.ContentType.ToLower()
        If (Not content_type.StartsWith("multipart/form-data")) Then Return '只处理传送文件的request
        If (Not work_request.HasEntityBody()) Then Return '只处理 包含 body 数据 的request

        '可能要缓存所有的数据才能获取Request.Files.Count,所以这时数据全部在GetPreloadedEntityBody()处得到。不能用这个!!!


        '查了资料,如果你访问了HttpRequest object,的任何属性,那么这个request的所有内容读要被预先读入内存,包括大量的文件内容

数据
        '所以千万不要访问application.Request的任何属性。否则将在GetPreloadedEntityBody()一下得到全部的数据
        ' If application.Request.Files.Count = 0 Then Return   不能用这个!!!!

        '获取边界标志
        Dim strboundary As String = "\r\n--" + content_type.Substring(content_type.IndexOf("boundary=") + 9)
        '边界字节标志,可用于区分客户端发送过来的request数据中各种不同content,边界标志。
        Dim boundaryData As Byte() = Encoding.UTF8.GetBytes(strboundary)

        '总个请求的数据长度,包括各种content和文件内容数据, 尝试上传一个2。8G的文件,结果这里得到一个负数,不知道是否http协议

不支持这么大的 文件
        'web。config 中定义maxRequestLength的解释 The value for the property 'maxRequestLength' is not valid. The error is:

The value must be inside the range 0-2097151.
        Dim request_total_length As Long = Convert.ToInt64(work_request.GetKnownRequestHeader

(HttpWorkerRequest.HeaderContentLength))
        '保存当前的request请求内容数据已经处理了多少字节。
        Dim finished_length As Long = 0


        Dim max_size As Long = 524288000 '定义允许的最大的上传文件字节长度,这里默认为 500M
        '仅仅在这里设置允许的文件大小,没有在web。config 文件中设置
        '***********************************
        '<httpRuntime   maxRequestLength="809600"   
        ' useFullyQualifiedRedirectUrl = "true"
        ' executionTimeout="600" />
        '*********************************************
        '则文件可以上传保存, 但最后还是出现 Maximum request length exceeded.的错误
        '下面使用 respone。redirect或者使用反射修改request._contentlength 属性后之后就没有此问题了。
     

        If (request_total_length > max_size) Then Return

        Dim preloaded_entity_body As Byte() = work_request.GetPreloadedEntityBody()

        If (Not preloaded_entity_body Is Nothing) Then
            ' current_point = current_point + preloaded_entity_body.Length
            Dim dddddddd As String = System.Text.Encoding.ASCII.GetChars(preloaded_entity_body)
            'PreloadedEntityBody也是属于整个request contents的一部分,所以这里也要设置 current_position,表示已经处理了多少字

节了。
            finished_length = finished_length + preloaded_entity_body.Length
            '某些时候content filename= 标志会在这里得到,所以还要处理这些数据
            parser.parse_bytes(preloaded_entity_body, preloaded_entity_body.Length)
        End If

        '是否所有的数据都在上面的GetPreloadedEntityBody()中得到,是就没必要做下一步的处理的了
        If (Not work_request.IsEntireEntityBodyIsPreloaded()) Then


            '最常见的是ReadEntityBody返回8192 个字节长度的数据,所以定义太大是没有什么必要的。
            Const BUFFER_LENGTH As Integer = 10240
            Dim buffer(BUFFER_LENGTH) As Byte
            Dim used_length As Integer = 0

            While (request_total_length - finished_length > BUFFER_LENGTH)

                used_length = work_request.ReadEntityBody(buffer, BUFFER_LENGTH)
                parser.parse_bytes(buffer, used_length)
                finished_length = finished_length + used_length
                '可以正确解释中文文件名,有人说网络上中文编码都是用的UTF8
                '实际调试发现使用System.Text.Encoding.UTF8来解码content header是正确得
                ' Dim dddddd As String = System.Text.Encoding.UTF8.GetChars(buffer)      ’这一句是调试的时候查看代码的,比较

影响性能,注释掉后传送速度由 3.3m/s 升到5.8m/s。


                '上传大文件时避免超时错误的解决方法*******************************

                '查看资料知道编程设置application.Server.ScriptTimeout 和 在web.config文件中 设置
                ' executionTimeout 有一样的作用。
                '****************************
                '     <httpRuntime   maxRequestLength="809600"   
                'useFullyQualifiedRedirectUrl = "true"
                '        executionTimeout="2" />
                ' <!--
                '只有当 compilation 元素中的调试属性为 False 时,此超时属性才适用,这时要使用Release模式编译, 若要帮助避免在

调试期间关闭应用程序,请不要将此超时属性设置为较大值
                '    -->  
                '********************************
                '使用Reflector查看 发现HttpServerUtility 类型(以上application.Server的类型)的.ScriptTimeout属性代码如下:
                '                  public int ScriptTimeout
                '{
                '      get
                '      {
                '            if (this._context != null)
                '            {
                '                  return Convert.ToInt32(this._context.Timeout.TotalSeconds, CultureInfo.InvariantCulture);
                '            }
                '            return 110;
                '      }
                '      [AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Medium)]
                '      set
                '      {
                '            if (this._context == null)
                '            {
                '                  throw new HttpException(SR.GetString("Server_not_available"));
                '            }
                '        If (value <= 0) Then
                '            {
                '                  throw new ArgumentOutOfRangeException("value");
                '            }
                '            this._context.Timeout = new TimeSpan(0, 0, value);
                '      }
                '}


                ' 实际就是设置_context.Timeout 。
                '            再一次使用Reflector查看(发现HttpContext的有一个方法为)
                ' internal void SetStartTime()
                '{
                ' this._timeoutStartTime = DateTime.UtcNow;   把超时时间设置为当前时间
                '}
                '怀疑和超时有关,通过以下的反射代码,调用此方法,果然可以做到传送时不超时。在 IIS5。1 和》net 2。0 下测试通过

                Dim ddflags As BindingFlags = (BindingFlags.NonPublic Or BindingFlags.Instance)
                Dim hct As Type = context.GetType
                hct.GetMethod("SetStartTime", ddflags).Invoke(context, Nothing)   '重置 context当前时间,避免超时

            End While

            '保证最后一个buffer没有超过已知的数据长度,这样就不用给ReadEntityBody发送更多的不需要得填充请求,
            '避免ReadEntityBody等待更多得数据到来,来填充我们提供得buffer,而这时数据已经获取完毕了。
            '这样就可以避免超时等待,尽快得退出循环。
            Dim left_size As Integer = request_total_length - finished_length
            Array.Resize(buffer, left_size)
            used_length = work_request.ReadEntityBody(buffer, left_size)
            Dim dddddddd As String = System.Text.Encoding.UTF8.GetChars(buffer)
            parser.parse_bytes(buffer, used_length)


            'work_request.SendStatus(200, "ok")
            'work_request.EndOfRequest()
            'Response.End() 调用这个可以结束当前请求并停止触发在HTTP管道中后续的事件,然后发生将控制返回到Web服务器中,

EndRequest事件并不会被触发

        End If

        '通过下面这个几个语句检测到,在调试的时候这里是没有数据的 , ReadEntityBody读到的数据没有被放到preload那块内存
        '但下面的 GetRequestData打印出来的却是包含整个文件的内容,确实是 ReadEntityBody中包含的数据也被缓存了。
        'preloaded_entity_body = work_request.GetPreloadedEntityBody()
        'If (Not preloaded_entity_body Is Nothing) Then
        '    Dim dddddddd As String = System.Text.Encoding.ASCII.GetChars(preloaded_entity_body)
        'End If

      

        '加上这几句后在IIS 5。1版+IE7中直接运行的时候,上传文件借宿后就没有超时等待了,但如果文件太大,错误依然存在。
        '但在调试的时候有问题,AddContentBytesToRequest函数getfield那里有问题。
        '估计是IIS版本不同
        Dim contents_without_file_data As Byte() = parser.Get_All_Contents()
        parser.Reset_Parser() 'Get_All_Contents 之后再通知parser准备处理下一个页面请求,否则可能Get_All_Contents得不到数据
        Dim ddddd As String = System.Text.Encoding.UTF8.GetChars(contents_without_file_data)


        GetRequestData(context, work_request)
        '这时打印时候, 好像数据都还在缓存里, 全部preload了, 怀疑所有ReadEntityBody读到的数据, 也被放到preload那块内存了
        AddContentBytesToRequest(work_request, contents_without_file_data)     '提交content修改到IIS
        GetRequestData(context, work_request)                               '在页面上打印request信息,调试的时候使用,看

AddContentBytesToRequest是否起作用了。


        '***** 避免上传结束后 文件太大错误的 解决方法一*******************************

        '把respone重新引导到一个地址,重新加载一下网页,还是打开上传文件页面,就不会出现上传保存文件结束后的,文件太大提示了


        '这个可以避免
        '[HttpException (0x80004005): Maximum request length exceeded.]
        'System.Web.HttpRequest.GetEntireRawContent(+3226541)
        ' 错误,还没有找到其他的解决办法。
        ' context.Response.Redirect("default.aspx")    '重定向可以避免错误页面
        '*******************************************************************************

        '*****避免上传结束后 文件太大错误的 解决方法二*******************************

        '经过使用Reflector查看GetEntireRawContent,发现这个从错误 和Request.ContentLength 有关,
        '而ContentLength被设置 为等于._wr.GetKnownRequestHeader(11),这个值是请求的总长度,所以最后比较时会发生文件太大错误,
        ''Dim ddd As Integer = context.Request.ContentLength          '和httprequest的 属性   _contentLength 有关,考虑使用

反射修改

        Dim flags As BindingFlags = (BindingFlags.NonPublic Or BindingFlags.Instance)
        Dim hr As Type = context.Request.GetType
        hr.GetField("_contentLength", flags).SetValue(context.Request, contents_without_file_data.Length)     '修改这个也可以

避免错误页面
        '*****************************************************************************


        'Catch ex As Exception

        'End Try


    End Sub

    '这里EndRequest不用写什么东西都可以
    Private Sub WB_EndRequest(ByVal source As Object, ByVal e As EventArgs)
        Dim application As HttpApplication = CType(source, HttpApplication)
        Dim context As HttpContext = application.Context
        context.Response.Write _
            ("<hr><h1><font color=red>HelloWorldModule: " & "End of Request</font></h1>")
    End Sub


    Private Sub WB_RequestError(ByVal source As Object, ByVal e As EventArgs)

    End Sub

    Private Function GetWorkerRequest() As HttpWorkerRequest

        Dim provider As IServiceProvider = HttpContext.Current
        Return CType(provider.GetService(GetType(HttpWorkerRequest)), HttpWorkerRequest)
    End Function

    Private Function AddContentBytesToRequest(ByVal wkr As HttpWorkerRequest, ByVal textData As Byte()) As Boolean

        Dim type_unknown As Type

        Dim flags As BindingFlags = (BindingFlags.NonPublic Or BindingFlags.Instance)

        Dim ddd As String = HttpContext.Current.Request.ServerVariables("SERVER_SOFTWARE")

        ' Is there application host IIS6.0?
        If (HttpContext.Current.Request.ServerVariables("SERVER_SOFTWARE").Equals("Microsoft-IIS/6.0")) Then
            type_unknown = wkr.GetType().BaseType.BaseType
        ElseIf (HttpContext.Current.Request.ServerVariables("SERVER_SOFTWARE").Equals("Microsoft-IIS/5.1")) Then
            ' 在IIS5.1中获取的是 BaseType : System.Web.Hosting.ISAPIWorkerRequest
            'System.Web.Hosting.ISAPIWorkerRequest类是HttpWorkerRequest类的一个抽象子类(译注:HttpWorkerRequest和

ISAPIWorkerRequest都是抽象类, 并且ISAPIWorkerRequest继承自HttpWorkerRequest),

            type_unknown = wkr.GetType().BaseType
        Else '直接在VS2005的调试器中调试运行时,SERVER_SOFTWARE 变量为空,而不是上面的两个值
            '在VS2005的调试调试器中调试运行时,下面一句获取的是HttpWorkerRequest类
            type_unknown = wkr.GetType().BaseType.BaseType
        End If
        'The difference that the second line returns System.Web.Hosting.ISAPIWorkerRequest
        'and the first System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6. Unfortunately there's no documentation
        'on these classes. I can't even use them for casting, comparing (IS operator) or whatever.

        'Set values of working request


        type_unknown.GetField("_contentAvailLength", flags).SetValue(wkr, textData.Length)
        type_unknown.GetField("_contentTotalLength", flags).SetValue(wkr, textData.Length)
        type_unknown.GetField("_preloadedContent", flags).SetValue(wkr, textData)
        type_unknown.GetField("_preloadedContentRead", flags).SetValue(wkr, True)
        Return True
    End Function

    '打印 request的各项属性,主要是content 内容长度等各项数据,便于调试
    Private Sub GetRequestData(ByVal ctx As HttpContext, ByVal hwr As HttpWorkerRequest)

        Dim bindingFlags As BindingFlags = bindingFlags.Instance Or bindingFlags.NonPublic

        Dim type_unknown As Type = hwr.GetType().BaseType
        ctx.Response.Write("SERVER_SOFTWARE: " + HttpContext.Current.Request.ServerVariables("SERVER_SOFTWARE") + "<br>")
        ctx.Response.Write("BaseType : " + type_unknown.ToString() + "<br>")
        ctx.Response.Write("ContentAvailLength : " + type_unknown.GetField("_contentAvailLength", bindingFlags).GetValue

(hwr).ToString() + "<br>")
        ctx.Response.Write("ContentTotalLength : " + type_unknown.GetField("_contentTotalLength", bindingFlags).GetValue

(hwr).ToString() + "<br>")
        ctx.Response.Write("PreloadedContentRead : " + type_unknown.GetField("_preloadedContentRead", bindingFlags).GetValue

(hwr).ToString() + "<br>")
        ctx.Response.Write("ContentType : " + type_unknown.GetField("_contentType", bindingFlags).GetValue(hwr).ToString() +

"<br>")
        ctx.Response.Write("ContentLengthSent : " + type_unknown.GetField("_contentLengthSent", bindingFlags).GetValue

(hwr).ToString() + "<br>")
        ctx.Response.Write("PreloadedContent : <br>" + Text.Encoding.UTF8.GetString(type_unknown.GetField

("_preloadedContent", bindingFlags).GetValue(hwr)))
    End Sub

End Class

'private void ClearRequestData()
'{
'BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
'Type type = hwr.GetType().BaseType;
'type.GetField("_contentAvailLength", bindingFlags).SetValue(hwr, 0);
'type.GetField("_contentTotalLength", bindingFlags).SetValue(hwr, 0);
'type.GetField("_preloadedContent", bindingFlags).SetValue(hwr, null);
'type.GetField("_preloadedContentRead", bindingFlags).SetValue(hwr, true);
'//type.GetField("_contentType", bindingFlags).SetValue(hwr, 0);
'type.GetField("_contentLengthSent", bindingFlags).SetValue(hwr, true);
'}

'private void SetRequestData(byte[] Data)
'{
'BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
'Type type = hwr.GetType().BaseType;
'type.GetField("_contentAvailLength", bindingFlags).SetValue(hwr, Data.Length);
'type.GetField("_contentTotalLength", bindingFlags).SetValue(hwr, Data.Length);
'type.GetField("_preloadedContent", bindingFlags).SetValue(hwr, Data);
'type.GetField("_preloadedContentRead", bindingFlags).SetValue(hwr, true);
'}

'private void GetRequestData()
'{
'BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
'Type type = hwr.GetType().BaseType;
'ctx.Response.Write("BaseType : " + type.ToString() + "<br>");
'ctx.Response.Write("ContentAvailLength : " + type.GetField("_contentAvailLength", bindingFlags).GetValue(hwr).ToString() +

"<br>");
'ctx.Response.Write("ContentTotalLength : " + type.GetField("_contentTotalLength", bindingFlags).GetValue(hwr).ToString() +

"<br>");
'ctx.Response.Write("PreloadedContentRead : " + type.GetField("_preloadedContentRead", bindingFlags).GetValue(hwr).ToString()

+ "<br>");
'ctx.Response.Write("ContentType : " + type.GetField("_contentType", bindingFlags).GetValue(hwr).ToString() + "<br>");
'ctx.Response.Write("ContentLengthSent : " + type.GetField("_contentLengthSent", bindingFlags).GetValue(hwr).ToString() +

"<br>");
'ctx.Response.Write("PreloadedContent : <br>" + DecodeToString((byte[])type.GetField("_preloadedContent",

bindingFlags).GetValue(hwr)));
'}


'private void InjectTextParts(HttpWorkerRequest request, byte[] textParts)
' {
'   BindingFlags flags1 = BindingFlags.NonPublic | BindingFlags.Instance;
'   Type type1 = request.GetType();
'   while ((type1 != null) && (type1.FullName != "System.Web.Hosting.ISAPIWorkerRequest"))
'   {
'    type1 = type1.BaseType;
'   }
'   if (type1 != null)
'   {
'    type1.GetField("_contentAvailLength", flags1).SetValue(request, textParts.Length);
'    type1.GetField("_contentTotalLength", flags1).SetValue(request, textParts.Length);
'    type1.GetField("_preloadedContent", flags1).SetValue(request, textParts);
'    type1.GetField("_preloadedContentRead", flags1).SetValue(request, true);
'   }
' }


未完 待续


类别:程序设计 | | 添加到搜藏 | 分享到i贴吧 | 浏览() | 评论 (1)
 
最近读者:
 
网友评论:
2
2007年06月15日 星期五 14:16 | 回复
大哥>给个源码把 谢谢先
 
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2010 Baidu