扩展方法,改编自http://blogs.visigo.com/chriscoulson/easy-handling-of-http-range-requests-in-asp-net/
/// <summary> /// 断点下载 /// </summary> /// <param name="controller"></param> /// <param name="fullpath"></param> /// <returns></returns> public static async Task<long> RangeDownload(this Controller controller, string fullpath) { long size, start, end, length, fp = 0; using (StreamReader reader = new StreamReader(File.OpenRead(fullpath))) { size = reader.BaseStream.Length; start = 0; end = size - 1; length = size; // Now that we've gotten so far without errors we send the accept range header /* At the moment we only support single ranges. * Multiple ranges requires some more work to ensure it works correctly * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 * * Multirange support annouces itself with: * header('Accept-Ranges: bytes'); * * Multirange content must be sent with multipart/byteranges mediatype, * (mediatype = mimetype) * as well as a boundry header to indicate the various chunks of data. */ controller.Response.Headers.Add("Accept-Ranges", "0-" + size); // header('Accept-Ranges: bytes'); // multipart/byteranges // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 if (!String.IsNullOrEmpty(controller.Request.Headers["HTTP_RANGE"])) { long anotherStart = start; long anotherEnd = end; string[] arr_split = controller.Request.Headers["HTTP_RANGE"].FirstOrDefault().Split(new char[] { Convert.ToChar("=") }); string range = arr_split[1]; // Make sure the client hasn't sent us a multibyte range if (range.IndexOf(",") > -1) { // (?) Shoud this be issued here, or should the first // range be used? Or should the header be ignored and // we output the whole content? controller.Response.Headers.Add("Content-Range", "bytes " + start + "-" + end + "/" + size); controller.Response.StatusCode = 416; controller.Response.StatusCode = 416; await controller.Response.WriteAsync("Requested Range Not Satisfiable"); } // If the range starts with an '-' we start from the beginning // If not, we forward the file pointer // And make sure to get the end byte if spesified if (range.StartsWith("-")) { // The n-number of the last bytes is requested anotherStart = size - Convert.ToInt64(range.Substring(1)); } else { arr_split = range.Split(new char[] { Convert.ToChar("-") }); anotherStart = Convert.ToInt64(arr_split[0]); long temp = 0; anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : size; } /* Check the range and make sure it's treated according to the specs. * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html */ // End bytes can not be larger than $end. anotherEnd = (anotherEnd > end) ? end : anotherEnd; // Validate the requested range and return an error if it's not correct. if (anotherStart > anotherEnd || anotherStart > size - 1 || anotherEnd >= size) { controller.Response.Headers.Add("Content-Range", "bytes " + start + "-" + end + "/" + size); controller.Response.StatusCode = 416; await controller.Response.WriteAsync( "Requested Range Not Satisfiable"); } start = anotherStart; end = anotherEnd; length = end - start + 1; // Calculate new content length fp = reader.BaseStream.Seek(start, SeekOrigin.Begin); controller.Response.StatusCode = 206; } } // Notify the client the byte range we'll be outputting controller.Response.Headers.Add("Content-Range", "bytes " + start + "-" + end + "/" + size); controller.Response.Headers.Add("Content-Length", length.ToString()); // Start buffered download await controller.Response.SendFileAsync(fullpath, fp, length); return fp; }
控制器方法内调用样例:
/// <summary> /// 下载指定版本 /// </summary> /// <param name="id"></param> /// <param name="version"></param> /// <returns></returns> public async Task Download(Guid id, int version) { string path = null; var state = appService.GetAppState(id, version); if (state == Model.AppState.Available) { path = appService.GetZipFromAppList(id, version); if (!System.IO.File.Exists(path)) { await this.StatusCode(404).ExecuteResultAsync(this.ControllerContext); } var offset = await this.RangeDownload(path); if (offset == 0) { appService.IncreaseNDownload(state, id, version); } } else { await this.StatusCode(404).ExecuteResultAsync(this.ControllerContext); } }