Last active
November 11, 2015 07:03
-
-
Save savitskiyan/7bffe67a64c238895172 to your computer and use it in GitHub Desktop.
ASP.NET MVC 6 WebAPI support partial download file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http.Headers; | |
using System.Threading.Tasks; | |
using Microsoft.AspNet.Mvc; | |
namespace Server.Controllers | |
{ | |
public sealed class FileActionResult : IActionResult | |
{ | |
public const int BufferSize = ushort.MaxValue; | |
private readonly string _filename; | |
private readonly string _contentType; | |
public FileActionResult(string filename, string contentType) | |
{ | |
if (filename == null) | |
throw new ArgumentNullException(nameof(filename), "Не задано имя файла"); | |
if (contentType == null) | |
throw new ArgumentNullException(nameof(contentType), "Не задан тип контента"); | |
_filename = filename; | |
_contentType = contentType; | |
} | |
private static async Task CreatePartialContent(Stream input, Stream output, long start, long end) | |
{ | |
var remainingBytes = end - start + 1; | |
long position; | |
var buffer = new byte[BufferSize]; | |
input.Position = start; | |
do | |
{ | |
try | |
{ | |
var count = remainingBytes > BufferSize | |
? input.Read(buffer, 0, BufferSize) | |
: input.Read(buffer, 0, (int) remainingBytes); | |
await output.WriteAsync(buffer, 0, count); | |
} | |
catch | |
{ | |
break; | |
} | |
position = input.Position; | |
remainingBytes = end - position + 1; | |
} while (position <= end); | |
} | |
private static bool TryReadRangeItem(RangeItemHeaderValue range, long contentLength, out long start, out long end) | |
{ | |
if (range.From != null) | |
{ | |
start = range.From.Value; | |
end = range.To ?? contentLength - 1; | |
} | |
else | |
{ | |
end = contentLength - 1; | |
start = contentLength - range.To ?? 0; | |
} | |
return (start < contentLength && end < contentLength); | |
} | |
public async Task ExecuteResultAsync(ActionContext context) | |
{ | |
var fileInfo = new FileInfo(_filename); | |
var request = context.HttpContext.Request; | |
var response = context.HttpContext.Response; | |
response.ContentType = _contentType; | |
if (!fileInfo.Exists) | |
{ | |
response.StatusCode = (int) HttpStatusCode.NotFound; | |
} | |
else | |
{ | |
var header = request.Headers["Range"].FirstOrDefault(); | |
RangeHeaderValue rangeHeader = null; | |
if (header != null) | |
rangeHeader = RangeHeaderValue.Parse(header); | |
long start, end; | |
response.Headers["Accept-Ranges"] = "bytes"; | |
// The request will be treated as normal request if there is no Range header. | |
if (rangeHeader == null || !rangeHeader.Ranges.Any()) | |
{ | |
response.ContentLength = fileInfo.Length; | |
response.StatusCode = (int)HttpStatusCode.OK; | |
try | |
{ | |
using (var input = GetFileStream(fileInfo.FullName)) | |
await input.CopyToAsync(response.Body, 4096); | |
} | |
catch | |
{ | |
} | |
} | |
else if (rangeHeader.Unit != "bytes" || rangeHeader.Ranges.Count > 1 || !TryReadRangeItem(rangeHeader.Ranges.First(), fileInfo.Length, out start, out end)) | |
{ | |
// 1. If the unit is not 'bytes'. | |
// 2. If there are multiple ranges in header value. | |
// 3. If start or end position is greater than file length. | |
response.Headers["Content-Range"] = new ContentRangeHeaderValue(0, fileInfo.Length).ToString(); | |
response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; | |
} | |
else | |
{ | |
// We are now ready to produce partial content. | |
response.ContentLength = end - start + 1; | |
response.Headers["Content-Range"] = new ContentRangeHeaderValue(start, end, fileInfo.Length).ToString(); | |
response.StatusCode = (int)HttpStatusCode.PartialContent; | |
try | |
{ | |
using (var input = GetFileStream(fileInfo.FullName)) | |
await CreatePartialContent(input, response.Body, start, end); | |
} | |
catch | |
{ | |
} | |
} | |
} | |
} | |
private static Stream GetFileStream(string path) | |
{ | |
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment