Server Side File Upload Progress Bar

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.

public sealed class UploadModule : IHttpModule, IConfigurationSectionHandler
    {
        //the name of the configuration section the the web.config
        private const string configSection = "UploadModuleManagement";
        private const string bufferSizeKey = "uploadBufferSize";
        private const string pagesKey = "uploadPages";
        private string uploadKey;
        private string contentLengthKey;
        private ReaderWriterLock rwl;
        
        public void Dispose()
        {

        }
        
        //set the total size in the application object (not session object)
        for the progress bar page to keep track on the progress
        private object TotalSize
        {
            set
            {
                //must use a lock to update the application object 
                rwl.AcquireWriterLock(1000);
                try
                {
                    if (value==null)
                        HttpContext.Current.Application.Remove(
                          contentLengthKey);
                    HttpContext.Current.Application[contentLengthKey]=value;
                }
                finally
                {
                    rwl.ReleaseWriterLock();
                }
            }
        }

        //visible by the progress bar page
        public static object GetTotalSize(string guid)
        {
            ReaderWriterLock srwl = new ReaderWriterLock();
            try
            {
                srwl.AcquireReaderLock(1000);
                return HttpContext.Current.Application[guid + 
                  "uploadlength"];
            }
            finally
            {
                srwl.ReleaseReaderLock();
            }

        }

        //visible by the progress bar page
        public static object GetCurrentSize(string guid)
        {
            ReaderWriterLock srwl = new ReaderWriterLock();
            try
            {
                srwl.AcquireReaderLock(1000);
                return HttpContext.Current.Application[guid + 
                  "uploadprogress"];
            }
            finally
            {
                srwl.ReleaseReaderLock();
            }

        }

       //visible by the progress bar page
        private object CurrentSize
        {
            set
            {
                rwl.AcquireWriterLock(1000);
                try
                {
                    if (value==null)
                        HttpContext.Current.Application.Remove(uploadKey);
                    HttpContext.Current.Application[uploadKey] =value;
                }
                finally
                {
                    rwl.ReleaseWriterLock();
                }
            }
        }
        

        //read from config section
        public object Create(object parent,object configContext, 
            XmlNode section)
        {
            if (section != null) 
            {
                HttpContext.Current.Application[bufferSizeKey] = 
                  Int32.Parse(section.SelectSingleNode("@bufferSize").Value);
                HttpContext.Current.Application[pagesKey] = 
                  section.SelectSingleNode("@pages").Value.Split(',');
            }
            else
            {
                HttpContext.Current.Application[bufferSizeKey] = 1024;
                HttpContext.Current.Application[pagesKey] = new string[]{""};
            }
            return null;
        }

        //check whether the page that is processing 
        //is belonged to an "upload page". 
        private bool IsUploadPages()
        {
            HttpApplication app =  HttpContext.Current.ApplicationInstance;
            string [] uploadPages = (string [])app.Application[pagesKey];
            for (int i = 0; i<uploadPages.Length ;i++)
            {
                if ( uploadPages[i].ToLower() ==
                  app.Request.Path.Substring(1).ToLower())
                    return true;
            }
            return false;
        }

        public void Init(HttpApplication app)
        {
            ConfigurationSettings.GetConfig(configSection);
            app.BeginRequest += new EventHandler(context_BeginRequest);
            app.Error += new EventHandler(context_Error);
            app.EndRequest += new EventHandler(context_EndRequest);
        }

        private void context_BeginRequest(object sender, EventArgs e)
        {
            
            HttpApplication app = sender as HttpApplication;
            HttpWorkerRequest worker = GetWorkerRequest(app.Context);
            //get the querystring, must not use Reques.Params["guid"]
            uploadKey = app.Context.Request.QueryString["guid"] + 
              "uploadprogress";
            contentLengthKey = app.Context.Request.QueryString["guid"] + 
              "uploadlength";
            rwl = new ReaderWriterLock();

           //the number of bytes get from the client everytime  
           int bufferSize = (int)app.Application[bufferSizeKey];
            //check whether the page is an upload page
            if (IsUploadPages())
            {
                if (app.Context.Request.ContentLength > 0)
                {
                    TotalSize = app.Context.Request.ContentLength;
                    MemoryStream mem = new MemoryStream(
                      app.Context.Request.ContentLength);
                    //read the first portion of data from the client
                    byte [] data = worker.GetPreloadedEntityBody();
                    mem.Write(data,  0, data.Length);

                    int read = 0;
                    int counter = data.Length;
                    //keep reading if the first 
                    //read cannot read all the data        
                    while (counter < app.Context.Request.ContentLength)
                    {
                        if (counter + bufferSize > 
                          app.Context.Request.ContentLength)
                            bufferSize = app.Context.Request.ContentLength 
                            - counter;
                        data = new byte[bufferSize];
                        CurrentSize = counter;
                        read = worker.ReadEntityBody(data, bufferSize);
                        counter += read;
                        mem.Write(data,  0, bufferSize);
                    }
                
                    mem.Position = 0;
                    //push all the data to memory stream
                    byte [] memData  = new byte[mem.Length];
                    mem.Read(memData, 0, (int)mem.Length);
                    //finishing the interception, push all 
                    //the data to the worker process again
                    PushRequestToIIS(worker, memData);
                }
            }
        }

        private void context_EndRequest(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            string [] uploadPages = (string [])app.Application[pagesKey];
            //check whether the page is an upload page and the application 
            //object to null, so that the progress 
            //bar page knows the upload is finished
            if (IsUploadPages())
            {
                TotalSize  = null;
                CurrentSize = null;
            }
        }

        private void context_Error(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            string [] uploadPages = (string [])app.Application[pagesKey];
            //check whether the page is an upload page
            if (IsUploadPages())
            {
                TotalSize  = null;
                CurrentSize = null;
            }
        }


        HttpWorkerRequest GetWorkerRequest(HttpContext context)
        {
            IServiceProvider provider = (IServiceProvider)
                    HttpContext.Current;
            return (HttpWorkerRequest)provider.GetService(
                     typeof(HttpWorkerRequest));
        }

        private void PushRequestToIIS(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);
            }
        }
    }

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 -

<configSections>
        <section name="UploadModuleManagement" 
         type="UploadModule.UploadModule, UploadModule" />
    </configSections>
    <UploadModuleManagement bufferSize="1024" 
      pages="fileupload/webform1.aspx" />

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.

posted on 2005-11-20 19:50  cy163  阅读(1200)  评论(1编辑  收藏  举报

导航