欢迎访问个人博客站点: 配置啦

分享项目中在用的asp.net下载业务的服务端基类(支持客户端显示下载百分比进度,支持并发数控制,支持限速)

    /// <summary>
    /// 功能简介:asp.net的下载业务的服务端基类(支持客户端显示下载百分比进度,支持并发数控制,支持限速)
    /// 创建时间:2015-11-20
    /// 创建人:pcw
    /// 博客:https://www.cnblogs.com/taohuadaozhu
    /// 备注:如果针对大文件下载,则还需要考虑操作系统或iis上最大下载字节数限制。
    /// </summary>
    public abstract class DownLoadAbs : IHttpHandler
    {
        private static StatusDataDict currStatuDataDict = new StatusDataDict(300);
        protected object lockObj = new object();
        public virtual void ProcessRequest(HttpContext context)
        {
            string sDiplayFileName = this.GetDisplayFileName(context);
            string sServerFileFullPath = this.GetServerFileFullPath(context);
            int iDownload = 0;
            iDownload = this.ResponseFile(context.Request, context.Response, sDiplayFileName, sServerFileFullPath, this.BytesCountPerSecond);
            if (iDownload != 1)
            {
                Utils.SaveErrorLog(string.Format("下载文件【{0}】失败(2015-12-15v1),返回值={1}", sServerFileFullPath, iDownload));
                if (iDownload == -202)
                {
                    context.Response.Write(RuntimeContext.GetResponseJson("系统检测到重复的并发下载请求,请稍后再点击下载", -1, null));
                }
                else if (iDownload == -203)
                {
                    context.Response.Write(RuntimeContext.GetResponseJson("并发下载人数超过最大连接数,请稍后再点击下载", -2, null));
                }
                context.Response.End();
            }
        }
        protected abstract string GetDisplayFileName(HttpContext hcontext);
        protected abstract string GetServerFileFullPath(HttpContext hcontext);

        protected virtual int GetMaxConnectCount()
        {
            return 3;
        }


        protected virtual long BytesCountPerSecond
        {
            get
            {
                return 1024000;
            }
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
        /// <summary>
        /// 输入参数 _Request: Page.Request对象,  _Response: Page.Response对象, _fileName: 下载文件名, _fullPath: 带文件名下载路径, _speed 每秒允许下载的字节数(默认:1024000 B,类似1M/秒)
        /// </summary>
        /// <param name="_Request"></param>
        /// <param name="_Response"></param>
        /// <param name="_displayFileName"></param>
        /// <param name="_serverFilefullPath"></param>
        /// <param name="_speed"></param>
        /// <returns></returns>
        protected int ResponseFile(HttpRequest _Request, HttpResponse _Response, string _displayFileName, string _serverFilefullPath, long _speed)
        {
            return this.ResponseForDownloadFile(_Request, _Response, _displayFileName, _serverFilefullPath, _speed);
        }
        /// <summary>
        /// 输入参数 _Request: Page.Request对象,  _Response: Page.Response对象, _fileName: 下载文件名, _fullPath: 带文件名下载路径, _speed 每秒允许下载的字节数(默认:1024000 B,类似1M/秒)
        /// </summary>
        /// <param name="_Request"></param>
        /// <param name="_Response"></param>
        /// <param name="_displayFileName"></param>
        /// <param name="_serverFilefullPath"></param>
        /// <param name="_speed"></param>
        /// <returns></returns>
        protected virtual int ResponseForDownloadFile(HttpRequest _Request, HttpResponse _Response, string _displayFileName, string _serverFilefullPath, long _speed)
        {
            bool bSuccess = true;
            if (string.IsNullOrEmpty(_serverFilefullPath))
                return -101;
            if (string.IsNullOrEmpty(_displayFileName))
                return -102;
            if (_speed < 1)
                return -103;
            if (_Request == null)
                return -104;
            if (_Response == null)
                return -105;
            if (File.Exists(_serverFilefullPath) == false)
                return -201;
            if (currStatuDataDict.ExistsStatus(_serverFilefullPath))
            {
                return -202;
            }
            if (currStatuDataDict.GetStatusCount() >= this.GetMaxConnectCount())
            {
                return -203;
            }
            currStatuDataDict.AddStatusData(_serverFilefullPath);
            FileStream targetFile = new FileStream(_serverFilefullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            BinaryReader br = new BinaryReader(targetFile);
            try
            {
                _Response.AddHeader("Accept-Ranges", "bytes");
                _Response.Buffer = false;
                long fileTotalLength = targetFile.Length;
                long startBytes = 0;
                int packForBlock = 10240; //10K bytes
                //int sleep = 200;   //每秒5次   即5*10K bytes每秒
                decimal dSleep = Convert.ToDecimal(1000 * packForBlock / _speed);
                decimal dMaxCount = 0;
                int sleep = (int)Math.Floor(dSleep) + 1;
                if (_Request.Headers["Range"] != null) //这里是客户端返回来的,已下载的进度
                {
                    _Response.StatusCode = 206;
                    string[] range = _Request.Headers["Range"].Split(new char[] { '=', '-' });
                    startBytes = Convert.ToInt64(range[1]);
                }
                _Response.AddHeader("Content-Length", (fileTotalLength - startBytes).ToString());//这是此次下载文件的总字节长度
                if (startBytes != 0)//如果客户端支持,否则不会添加进度相关的信息
                {
                    _Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileTotalLength - 1, fileTotalLength));//这是本次下载后重新定位的进度
                }
                /*
                _Response.AddHeader("Connection", "Keep-Alive");
                _Response.AddHeader("Keep-Alive", "timeout=600, max=4");
                 */
                _Response.ContentType = "application/octet-stream";
                _Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(_displayFileName, System.Text.Encoding.UTF8));
                br.BaseStream.Seek(startBytes, SeekOrigin.Begin);
                dMaxCount = (fileTotalLength - startBytes) / packForBlock;
                int maxCount = (int)Math.Floor(dMaxCount) + 1;
                byte[] bytesRead = new byte[packForBlock];
                for (int i = 0; i < maxCount; i++)
                {
                    if (_Response != null && _Response.IsClientConnected)
                    {
                        if (File.Exists(_serverFilefullPath))
                        {
                            bytesRead = br.ReadBytes(packForBlock);
                            if (bytesRead != null)
                            {
                                _Response.BinaryWrite(bytesRead);
                                //_Response.Flush();//add by pcw 
                                Thread.Sleep(sleep);//需要注意响应的最大时间设置
                            }
                        }
                    }
                    else
                    {
                        i = maxCount;
                    }
                }
            }
            catch (Exception error)
            {
                bSuccess = false;
                Utils.SaveErrorLog(string
.Format("输出文件【{0}】的文件流过程出现异常:{1},调试信息:{2}", _serverFilefullPath, error.Message, error.StackTrace));
            }
            finally
            {
                currStatuDataDict.RemoveStatuData(_serverFilefullPath);
                if (br != null)
                {
                    br.Close();
                    br.Dispose();
                    br = null;
                }
                if (targetFile != null)
                {
                    targetFile.Close();
                    targetFile.Dispose();
                    targetFile = null;
                }
                if (_Response != null)
                {
                    if (bSuccess)
                    {
                        Utils.SaveLog(string.Format("已成功提供客户端下载文件【{0}】", _serverFilefullPath));
                    }
                    //_Response.End();
                    HttpContext.Current.Response.SuppressContent = true;  // Gets or sets a value indicating whether to send HTTP content to the client.
                    HttpContext.Current.ApplicationInstance.CompleteRequest(); // Causes ASP.NET to bypass all events and filtering in the HTTP pipeline chain of execution and directly execute the EndRequest event.
                }
            }
            return 1;
        }
    }