asp.net web api 下载之断点续传

一、基本思想

利用 HTTP 请求的Range标头值,来向服务端传递请求数据的开始位置和结束位置。服务端获得这两个参数后,将指定范围内的数据传递给客户端。当客户端请求暂停或中断之后,待到客户端再次向服务器发起请求,继续下载数据时,客户端传递给服务端的Range值说明了向服务端请求数据的范围,即从上一次中断传输的位置开始直到最后。

二、示例代码

1 DownloadCore:完成下载任务

public class DownloadCore<T>
    {
        private HttpRequestMessage request;
        private IFileProvider<T> fileProvider;
        private T requestModel;
        public DownloadCore(HttpRequestMessage request,IFileProvider<T> fileProvider,T requestModel)
        {
            this.request = request;
            this.fileProvider = fileProvider;
            this.requestModel = requestModel;
        }
        public HttpResponseMessage Download()
        {
            if (requestModel == null)
            {
                //抛出异常
            }
            if (!fileProvider.Exists(requestModel))
            {
                //抛出异常            }
            long fileLength = fileProvider.GetLength(requestModel);
            if (fileLength == 0)
            {
                //抛出异常 
            }
            ContentInfo contentInfo = GetContentInfoFromRequest(fileLength);
            Stream stream = PartialStream.GetPartialStream(fileProvider.Open(requestModel), contentInfo.From, contentInfo.To);
            if (stream == null)
            {
                //抛出异常 
       }
            HttpContent content = new StreamContent(stream, AppSettings.BufferSize);
            return SetResponse(content, contentInfo, fileLength, fileProvider.GetFileName(requestModel));
        }
        private ContentInfo GetContentInfoFromRequest(long entityLength)
        {
            var result = new ContentInfo
            {
                From = 0,
                To = entityLength - 1,
                IsPartial = false,
                Length = entityLength
            };
            RangeHeaderValue rangeHeader = request.Headers.Range;
            if (rangeHeader != null && rangeHeader.Ranges.Count != 0)
            {
                //仅支持一个range
                if (rangeHeader.Ranges.Count > 1)
                {
                    //抛出异常 
                }
                RangeItemHeaderValue range = rangeHeader.Ranges.First();
                if (range.From.HasValue && range.From < 0 || range.To.HasValue && range.To > entityLength - 1)
                {
                    //抛出异常 
                }

                result.From = range.From ?? 0;
                result.To = range.To ?? entityLength - 1;
                result.IsPartial = true;
                result.Length = entityLength;

                if (range.From.HasValue && range.To.HasValue)
                {
                    result.Length = range.To.Value - range.From.Value + 1;
                }
                else if (range.From.HasValue)
                {
                    result.Length = entityLength - range.From.Value;
                }
                else if (range.To.HasValue)
                {
                    result.Length = range.To.Value + 1;
                }
            }
            return result;
        }

        private HttpResponseMessage SetResponse(HttpContent content, ContentInfo contentInfo, long entityLength, string fileNameWithExtend)
        {
            HttpResponseMessage response = new HttpResponseMessage() ;
            response.Headers.AcceptRanges.Add("bytes");
            response.StatusCode = contentInfo.IsPartial ? HttpStatusCode.PartialContent : HttpStatusCode.OK;
            response.Content = content;
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = fileNameWithExtend;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            response.Content.Headers.ContentLength = contentInfo.Length;
            if (contentInfo.IsPartial)
            {
                response.Content.Headers.ContentRange = new ContentRangeHeaderValue(contentInfo.From, contentInfo.To, entityLength);
            }
            return response;
        }
}

网络请求参数解析

public static DownloadUriModel GetDownloadParam(HttpRequestMessage request)
        {
            var uriQuery = request.RequestUri.Query;
            //针对中文重新编码
            uriQuery = HttpUtility.UrlDecode(uriQuery);

            var queryChildren = uriQuery.Substring(1, uriQuery.Length - 1).Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            Dictionary<string, string> queryDict = new Dictionary<string, string>();
            queryChildren.ForEach(item =>
            {
                var child = item.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
                queryDict.Add(child[0], child[1]);
            });          

            string fileName = queryDict.ContainsKey("fileName") ? queryDict["fileName"] : null;
            if (string.IsNullOrWhiteSpace(fileName))
            {
                //抛出异常             
       }
            string extend = Path.GetExtension(fileName);
            if (!string.IsNullOrWhiteSpace(extend))
            {
                fileName = Path.GetFileNameWithoutExtension(fileName);
            }

            string fileType = queryDict.ContainsKey("fileType") ? queryDict["fileType"] : null;
            if (string.IsNullOrWhiteSpace(fileType))
            {
                //抛出异常 
            }

            return new DownloadUriModel
            {
                FileNameNoExtend = fileName,
                FileType = fileType,                
            };
        }

部分流数据

public class PartialStream
    {
        public static Stream GetPartialStream(Stream fileStream, long start, long end)
        {
            if (fileStream == null)
            {
                return null;
            }
            if (start > 0)
            {
                fileStream.Seek(start, SeekOrigin.Begin);
            }

            return fileStream;
        }
}

2 数据

下载数据的来源包括本地磁盘,网络,数据库等,这里只列举待下载数据在本地磁盘和网络的情形。

本地磁盘

public class DiskFileProvider : IFileProvider<DownloadRequestModel>
    {
        /// <summary>
        /// android app 所在文件夹路径
        /// </summary>
        private readonly string filesDirectory;

        public DiskFileProvider()
        {
            filesDirectory = AppSettings.AppLocation;
        }

        public bool Exists(DownloadRequestModel model)
        {
            string searchPattern = string.Format("{0}.{1}",model.FileNameNoExtend, model.FileType);
            string file = Directory.GetFiles(filesDirectory, searchPattern, SearchOption.TopDirectoryOnly)
                    .FirstOrDefault();
            return file != null;
        }

        public Stream Open(DownloadRequestModel model)
        {
            return File.Open(GetFilePath(model), FileMode.Open, FileAccess.Read,FileShare.Delete);
        }

        public long GetLength(DownloadRequestModel model)
        {
            return new FileInfo(GetFilePath(model)).Length;
        }

        private string GetFilePath(DownloadRequestModel model)
        {
            string searchPattern = string.Format("{0}.{1}", model.FileNameNoExtend, model.FileType);
            return Path.Combine(filesDirectory, searchPattern);
        }


        public string GetFileName(DownloadRequestModel model)
        {
            return model.FileNameWithExtend;
        }
}

网络

public class NetFileProvider: IFileProvider<DownloadUriModel>
{
public bool Exists(DownloadUriModel model)
{
//具体实现
}


public Stream Open(DownloadUriModel model)
{
//具体实现
}

......
}

统一的接口

public interface IFileProvider<T>
{
        bool Exists(T model);
        Stream Open(T model);
        long GetLength(T model);
        string GetFileName(T model);
}

3 数据模型

public class DownloadRequestModel
{
        /// <summary>
        /// 文件名(不含扩展名)
        /// </summary>
        public string FileNameNoExtend { get; set; }
        /// <summary>
        /// 文件类型(扩展名)
        /// </summary>
        public string FileType { get; set; }
        /// <summary>
        /// 文件名
        /// </summary>
        public string FileNameWithExtend
        {
            get 
            {
                string extend = FileType.Contains(".") ? FileType.Substring(1) : FileType;
                return string.Format("{0}.{1}", FileNameNoExtend, extend);
            }
        }
        
}

//扩展实现
public class DownloadUriModel:DownloadRequestModel
{
......

}

public class ContentInfo
{
        public long From;
        public long To;
        public bool IsPartial;
        public long Length;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏坚毅的PHP

zookeeper学习系列:二、api实践

上一章我们知道zookeeper的简介,启动,设置节点以及结构性能。本小节我们来玩玩api,获取下数据。 php版本: http://anykoro.sinaa...

3954
来自专栏编码小白

tomcat请求处理分析(六)servlet的处理过程

1.1.1.1  servlet的解析过程 servlet的解析分为两步实现,第一个是匹配到对应的Wrapper,第二个是加载对应的servlet并进行数据,这...

7217
来自专栏逸鹏说道

我这么玩Web Api(二)

数据验证,全局数据验证与单元测试 目录 一、模型状态 - ModelState 二、数据注解 - Data Annotations 三、自定义数据注解 四、全局...

5386
来自专栏跟着阿笨一起玩NET

Winfrom 如何安全简单的跨线程更新控件

来源:http://www.cnblogs.com/rainbowzc/archive/2010/09/29/1838788.html

781
来自专栏大内老A

Enterprise Library深入解析与灵活应用(6):自己动手创建迷你版AOP框架

基于Enterprise Library PIAB的AOP框架已经在公司项目开发中得到广泛的使用,但是最近同事维护一个老的项目,使用到了Enterprise L...

2128
来自专栏菩提树下的杨过

silverlight动态读取txt文件/解析json数据/调用wcf示例

终于开始正式学习silverlight,虽然有点晚,但总算开始了,今天看了一下sdk,主要是想看下silverlight中如何动态调用数据,对于数据库的访问,s...

23910
来自专栏刘望舒

LeakCanary看这一篇文章就够了

LeakCanary是Square公司基于MAT开源的一个内存泄漏检测工具,在发生内存泄漏的时候LeakCanary会自动显示泄漏信息。

2.4K5
来自专栏瓜大三哥

Yaffs_guts(三)

1.垃圾回收 1.static int yaffs_InitialiseBlocks(yaffs_Device *dev,int nBlocks)//块初始化 ...

2255
来自专栏Flutter入门

Weex是如何在Android客户端上跑起来的

Weex可以通过自己设计的DSL,书写.we文件或者.vue文件来开发界面,整个页面书写分成了3段,template、style、script,借鉴了成熟的MV...

4625
来自专栏c#开发者

LightSwitch 2011 数据字段唯一性验证方案

LightSwitch 2011 数据字段唯一性验证方案 ? 验证单表数据的某个字段不能输入重复值 设置实体字段唯一索引 ? 如果不写代码,那么验证只会在...

3505

扫码关注云+社区

领取腾讯云代金券