现在的位置: 首页 > 综合 > 正文

Server Side File Upload Progress Bar

2013年04月30日 ⁄ 综合 ⁄ 共 7554字 ⁄ 字号 评论关闭

To test the application, please type http://localhost/fileupload/webform1.aspx?guid=123abc. It is recommended that you test with files that is over 20MB to see the effects. During testing, please use two machines to test - one as a server and one as a client. When testing on the same machine, the internet explorer tends to hang up during upload because the ASP.NET process has taken all the CPU power from the internet explorer, preventing it from refreshing the progress status in the window.

Introduction

There are many ASP.NET file upload progress bars flowing around, but I have come across many of them that don't work. This code is referenced by the article in http://krystalware.com/blog/archive/2004/10/11/303.aspx. It works. However, that one provides more functions then what I want, so I spent time to go through his code and extract some important bits of his code and build this file upload progress bar.

<span class="cs-keyword">public</span> <span class="cs-keyword">sealed</span> <span class="cs-keyword">class</span> UploadModule : IHttpModule, IConfigurationSectionHandler
    {
        <span class="cs-comment">//the name of the configuration section the the web.config</span>
        <span class="cs-keyword">private</span> <span class="cs-keyword">const</span> <span class="cs-keyword">string</span> configSection = <span class="cpp-string">"UploadModuleManagement"</span>;
        <span class="cs-keyword">private</span> <span class="cs-keyword">const</span> <span class="cs-keyword">string</span> bufferSizeKey = <span class="cpp-string">"uploadBufferSize"</span>;
        <span class="cs-keyword">private</span> <span class="cs-keyword">const</span> <span class="cs-keyword">string</span> pagesKey = <span class="cpp-string">"uploadPages"</span>;
        <span class="cs-keyword">private</span> <span class="cs-keyword">string</span> uploadKey;
        <span class="cs-keyword">private</span> <span class="cs-keyword">string</span> contentLengthKey;
        <span class="cs-keyword">private</span> ReaderWriterLock rwl;
 
        <span class="cs-keyword">public</span> <span class="cs-keyword">void</span> Dispose()
        {
 
        }
 
        <span class="cs-comment">//set the total size in the application object (not session object)</span>
        <span class="cs-keyword">for</span> the progress bar page to keep track on the progress
        <span class="cs-keyword">private</span> <span class="cs-keyword">object</span> TotalSize
        {
            <span class="cs-keyword">set</span>
            {
                <span class="cs-comment">//must use a lock to update the application object </span>
                rwl.AcquireWriterLock(<span class="cs-literal">1000</span>);
                <span class="cs-keyword">try</span>
                {
                    <span class="cs-keyword">if</span> (value==<span class="cs-keyword">null</span>)
                        HttpContext.Current.Application.Remove(
                          contentLengthKey);
                    HttpContext.Current.Application[contentLengthKey]=value;
                }
                <span class="cs-keyword">finally</span>
                {
                    rwl.ReleaseWriterLock();
                }
            }
        }
 
        <span class="cs-comment">//visible by the progress bar page</span>
        <span class="cs-keyword">public</span> <span class="cs-keyword">static</span> <span class="cs-keyword">object</span> GetTotalSize(<span class="cs-keyword">string</span> guid)
        {
            ReaderWriterLock srwl = <span class="cs-keyword">new</span> ReaderWriterLock();
            <span class="cs-keyword">try</span>
            {
                srwl.AcquireReaderLock(<span class="cs-literal">1000</span>);
                <span class="cs-keyword">return</span> HttpContext.Current.Application[guid + 
                  <span class="cpp-string">"uploadlength"</span>];
            }
            <span class="cs-keyword">finally</span>
            {
                srwl.ReleaseReaderLock();
            }
 
        }
 
        <span class="cs-comment">//visible by the progress bar page</span>
        <span class="cs-keyword">public</span> <span class="cs-keyword">static</span> <span class="cs-keyword">object</span> GetCurrentSize(<span class="cs-keyword">string</span> guid)
        {
            ReaderWriterLock srwl = <span class="cs-keyword">new</span> ReaderWriterLock();
            <span class="cs-keyword">try</span>
            {
                srwl.AcquireReaderLock(<span class="cs-literal">1000</span>);
                <span class="cs-keyword">return</span> HttpContext.Current.Application[guid + 
                  <span class="cpp-string">"uploadprogress"</span>];
            }
            <span class="cs-keyword">finally</span>
            {
                srwl.ReleaseReaderLock();
            }
 
        }
 
       <span class="cs-comment">//visible by the progress bar page</span>
        <span class="cs-keyword">private</span> <span class="cs-keyword">object</span> CurrentSize
        {
            <span class="cs-keyword">set</span>
            {
                rwl.AcquireWriterLock(<span class="cs-literal">1000</span>);
                <span class="cs-keyword">try</span>
                {
                    <span class="cs-keyword">if</span> (value==<span class="cs-keyword">null</span>)
                        HttpContext.Current.Application.Remove(uploadKey);
                    HttpContext.Current.Application[uploadKey] =value;
                }
                <span class="cs-keyword">finally</span>
                {
                    rwl.ReleaseWriterLock();
                }
            }
        }
 
 
        <span class="cs-comment">//read from config section</span>
        <span class="cs-keyword">public</span> <span class="cs-keyword">object</span> Create(<span class="cs-keyword">object</span> parent,<span class="cs-keyword">object</span> configContext, 
            XmlNode section)
        {
            <span class="cs-keyword">if</span> (section != <span class="cs-keyword">null</span>) 
            {
                HttpContext.Current.Application[bufferSizeKey] = 
                  Int32.Parse(section.SelectSingleNode(<span class="cpp-string">"@bufferSize"</span>).Value);
                HttpContext.Current.Application[pagesKey] = 
                  section.SelectSingleNode(<span class="cpp-string">"@pages"</span>).Value.Split(',');
            }
            <span class="cs-keyword">else</span>
            {
                HttpContext.Current.Application[bufferSizeKey] = <span class="cs-literal">1024</span>;
                HttpContext.Current.Application[pagesKey] = <span class="cs-keyword">new</span> <span class="cs-keyword">string</span>[]{<span class="cpp-string">""</span>};
            }
            <span class="cs-keyword">return</span> <span class="cs-keyword">null</span>;
        }
 
        <span class="cs-comment">//check whether the page that is processing </span>
        <span class="cs-comment">//is belonged to an "upload page". </span>
        <span class="cs-keyword">private</span> <span class="cs-keyword">bool</span> IsUploadPages()
        {
            HttpApplication app =  HttpContext.Current.ApplicationInstance;
            <span class="cs-keyword">string</span> [] uploadPages = (<span class="cs-keyword">string</span> [])app.Application[pagesKey];
            <span class="cs-keyword">for</span> (<span class="cs-keyword">int</span> i = <span class="cs-literal">0</span>; i&lt;uploadPages.Length ;i++)
            {
                <span class="cs-keyword">if</span> ( uploadPages[i].ToLower() ==
                  app.Request.Path.Substring(<span class="cs-literal">1</span>).ToLower())
                    <span class="cs-keyword">return</span> <span class="cs-keyword">true</span>;
            }
            <span class="cs-keyword">return</span> <span class="cs-keyword">false</span>;
        }
 
        <span class="cs-keyword">public</span> <span class="cs-keyword">void</span> Init(HttpApplication app)
        {
            ConfigurationSettings.GetConfig(configSection);
            app.BeginRequest += <span class="cs-keyword">new</span> EventHandler(context_BeginRequest);
            app.Error += <span class="cs-keyword">new</span> EventHandler(context_Error);
            app.EndRequest += <span class="cs-keyword">new</span> EventHandler(context_EndRequest);
        }
 
        <span class="cs-keyword">private</span> <span class="cs-keyword">void</span> context_BeginRequest(<span class="cs-keyword">object</span> sender, EventArgs e)
        {
 
            HttpApplication app = sender <span class="cs-keyword">as</span> HttpApplication;
            HttpWorkerRequest worker = GetWorkerRequest(app.Context);
            <span class="cs-comment">//get the querystring, must not use Reques.Params["guid"]</span>
            uploadKey = app.Context.Request.QueryString[<span class="cpp-string">"guid"</span>] + 
              <span class="cpp-string">"uploadprogress"</span>;
            contentLengthKey = app.Context.Request.QueryString[<span class="cpp-string">"guid"</span>] + 
              <span class="cpp-string">"uploadlength"</span>;
            rwl = <span class="cs-keyword">new</span> ReaderWriterLock();
 
           <span class="cs-comment">//the number of bytes get from the client everytime  </span>
           <span class="cs-keyword">int</span> bufferSize = (<span class="cs-keyword">int</span>)app.Application[bufferSizeKey];
            <span class="cs-comment">//check whether the page is an upload page</span>
            <span class="cs-keyword">if</span> (IsUploadPages())
            {
                <span class="cs-keyword">if</span> (app.Context.Request.ContentLength &gt; <span class="cs-literal">0</span>)
                {
                    TotalSize = app.Context.Request.ContentLength;
                    MemoryStream mem = <span class="cs-keyword">new</span> MemoryStream(
                      app.Context.Request.ContentLength);
                    <span class="cs-comment">//read the first portion of data from the client</span>
                    <span class="cs-keyword">byte</span> [] data = worker.GetPreloadedEntityBody();
                    mem.Write(data,  <span class="cs-literal">0</span>, data.Length);
 
                    <span class="cs-keyword">int</span> read = <span class="cs-literal">0</span>;
                    <span class="cs-keyword">int</span> counter = data.Length;
                    <span class="cs-comment">//keep reading if the first </span>
                    <span class="cs-comment">//read cannot read all the data        </span>
                    <span class="cs-keyword">while</span> (counter &lt; app.Context.Request.ContentLength)
                    {
                        <span class="cs-keyword">if</span> (counter + bufferSize &gt; 
                          app.Context.Request.ContentLength)
                            bufferSize = app.Context.Request.ContentLength 
                            - counter;
                        data = <span class="cs-keyword">new</span> <span class="cs-keyword">byte</span>[bufferSize];
                        CurrentSize = counter;
                        read = worker.ReadEntityBody(data, bufferSize);
                        counter += read;
                        mem.Write(data,  <span class="cs-literal">0</span>, bufferSize);
                    }
 
                    mem.Position = <span class="cs-literal">0</span>;
                    <span class="cs-comment">//push all the data to memory stream</span>
                    <span class="cs-keyword">byte</span> [] memData  = <span class="cs-keyword">new</span> <span class="cs-keyword">byte</span>[mem.Length];
                    mem.Read(memData, <span class="cs-literal">0</span>, (<span class="cs-keyword">int</span>)mem.Length);
                    <span class="cs-comment">//finishing the interception, push all </span>
                    <span class="cs-comment">//the data to the worker process again</span>
                    PushRequestToIIS(worker, memData);
                }
            }
        }
 
        <span class="cs-keyword">private</span> <span class="cs-keyword">void</span> context_EndRequest(<span class="cs-keyword">object</span> sender, EventArgs e)
        {
            HttpApplication app = sender <span class="cs-keyword">as</span> HttpApplication;
            <span class="cs-keyword">string</span> [] uploadPages = (<span class="cs-keyword">string</span> [])app.Application[pagesKey];
            <span class="cs-comment">//check whether the page is an upload page and the application </span>
            <span class="cs-comment">//object to null, so that the progress </span>
            <span class="cs-comment">//bar page knows the upload is finished</span>
            <span class="cs-keyword">if</span> (IsUploadPages())
            {
                TotalSize  = <span class="cs-keyword">null</span>;
                CurrentSize = <span class="cs-keyword">null</span>;
            }
        }
 
        <span class="cs-keyword">private</span> <span class="cs-keyword">void</span> context_Error(<span class="cs-keyword">object</span> sender, EventArgs e)
        {
            HttpApplication app = sender <span class="cs-keyword">as</span> HttpApplication;
            <span class="cs-keyword">string</span> [] uploadPages = (<span class="cs-keyword">string</span> [])app.Application[pagesKey];
            <span class="cs-comment">//check whether the page is an upload page</span>
            <span class="cs-keyword">if</span> (IsUploadPages())
            {
                TotalSize  = <span class="cs-keyword">null</span>;
                CurrentSize = <span class="cs-keyword">null</span>;
            }
        }
 
 
        HttpWorkerRequest GetWorkerRequest(HttpContext context)
        {
            IServiceProvider provider = (IServiceProvider)
                    HttpContext.Current;
            <span class="cs-keyword">return</span> (HttpWorkerRequest)provider.GetService(
                     <span class="cs-keyword">typeof</span>(HttpWorkerRequest));
        }
 
        <span class="cs-keyword">private</span> <span class="cs-keyword">void</span> PushRequestToIIS(HttpWorkerRequest request, 
            <span class="cs-keyword">byte</span>[] textParts)
        {
            BindingFlags bindingFlags = BindingFlags.Instance 
                      | BindingFlags.NonPublic; 
            Type type = request.GetType();
            <span class="cs-keyword">while</span> ((type != <span class="cs-keyword">null</span>) &amp;&amp; (type.FullName != 
                 <span class="cpp-string">"System.Web.Hosting.ISAPIWorkerRequest"</span>))
                type = type.BaseType; 
 
            <span class="cs-keyword">if</span> (type != <span class="cs-keyword">null</span>)
            {
                type.GetField(<span class="cpp-string">"_contentAvailLength"</span>, 
                  bindingFlags).SetValue(request, textParts.Length); 
                type.GetField(<span class="cpp-string">"_contentTotalLength"</span>, 
                  bindingFlags).SetValue(request, textParts.Length);
                type.GetField(<span class="cpp-string">"_preloadedContent"</span>, 
                  bindingFlags).SetValue(request, textParts); 
                type.GetField(<span class="cpp-string">"_preloadedContentRead"</span>, 
                  bindingFlags).SetValue(request, <span class="cs-keyword">true</span>);
            }
        }
    }

