From 04e9da4e88c3dec941061ca311a5d6ed51e5e27e Mon Sep 17 00:00:00 2001 From: Chris R Date: Fri, 5 Feb 2016 09:38:15 -0800 Subject: [PATCH] #527 Add IFileInfo overloads for SendFileAsync. --- .../SendFileResponseExtensions.cs | 98 ++++++++++++++++--- .../StreamCopyOperation.cs | 12 +-- .../project.json | 1 + .../IHttpSendFileFeature.cs | 2 +- 4 files changed, 90 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.AspNetCore.Http.Extensions/SendFileResponseExtensions.cs b/src/Microsoft.AspNetCore.Http.Extensions/SendFileResponseExtensions.cs index 6058eb9a..c747df18 100644 --- a/src/Microsoft.AspNetCore.Http.Extensions/SendFileResponseExtensions.cs +++ b/src/Microsoft.AspNetCore.Http.Extensions/SendFileResponseExtensions.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Http { @@ -15,13 +16,73 @@ namespace Microsoft.AspNetCore.Http /// public static class SendFileResponseExtensions { + /// + /// Sends the given file using the SendFile extension. + /// + /// + /// The file. + public static Task SendFileAsync(this HttpResponse response, IFileInfo file, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (response == null) + { + throw new ArgumentNullException(nameof(response)); + } + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + + return response.SendFileAsync(file, 0, null, cancellationToken); + } + + /// + /// Sends the given file using the SendFile extension. + /// + /// + /// The file. + /// The offset in the file. + /// The number of bytes to send, or null to send the remainder of the file. + /// + /// + public static async Task SendFileAsync(this HttpResponse response, IFileInfo file, long offset, long? count, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (response == null) + { + throw new ArgumentNullException(nameof(response)); + } + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + CheckRange(offset, count, file.Length); + + if (string.IsNullOrEmpty(file.PhysicalPath)) + { + using (var fileContent = file.CreateReadStream()) + { + if (offset > 0) + { + fileContent.Seek(offset, SeekOrigin.Begin); + } + await StreamCopyOperation.CopyToAsync(fileContent, response.Body, count, cancellationToken); + } + } + else + { + await response.SendFileAsync(file.PhysicalPath, offset, count, cancellationToken); + } + } + /// /// Sends the given file using the SendFile extension. /// /// /// The full path to the file. /// - public static Task SendFileAsync(this HttpResponse response, string fileName) + public static Task SendFileAsync(this HttpResponse response, string fileName, + CancellationToken cancellationToken = default(CancellationToken)) { if (response == null) { @@ -33,7 +94,7 @@ public static Task SendFileAsync(this HttpResponse response, string fileName) throw new ArgumentNullException(nameof(fileName)); } - return response.SendFileAsync(fileName, 0, null, CancellationToken.None); + return response.SendFileAsync(fileName, 0, null, cancellationToken); } /// @@ -42,10 +103,11 @@ public static Task SendFileAsync(this HttpResponse response, string fileName) /// /// The full path to the file. /// The offset in the file. - /// The number of types to send, or null to send the remainder of the file. + /// The number of bytes to send, or null to send the remainder of the file. /// /// - public static Task SendFileAsync(this HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken) + public static Task SendFileAsync(this HttpResponse response, string fileName, long offset, long? count, + CancellationToken cancellationToken = default(CancellationToken)) { if (response == null) { @@ -67,21 +129,13 @@ public static Task SendFileAsync(this HttpResponse response, string fileName, lo } // Not safe for overlapped writes. - private static async Task SendFileAsync(Stream outputStream, string fileName, long offset, long? length, CancellationToken cancel) + private static async Task SendFileAsync(Stream outputStream, string fileName, long offset, long? count, + CancellationToken cancel = default(CancellationToken)) { cancel.ThrowIfCancellationRequested(); var fileInfo = new FileInfo(fileName); - if (offset < 0 || offset > fileInfo.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty); - } - - if (length.HasValue && - (length.Value < 0 || length.Value > fileInfo.Length - offset)) - { - throw new ArgumentOutOfRangeException(nameof(length), length, string.Empty); - } + CheckRange(offset, count, fileInfo.Length); int bufferSize = 1024 * 16; @@ -96,8 +150,20 @@ private static async Task SendFileAsync(Stream outputStream, string fileName, lo using (fileStream) { fileStream.Seek(offset, SeekOrigin.Begin); + await StreamCopyOperation.CopyToAsync(fileStream, outputStream, count, cancel); + } + } - await StreamCopyOperation.CopyToAsync(fileStream, outputStream, length, cancel); + private static void CheckRange(long offset, long? count, long fileLength) + { + if (offset < 0 || offset > fileLength) + { + throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty); + } + if (count.HasValue && + (count.Value < 0 || count.Value > fileLength - offset)) + { + throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty); } } } diff --git a/src/Microsoft.AspNetCore.Http.Extensions/StreamCopyOperation.cs b/src/Microsoft.AspNetCore.Http.Extensions/StreamCopyOperation.cs index c42661a1..ef81ac3b 100644 --- a/src/Microsoft.AspNetCore.Http.Extensions/StreamCopyOperation.cs +++ b/src/Microsoft.AspNetCore.Http.Extensions/StreamCopyOperation.cs @@ -15,9 +15,9 @@ public static class StreamCopyOperation { private const int DefaultBufferSize = 4096; - public static async Task CopyToAsync(Stream source, Stream destination, long? length, CancellationToken cancel) + public static async Task CopyToAsync(Stream source, Stream destination, long? count, CancellationToken cancel) { - long? bytesRemaining = length; + long? bytesRemaining = count; var buffer = ArrayPool.Shared.Rent(DefaultBufferSize); try @@ -42,22 +42,22 @@ public static async Task CopyToAsync(Stream source, Stream destination, long? le { readLength = (int)Math.Min(bytesRemaining.Value, (long)readLength); } - int count = await source.ReadAsync(buffer, 0, readLength, cancel); + int read = await source.ReadAsync(buffer, 0, readLength, cancel); if (bytesRemaining.HasValue) { - bytesRemaining -= count; + bytesRemaining -= read; } // End of the source stream. - if (count == 0) + if (read == 0) { return; } cancel.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, count, cancel); + await destination.WriteAsync(buffer, 0, read, cancel); } } finally diff --git a/src/Microsoft.AspNetCore.Http.Extensions/project.json b/src/Microsoft.AspNetCore.Http.Extensions/project.json index f38f4a0f..a6412493 100644 --- a/src/Microsoft.AspNetCore.Http.Extensions/project.json +++ b/src/Microsoft.AspNetCore.Http.Extensions/project.json @@ -11,6 +11,7 @@ }, "dependencies": { "Microsoft.AspNetCore.Http.Abstractions": "1.0.0-*", + "Microsoft.Extensions.FileProviders.Abstractions": "1.0.0-*", "Microsoft.Net.Http.Headers": "1.0.0-*", "System.Buffers": "4.0.0-*" }, diff --git a/src/Microsoft.AspNetCore.Http.Features/IHttpSendFileFeature.cs b/src/Microsoft.AspNetCore.Http.Features/IHttpSendFileFeature.cs index 0776034e..9418a7ff 100644 --- a/src/Microsoft.AspNetCore.Http.Features/IHttpSendFileFeature.cs +++ b/src/Microsoft.AspNetCore.Http.Features/IHttpSendFileFeature.cs @@ -8,6 +8,6 @@ namespace Microsoft.AspNetCore.Http.Features { public interface IHttpSendFileFeature { - Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation); + Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation); } } \ No newline at end of file