Skip to content

Instantly share code, notes, and snippets.

@savitskiyan
Last active November 11, 2015 07:00
Show Gist options
  • Save savitskiyan/4406c2e8ff2d73709f50 to your computer and use it in GitHub Desktop.
Save savitskiyan/4406c2e8ff2d73709f50 to your computer and use it in GitHub Desktop.
ASP.NET MVC 5 WebAPI support partial download file
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