The interception is fulfilled by extracting the HttpWorkerRequest in the GetWorkerRequest method. I have done some research on that code in this regard. It is quite powerful. You can probably do more research yourself to find out more.

I cannot used Session to store the percentage of upload because the Session object in the ASP.NET process has not been initialized. Instead, I use the Application object instead.

The core part of the method is worker.GetPreloadedEntityBody() and worker.ReadEntityBody(data, bufferSize). These two methods read the data from the client machine. After all the data is read, they are stored in the Memory Stream. The final step is to push the data into ISAPIWorkerRequest. These ideas are coming from the slick upload component in http://krystalware.com/blog/archive/2004/10/11/303.aspx. The slick upload even supports saving the uploading file contents in the disk, without consuming the .NET memory stream during upload. Worth to have a look. Instead of calling my component as a "File upload progress bar", I should call it "MIME upload progress bar".

The web config is configured in the following way -

&lt;configSections&gt;
        &lt;section name="UploadModuleManagement" 
         type="UploadModule.UploadModule, UploadModule" /&gt;
    &lt;/configSections&gt;
    &lt;UploadModuleManagement bufferSize="1024" 
      pages="fileupload/webform1.aspx" /&gt;

Where "fileupload/webform1.aspx" is your page that performs the upload. The buffersize is the number of bytes that is read from the client.

About benoityip

I have been in programming for some years. Though I am not a genius in programming, I am interested in it.
Other than computing, I am interested in buddism as well. It talks about true happiness in our mind, everything is creating from the mind, ouselves. I am the creator.

Click here to view benoityip's online profile.

抱歉!评论已关闭.