ASP.NET Core斷點續傳
在ASP.NET WebAPi寫過完整的斷點續傳文章,目前我對ASP.NET Core僅止於總體上會用,對於原理還未去深刻學習,因爲有園友想看斷點續傳在ASP.NET Core中的具體實現,因而藉助在家中休息時間看了下ASP.NET Core是否支持斷點續傳以及支持後具體實現以及相關APi,花了一點時間,本文而由此而生。html
斷點續傳基礎
此前在ASP.NET WebAPi中對於一些基礎內容已經詳細講解過,同時也進行了封裝,因此再處理ASP.NET Core不過是APi使用不一樣罷了,斷點續傳重點在於AcceptRange和ContentRange以及對應響應請求頭設置,其他和ASP.NET WebAPi使用別無二致。git
在ASP.NET WebAPi中咱們封裝了對文件的操做接口IFileProvider和具體實現FileProvider,在控制器中咱們是直接實例化,在ASP.NET Core中有了依賴注入,咱們可直接藉助控制器構造函數注入接口。github
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddScoped<IFileProvider, FileProvider>(); }
固然前提是新建一個ASP.NET Core Web應用程序,而後咱們新建一個DownLoadController控制器。在ASP.NET WebAPi中對於請求-響應機制對象是HttpRequestMessage和HttpResponseMessage,而在ASP.NET Core則是HttpRequest和HttpResponse對象。那麼咱們在控制器中如何獲取這兩個對象中呢?若是咱們稍微有點經驗的話就能明瞭請求和響應對象必然存儲在上下文中,那麼咱們又如何獲取上下文呢?經過IHttpContextAccessor接口獲取。因此在控制器構造函數中獲取文件接口和上下文以及對應的常量以下:c#
private const int BufferSize = 80 * 1024; private const string MimeType = "application/octet-stream"; public IFileProvider _fileProvider { get; set; } private IHttpContextAccessor _contextAccessor; private HttpContext _context { get { return _contextAccessor.HttpContext; } } public FileDownloadController( IFileProvider fileProvider, IHttpContextAccessor contextAccessor) { _fileProvider = fileProvider; _contextAccessor = contextAccessor; }
在ASP.NET WebAPi中獲取請求頭Range利用請求中的Headers屬性獲取,但在ASP.NET Core中則須要經過請求中的GetTypedHeaders()方法獲取。windows
ASP.NET Core對於請求頭中參數的獲取和值的設置更加友好,好比咱們要獲取請求頭中的請求類型,在ASP.NET WebAPi中咱們指定字符串Request.Headers["Content-Type"],而在ASP.NET Core中則對應的是Request.Headers[HeaderNames.ContentType],直接經過枚舉指定,如此一來則省事多了。最終在DownLoadController控制器中對於在ASP.NET Core中斷點續傳的整個邏輯以下:api
public class FileDownloadController { private const int BufferSize = 80 * 1024; private const string MimeType = "application/octet-stream"; public IFileProvider _fileProvider { get; set; } private IHttpContextAccessor _contextAccessor; private HttpContext _context { get { return _contextAccessor.HttpContext; } } public FileDownloadController( IFileProvider fileProvider, IHttpContextAccessor contextAccessor) { _fileProvider = fileProvider; _contextAccessor = contextAccessor; } /// <summary> /// 下載文件 /// </summary> /// <param name="fileName"></param> /// <returns></returns> [HttpGet("api/download")] public IActionResult GetFile(string fileName) { fileName = "cn_windows_8_1_x64_dvd_2707237.iso"; if (!_fileProvider.Exists(fileName)) { return new StatusCodeResult(StatusCodes.Status404NotFound); } //獲取下載文件長度 var fileLength = _fileProvider.GetLength(fileName); //初始化下載文件信息 var fileInfo = GetFileInfoFromRequest(_context.Request, fileLength); //獲取剩餘部分文件流 var stream = new PartialContentFileStream(_fileProvider.Open(fileName), fileInfo.From, fileInfo.To); //設置響應 請求頭 SetResponseHeaders(_context.Response, fileInfo, fileLength, fileName); return new FileStreamResult(stream, new MediaTypeHeaderValue(MimeType)); } /// <summary> /// 根據請求信息賦予封裝的文件信息類 /// </summary> /// <param name="request"></param> /// <param name="entityLength"></param> /// <returns></returns> private FileInfo GetFileInfoFromRequest(HttpRequest request, long entityLength) { var fileInfo = new FileInfo { From = 0, To = entityLength - 1, IsPartial = false, Length = entityLength }; var requestHeaders = request.GetTypedHeaders(); if (requestHeaders.Range != null && requestHeaders.Range.Ranges.Count > 0) { var range = requestHeaders.Range.Ranges.FirstOrDefault(); if (range.From.HasValue && range.From < 0 || range.To.HasValue && range.To > entityLength - 1) { return null; } var start = range.From; var end = range.To; if (start.HasValue) { if (start.Value >= entityLength) { return null; } if (!end.HasValue || end.Value >= entityLength) { end = entityLength - 1; } } else { if (end.Value == 0) { return null; } var bytes = Math.Min(end.Value, entityLength); start = entityLength - bytes; end = start + bytes - 1; } fileInfo.IsPartial = true; fileInfo.Length = end.Value - start.Value + 1; } return fileInfo; } /// <summary> /// 設置響應頭信息 /// </summary> /// <param name="response"></param> /// <param name="fileInfo"></param> /// <param name="fileLength"></param> /// <param name="fileName"></param> private void SetResponseHeaders(HttpResponse response, FileInfo fileInfo, long fileLength, string fileName) { response.Headers[HeaderNames.AcceptRanges] = "bytes"; response.StatusCode = fileInfo.IsPartial ? StatusCodes.Status206PartialContent : StatusCodes.Status200OK; var contentDisposition = new ContentDispositionHeaderValue("attachment"); contentDisposition.SetHttpFileName(fileName); response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString(); response.Headers[HeaderNames.ContentType] = MimeType; response.Headers[HeaderNames.ContentLength] = fileInfo.Length.ToString(); if (fileInfo.IsPartial) { response.Headers[HeaderNames.ContentRange] = new ContentRangeHeaderValue(fileInfo.From, fileInfo.To, fileLength).ToString(); } } }
總結
ASP.NET Core中FileResult、FileStreamResult、FilePhsicalResult都已支持斷點續傳,若是對於很小的文件直接下載便可,若是稍微大一點文件則可利用斷點續傳便可,若是對於很是大的文件則須要自定義流來下載這樣更高效,好比對於獲取視頻流文件。上述咱們依然是採起自定義流的形式來實現斷點續傳,若對其中封裝的自定義流和接口有疑惑請移步右上角個人github參看ASP.NET WebAPi具體實現。不管是ASP.NET WebAPi和ASP.NET Core斷點續傳都實現了核心邏輯,對於一些細節未考慮其中,但願對想學習斷點續傳的您有所幫助,祝您閱讀愉快,新年快樂!完整代碼,我會上傳到github!markdown