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

Commit 917c919

Browse files
committed
Cleanup & comments
Missing comments on Controller Added functional tests Correct XML comment errors Implemented fallback for FilePathResult Setting the content-length
1 parent 0974c69 commit 917c919

File tree

19 files changed

+656
-46
lines changed

19 files changed

+656
-46
lines changed

Mvc.sln

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UrlHelperWebSite", "test\We
8282
EndProject
8383
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiExplorerWebSite", "test\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.kproj", "{61061528-071E-424E-965A-07BCC2F02672}"
8484
EndProject
85+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FilesWebSite", "test\WebSites\FilesWebSite\FilesWebSite.kproj", "{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}"
86+
EndProject
8587
Global
8688
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8789
Debug|Any CPU = Debug|Any CPU
@@ -412,6 +414,16 @@ Global
412414
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
413415
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
414416
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|x86.ActiveCfg = Release|Any CPU
417+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
418+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
419+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
420+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
421+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|x86.ActiveCfg = Debug|Any CPU
422+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
423+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Any CPU.Build.0 = Release|Any CPU
424+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
425+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
426+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|x86.ActiveCfg = Release|Any CPU
415427
{61061528-071E-424E-965A-07BCC2F02672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
416428
{61061528-071E-424E-965A-07BCC2F02672}.Debug|Any CPU.Build.0 = Debug|Any CPU
417429
{61061528-071E-424E-965A-07BCC2F02672}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@@ -460,6 +472,7 @@ Global
460472
{1976AC4A-FEA4-4587-A158-D9F79736D2B6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
461473
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
462474
{A192E504-2881-41DC-90D1-B7F1DD1134E8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
475+
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
463476
{61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
464477
EndGlobalSection
465478
EndGlobal

src/Microsoft.AspNet.Mvc.Core/ActionResults/FileContentResult.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using Microsoft.AspNet.Http;
54
using System;
65
using System.Threading.Tasks;
6+
using Microsoft.AspNet.Http;
77

88
namespace Microsoft.AspNet.Mvc
99
{
10+
/// <summary>
11+
/// Represents an <see cref="ActionResult"/> that when executed will
12+
/// write a binary file to the response.
13+
/// </summary>
1014
public class FileContentResult : FileResult
1115
{
16+
/// <summary>
17+
/// Creates a new <see cref="FileContentResult"/> instance with
18+
/// the provided <paramref name="fileContents"/> and the
19+
/// provided <paramref name="contentType"/>.
20+
/// </summary>
21+
/// <param name="fileContents">The bytes that represent the file contents.</param>
22+
/// <param name="contentType">The Content-Type header of the response.</param>
1223
public FileContentResult(byte[] fileContents, string contentType)
1324
: base(contentType)
1425
{
@@ -20,8 +31,12 @@ public FileContentResult(byte[] fileContents, string contentType)
2031
FileContents = fileContents;
2132
}
2233

34+
/// <summary>
35+
/// Gets the file contents.
36+
/// </summary>
2337
public byte[] FileContents { get; private set; }
2438

39+
/// <inheritdoc />
2540
protected async override Task WriteFileAsync(HttpResponse response)
2641
{
2742
await response.Body.WriteAsync(FileContents, 0, FileContents.Length);

src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,95 @@
33

44
using Microsoft.AspNet.Http;
55
using Microsoft.AspNet.HttpFeature;
6-
using System;
76
using System.Threading;
87
using System.Threading.Tasks;
8+
using System.IO;
9+
using Microsoft.AspNet.FileSystems;
910

1011
namespace Microsoft.AspNet.Mvc
1112
{
13+
/// <summary>
14+
/// Represents an <see cref="ActionResult"/> that when executed will
15+
/// write a file from disk to the response using mechanisms provided
16+
/// by the host.
17+
/// </summary>
1218
public class FilePathResult : FileResult
1319
{
20+
private const int DefaultBufferSize = 0x1000;
21+
22+
/// <summary>
23+
/// Creates a new <see cref="FilePathResult"/> instance with
24+
/// the provided <paramref name="fileName"/> and the
25+
/// provided <paramref name="contentType"/>.
26+
/// </summary>
27+
/// <param name="fileName">The path to the file.</param>
28+
/// <param name="contentType">The Content-Type header of the response.</param>
1429
public FilePathResult([NotNull]string fileName, [NotNull]string contentType)
1530
: base(contentType)
1631
{
1732
FileName = fileName;
1833
}
1934

35+
/// <summary>
36+
/// Gets the path to the file that will be sent back as the response.
37+
/// </summary>
2038
public string FileName { get; private set; }
2139

40+
/// <inheritdoc />
2241
protected async override Task WriteFileAsync(HttpResponse response)
2342
{
2443
var sendFile = response.HttpContext.GetFeature<IHttpSendFileFeature>();
25-
await sendFile.SendFileAsync(FileName, 0, null, CancellationToken.None);
44+
if (sendFile != null)
45+
{
46+
var length = GetFileLengthOrNull(response);
47+
if (length.HasValue)
48+
{
49+
// Set the content-length to prevent the transfer-encoding to be chunked.
50+
response.ContentLength = length;
51+
}
52+
53+
await sendFile.SendFileAsync(FileName, 0, length, CancellationToken.None);
54+
}
55+
else
56+
{
57+
await CopyStreamToResponse(FileName, response);
58+
}
59+
}
60+
61+
private long? GetFileLengthOrNull(HttpResponse response)
62+
{
63+
var fileSystem = (IFileSystem)response
64+
.HttpContext
65+
.RequestServices
66+
.GetService(typeof(IFileSystem));
67+
68+
IFileInfo fileInfo = null;
69+
if (fileSystem.TryGetFileInfo(FileName, out fileInfo))
70+
{
71+
return fileInfo.Length;
72+
}
73+
74+
return null;
75+
}
76+
77+
private static async Task CopyStreamToResponse(string fileName, HttpResponse response)
78+
{
79+
var fileStream = new FileStream(
80+
fileName, FileMode.Open,
81+
FileAccess.Read,
82+
FileShare.ReadWrite,
83+
DefaultBufferSize,
84+
FileOptions.Asynchronous | FileOptions.SequentialScan);
85+
86+
if (fileStream.CanSeek)
87+
{
88+
response.ContentLength = fileStream.Length;
89+
}
90+
91+
using (fileStream)
92+
{
93+
await fileStream.CopyToAsync(response.Body, DefaultBufferSize);
94+
}
2695
}
2796
}
2897
}

src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,46 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using Microsoft.AspNet.Http;
54
using System;
65
using System.Text;
76
using System.Threading.Tasks;
7+
using Microsoft.AspNet.Http;
88

99
namespace Microsoft.AspNet.Mvc
1010
{
11+
/// <summary>
12+
/// Represents an <see cref="ActionResult"/> that when executed will
13+
/// write a file as the response.
14+
/// </summary>
1115
public abstract class FileResult : ActionResult
1216
{
1317
private string _fileDownloadName;
1418

19+
/// <summary>
20+
/// Creates a new <see cref="FileResult"/> instance with
21+
/// the provided <paramref name="contentType"/>.
22+
/// </summary>
23+
/// <param name="contentType">The Content-Type header of the response.</param>
1524
protected FileResult([NotNull]string contentType)
1625
{
1726
ContentType = contentType;
1827
}
1928

29+
/// <summary>
30+
/// Gets the Content-Type header value that will be written to the response.
31+
/// </summary>
2032
public string ContentType { get; private set; }
2133

34+
/// <summary>
35+
/// Gets the file name that will be used in the Content-Disposition header of the response.
36+
/// </summary>
2237
public string FileDownloadName
2338
{
2439
get { return _fileDownloadName ?? string.Empty; }
2540
set { _fileDownloadName = value; }
2641
}
2742

43+
/// <inheritdoc />
2844
public async override Task ExecuteResultAsync([NotNull]ActionContext context)
2945
{
3046
var response = context.HttpContext.Response;
@@ -44,8 +60,18 @@ public async override Task ExecuteResultAsync([NotNull]ActionContext context)
4460
await WriteFileAsync(response);
4561
}
4662

63+
/// <summary>
64+
/// Writes the file to the response.
65+
/// </summary>
66+
/// <param name="response">
67+
/// The <see cref="HttpResponse"/> where the file will be written
68+
/// .</param>
69+
/// <returns>
70+
/// A <see cref="Task"/> that will complete when the file has been written to the response.
71+
/// </returns>
4772
protected abstract Task WriteFileAsync(HttpResponse response);
4873

74+
// This is a temporary implementation until we have the right abstractions in HttpAbstractions.
4975
internal static class ContentDispositionUtil
5076
{
5177
private const string HexDigits = "0123456789ABCDEF";
@@ -96,10 +122,10 @@ public static string GetHeaderValue(string fileName)
96122
}
97123
}
98124

99-
return CreateNonUnicodeHeaderValue(fileName);
125+
return CreateNonUnicodeCharactersHeaderValue(fileName);
100126
}
101127

102-
private static string CreateNonUnicodeHeaderValue(string fileName)
128+
private static string CreateNonUnicodeCharactersHeaderValue(string fileName)
103129
{
104130
string escapedFileName = EscapeFileName(fileName);
105131
return string.Format("attachment; filename={0}", escapedFileName);
@@ -108,6 +134,10 @@ private static string CreateNonUnicodeHeaderValue(string fileName)
108134
private static string EscapeFileName(string fileName)
109135
{
110136
var hasToBeQuoted = false;
137+
138+
// We can't break the loop earlier because we need to check the
139+
// whole name for \n, in which case we need to provide a special
140+
// encoding.
111141
for (int i = 0; i < fileName.Length; i++)
112142
{
113143
if (fileName[i] == '\n')
@@ -163,9 +193,11 @@ private static string QuoteFileName(string fileName)
163193
switch (fileName[i])
164194
{
165195
case '\\':
196+
// Escape \
166197
builder.Append("\\\\");
167198
break;
168199
case '"':
200+
// Escape "
169201
builder.Append("\\\"");
170202
break;
171203
default:
@@ -182,14 +214,16 @@ private static string GetRfc2047Base64EncodedWord(string fileName)
182214
{
183215
// See RFC 2047 for details. Section 8 for examples.
184216
const string charset = "utf-8";
217+
// B means Base64
185218
const string encoding = "B";
186219

187-
var base64EncodedFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(fileName));
220+
var fileNameBytes = Encoding.UTF8.GetBytes(fileName);
221+
var base64EncodedFileName = Convert.ToBase64String(fileNameBytes);
188222

223+
// Encoded words are defined as "=?{charset}?{encoding}?{encpoded value}?="
189224
return string.Format("\"=?{0}?{1}?{2}?=\"", charset, encoding, base64EncodedFileName);
190225
}
191226

192-
193227
// Application of RFC 2231 Encoding to Hypertext Transfer Protocol (HTTP) Header Fields, sec. 3.2
194228
// http://greenbytes.de/tech/webdav/draft-reschke-rfc2231-in-http-latest.html
195229
private static bool IsByteValidHeaderValueCharacter(byte b)

src/Microsoft.AspNet.Mvc.Core/ActionResults/FileStreamResult.cs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,46 @@
77

88
namespace Microsoft.AspNet.Mvc
99
{
10+
/// <summary>
11+
/// Represents an <see cref="ActionResult"/> that when executed will
12+
/// write a file from a stream to the response.
13+
/// </summary>
1014
public class FileStreamResult : FileResult
1115
{
1216
// default buffer size as defined in BufferedStream type
1317
private const int BufferSize = 0x1000;
1418

19+
/// <summary>
20+
/// Creates a new <see cref="FileStreamResult"/> instance with
21+
/// the provided <paramref name="fileStream"/> and the
22+
/// provided <paramref name="contentType"/>.
23+
/// </summary>
24+
/// <param name="fileStream">The stream with the file.</param>
25+
/// <param name="contentType">The Content-Type header of the response.</param>
1526
public FileStreamResult([NotNull]Stream fileStream, string contentType)
1627
: base(contentType)
1728
{
18-
1929
FileStream = fileStream;
2030
}
2131

32+
/// <summary>
33+
/// Gets the stream with the file that will be sent back as the response.
34+
/// </summary>
2235
public Stream FileStream { get; private set; }
2336

37+
/// <inheritdoc />
2438
protected async override Task WriteFileAsync(HttpResponse response)
2539
{
26-
// grab chunks of data and write to the output stream
2740
var outputStream = response.Body;
28-
using (FileStream)
29-
{
30-
byte[] buffer = new byte[BufferSize];
3141

32-
while (true)
33-
{
34-
int bytesRead = await FileStream.ReadAsync(buffer, 0, BufferSize);
35-
if (bytesRead == 0)
36-
{
37-
// no more data
38-
break;
39-
}
42+
if (FileStream.CanSeek)
43+
{
44+
response.ContentLength = FileStream.Length - FileStream.Position;
45+
}
4046

41-
await outputStream.WriteAsync(buffer, 0, bytesRead);
42-
}
47+
using (FileStream)
48+
{
49+
await FileStream.CopyToAsync(outputStream, BufferSize);
4350
}
4451
}
4552
}

0 commit comments

Comments
 (0)