Last active
November 11, 2015 07:00
-
-
Save savitskiyan/4406c2e8ff2d73709f50 to your computer and use it in GitHub Desktop.
ASP.NET MVC 5 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; | |
using System.Net.Http.Headers; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using System.Web.Http; | |
namespace Server.Controllers | |
{ | |
public sealed class FileActionResult : IHttpActionResult | |
{ | |
public const int BufferSize = ushort.MaxValue; | |
private readonly HttpRequestMessage _request; | |
private readonly string _filename; | |
private readonly string _contentType; | |
public FileActionResult(HttpRequestMessage request, string filename, string contentType) | |
{ | |
if (request == null) | |
throw new ArgumentNullException(nameof(request), "Íå çàäàí çàïðîñ"); | |
if (filename == null) | |
throw new ArgumentNullException(nameof(filename), "Íå çàäàíî èìÿ ôàéëà"); | |
if (contentType == null) | |
throw new ArgumentNullException(nameof(contentType), "Íå çàäàí òèï êîíòåíòà"); | |
_request = request; | |
_filename = filename; | |
_contentType = contentType; | |
} | |
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) | |
{ | |
return Task.FromResult(Execute()); | |
} | |
public HttpResponseMessage Execute() | |
{ | |
var fileInfo = new FileInfo(_filename); | |
if (!fileInfo.Exists) | |
throw new HttpResponseException(HttpStatusCode.NotFound); | |
var totalLength = fileInfo.Length; | |
var rangeHeader = _request.Headers.Range; | |
long start, end; | |
var response = new HttpResponseMessage(); | |
response.Headers.AcceptRanges.Add("bytes"); | |
// The request will be treated as normal request if there is no Range header. | |
if (rangeHeader == null || !rangeHeader.Ranges.Any()) | |
{ | |
response.StatusCode = HttpStatusCode.OK; | |
response.Content = new PushStreamContent((output, httpContent, transpContext) => | |
{ | |
using (output) // Copy the file to output stream straightforward. | |
using (Stream input = fileInfo.OpenRead()) | |
{ | |
try | |
{ | |
input.CopyTo(output, BufferSize); | |
} | |
catch //(Exception error) | |
{ | |
// Trace.WriteLine(error.); | |
} | |
} | |
}, new MediaTypeHeaderValue(_contentType)); | |
response.Content.Headers.ContentLength = totalLength; | |
} | |
else if (rangeHeader.Unit != "bytes" || rangeHeader.Ranges.Count > 1 || | |
!TryReadRangeItem(rangeHeader.Ranges.First(), totalLength, 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.StatusCode = HttpStatusCode.RequestedRangeNotSatisfiable; | |
response.Content = new StreamContent(Stream.Null); // No content for this status. | |
response.Content.Headers.ContentRange = new ContentRangeHeaderValue(totalLength); | |
response.Content.Headers.ContentType = new MediaTypeHeaderValue(_contentType); | |
} | |
else | |
{ | |
// We are now ready to produce partial content. | |
response.StatusCode = HttpStatusCode.PartialContent; | |
response.Content = new PushStreamContent((output, httpContent, transpContext) => | |
{ | |
using (output) // Copy the file to output stream in indicated range. | |
using (Stream input = fileInfo.OpenRead()) | |
CreatePartialContent(input, output, start, end); | |
}, new MediaTypeHeaderValue(_contentType)); | |
response.Content.Headers.ContentLength = end - start + 1; | |
response.Content.Headers.ContentRange = new ContentRangeHeaderValue(start, end, totalLength); | |
} | |
return response; | |
} | |
private static void 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); | |
output.Write(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); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment