Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit d8b2017

Browse files
committed
Moved over SendFileAsync as abstract method on HttpResponse
1 parent 765a520 commit d8b2017

File tree

7 files changed

+143
-125
lines changed

7 files changed

+143
-125
lines changed

src/Microsoft.AspNetCore.Http.Abstractions/HttpResponse.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using System;
55
using System.IO;
6+
using System.Threading;
67
using System.Threading.Tasks;
8+
using Microsoft.Extensions.FileProviders;
79

810
namespace Microsoft.AspNetCore.Http
911
{
@@ -103,5 +105,23 @@ public abstract class HttpResponse
103105
/// <param name="location">The URL to redirect the client to.</param>
104106
/// <param name="permanent"><c>True</c> if the redirect is permanent (301), otherwise <c>false</c> (302).</param>
105107
public abstract void Redirect(string location, bool permanent);
108+
109+
/// <summary>
110+
/// Sends the given file using the SendFile extension.
111+
/// </summary>
112+
/// <param name="file">The file to send.</param>
113+
/// <param name="cancellationToken"></param>
114+
/// <returns></returns>
115+
public abstract Task SendFileAsync(IFileInfo file, CancellationToken cancellationToken = default(CancellationToken));
116+
117+
/// <summary>
118+
/// Sends the given file using the SendFile extension.
119+
/// </summary>
120+
/// <param name="file">The file to send.</param>
121+
/// <param name="offset">The offset in the file.</param>
122+
/// <param name="count">The number of types to send, or null to send the remainder of the file.</param>
123+
/// <param name="cancellationToken"></param>
124+
/// <returns></returns>
125+
public abstract Task SendFileAsync(IFileInfo file, long offset, long? count, CancellationToken cancellationToken = default(CancellationToken));
106126
}
107127
}

src/Microsoft.AspNetCore.Http.Extensions/StreamCopyOperation.cs renamed to src/Microsoft.AspNetCore.Http.Abstractions/StreamCopyOperation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
using System.Threading;
99
using System.Threading.Tasks;
1010

11-
namespace Microsoft.AspNetCore.Http.Extensions
11+
namespace Microsoft.AspNetCore.Http
1212
{
1313
// FYI: In most cases the source will be a FileStream and the destination will be to the network.
1414
public static class StreamCopyOperation

src/Microsoft.AspNetCore.Http.Abstractions/project.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
},
1212
"dependencies": {
1313
"Microsoft.AspNetCore.Http.Features": "1.0.0-*",
14+
"Microsoft.Extensions.FileProviders.Abstractions": "1.0.0-*",
1415
"Microsoft.Extensions.ActivatorUtilities.Sources": {
1516
"type": "build",
1617
"version": "1.0.0-*"
1718
},
18-
"System.Text.Encodings.Web": "4.0.0-*"
19+
"System.Text.Encodings.Web": "4.0.0-*",
20+
"System.Buffers": "4.0.0-*"
1921
},
2022
"frameworks": {
2123
"net451": {
@@ -32,7 +34,8 @@
3234
"System.Net.WebSockets": "4.0.0-*",
3335
"System.Reflection.TypeExtensions": "4.1.0-*",
3436
"System.Runtime.InteropServices": "4.0.21-*",
35-
"System.Security.Cryptography.X509Certificates": "4.0.0-*"
37+
"System.Security.Cryptography.X509Certificates": "4.0.0-*",
38+
"System.IO.FileSystem": "4.0.1-*"
3639
}
3740
}
3841
}

src/Microsoft.AspNetCore.Http.Extensions/SendFileResponseExtensions.cs

Lines changed: 0 additions & 104 deletions
This file was deleted.

src/Microsoft.AspNetCore.Http.Extensions/project.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,10 @@
1111
},
1212
"dependencies": {
1313
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0-*",
14-
"Microsoft.Net.Http.Headers": "1.0.0-*",
15-
"System.Buffers": "4.0.0-*"
14+
"Microsoft.Net.Http.Headers": "1.0.0-*"
1615
},
1716
"frameworks": {
1817
"net451": {},
19-
"dotnet5.4": {
20-
"dependencies": {
21-
"System.IO.FileSystem": "4.0.1-*"
22-
}
23-
}
18+
"dotnet5.4": { }
2419
}
2520
}

src/Microsoft.AspNetCore.Http/DefaultHttpResponse.cs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
using System;
55
using System.IO;
6+
using System.Threading;
67
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Http.Features;
89
using Microsoft.AspNetCore.Http.Features.Internal;
10+
using Microsoft.Extensions.FileProviders;
911
using Microsoft.Net.Http.Headers;
1012

1113
namespace Microsoft.AspNetCore.Http.Internal
@@ -37,7 +39,6 @@ public virtual void Uninitialize()
3739

3840
private IResponseCookiesFeature ResponseCookiesFeature =>
3941
_features.Fetch(ref _features.Cache.Cookies, f => new ResponseCookiesFeature(f));
40-
4142

4243
public override HttpContext HttpContext { get { return _context; } }
4344

@@ -133,6 +134,91 @@ public override void Redirect(string location, bool permanent)
133134
Headers[HeaderNames.Location] = location;
134135
}
135136

137+
public override Task SendFileAsync(IFileInfo file, CancellationToken cancellationToken = default(CancellationToken))
138+
{
139+
if (file == null)
140+
{
141+
throw new ArgumentNullException(nameof(file));
142+
}
143+
144+
return SendFileAsync(file, 0, null, cancellationToken);
145+
}
146+
147+
/// <summary>
148+
/// Sends the given file using the SendFile extension.
149+
/// </summary>
150+
/// <param name="file">The file to send.</param>
151+
/// <param name="offset">The offset in the file.</param>
152+
/// <param name="count">The number of types to send, or null to send the remainder of the file.</param>
153+
/// <param name="cancellationToken"></param>
154+
/// <returns></returns>
155+
public override async Task SendFileAsync(IFileInfo file, long offset, long? count, CancellationToken cancellationToken = default(CancellationToken))
156+
{
157+
if (file == null)
158+
{
159+
throw new ArgumentNullException(nameof(file));
160+
}
161+
162+
if (string.IsNullOrEmpty(file.PhysicalPath))
163+
{
164+
using (var readStream = file.CreateReadStream())
165+
{
166+
await SendFileAsync(Body, readStream, offset, count, cancellationToken);
167+
return;
168+
}
169+
}
170+
171+
var sendFile = HttpContext.Features.Get<IHttpSendFileFeature>();
172+
173+
if (sendFile == null)
174+
{
175+
await SendFileAsync(Body, file.PhysicalPath, offset, count, cancellationToken);
176+
return;
177+
}
178+
179+
await sendFile.SendFileAsync(file.PhysicalPath, offset, count, cancellationToken);
180+
}
181+
182+
private static async Task SendFileAsync(Stream outputStream, string fileName, long offset, long? count, CancellationToken cancellationToken)
183+
{
184+
int bufferSize = 1024 * 16;
185+
186+
var fileStream = new FileStream(
187+
fileName,
188+
FileMode.Open,
189+
FileAccess.Read,
190+
FileShare.ReadWrite,
191+
bufferSize: bufferSize,
192+
options: FileOptions.Asynchronous | FileOptions.SequentialScan);
193+
194+
using (fileStream)
195+
{
196+
await SendFileAsync(outputStream, fileStream, offset, count, cancellationToken);
197+
}
198+
}
199+
200+
// Not safe for overlapped writes.
201+
private static async Task SendFileAsync(Stream outputStream, Stream readStream, long offset, long? length, CancellationToken cancellationToken)
202+
{
203+
cancellationToken.ThrowIfCancellationRequested();
204+
205+
if (offset < 0 || offset > readStream.Length)
206+
{
207+
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
208+
}
209+
210+
if (length.HasValue &&
211+
(length.Value < 0 || length.Value > readStream.Length - offset))
212+
{
213+
throw new ArgumentOutOfRangeException(nameof(length), length, string.Empty);
214+
}
215+
216+
readStream.Seek(offset, SeekOrigin.Begin); // TODO: What if !CanSeek?
217+
218+
await StreamCopyOperation.CopyToAsync(readStream, outputStream, length, cancellationToken);
219+
}
220+
221+
136222
struct FeatureInterfaces
137223
{
138224
public IHttpResponseFeature Response;

test/Microsoft.AspNetCore.Http.Extensions.Tests/SendFileResponseExtensionsTests.cs renamed to test/Microsoft.AspNetCore.Http.Abstractions.Tests/SendFileTests.cs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,13 @@
66
using System.Threading.Tasks;
77
using Microsoft.AspNetCore.Http.Features;
88
using Microsoft.AspNetCore.Http.Internal;
9+
using Microsoft.Extensions.FileProviders;
910
using Xunit;
1011

11-
namespace Microsoft.AspNetCore.Http.Extensions.Tests
12+
namespace Microsoft.AspNetCore.Http.Tests
1213
{
13-
public class SendFileResponseExtensionsTests
14+
public class SendFileTests
1415
{
15-
[Fact]
16-
public Task SendFileWhenFileNotFoundThrows()
17-
{
18-
var response = new DefaultHttpContext().Response;
19-
return Assert.ThrowsAsync<FileNotFoundException>(() => response.SendFileAsync("foo"));
20-
}
21-
2216
[Fact]
2317
public async Task SendFileWorks()
2418
{
@@ -27,7 +21,8 @@ public async Task SendFileWorks()
2721
var fakeFeature = new FakeSendFileFeature();
2822
context.Features.Set<IHttpSendFileFeature>(fakeFeature);
2923

30-
await response.SendFileAsync("bob", 1, 3, CancellationToken.None);
24+
var fileInfo = new FakeFileInfo("bob");
25+
await response.SendFileAsync(fileInfo, 1, 3, CancellationToken.None);
3126

3227
Assert.Equal("bob", fakeFeature.name);
3328
Assert.Equal(1, fakeFeature.offset);
@@ -51,5 +46,28 @@ public Task SendFileAsync(string path, long offset, long? length, CancellationTo
5146
return Task.FromResult(0);
5247
}
5348
}
49+
50+
private class FakeFileInfo : IFileInfo
51+
{
52+
public FakeFileInfo(string name, bool exists = true)
53+
{
54+
Name = name;
55+
Exists = exists;
56+
}
57+
58+
public Stream CreateReadStream() => new MemoryStream();
59+
60+
public bool Exists { get; }
61+
62+
public long Length => 0;
63+
64+
public string PhysicalPath => Name;
65+
66+
public string Name { get; }
67+
68+
public DateTimeOffset LastModified => DateTimeOffset.UtcNow;
69+
70+
public bool IsDirectory => false;
71+
}
5472
}
5573
}

0 commit comments

Comments
 (0)