Skip to content

Commit 72444f9

Browse files
authored
Add IResult implementations for more IActionResults (#32647)
* Adding implementation for RedirectResult, FileContentResult, VirtualFileResult * Adding Physical implemenation and tests * Revert FileStreamResult as is not completed * Adding existing test for new implementation of IResult * Applying observations for generics and identations
1 parent 3c7a5e7 commit 72444f9

21 files changed

+2354
-1104
lines changed

src/Mvc/Mvc.Core/src/FileContentResult.cs

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

44
using System;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.IO;
67
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Http;
79
using Microsoft.AspNetCore.Mvc.Infrastructure;
810
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.Logging;
912
using Microsoft.Net.Http.Headers;
1013

1114
namespace Microsoft.AspNetCore.Mvc
@@ -14,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc
1417
/// Represents an <see cref="ActionResult"/> that when executed will
1518
/// write a binary file to the response.
1619
/// </summary>
17-
public class FileContentResult : FileResult
20+
public class FileContentResult : FileResult, IResult
1821
{
1922
private byte[] _fileContents;
2023

@@ -77,5 +80,43 @@ public override Task ExecuteResultAsync(ActionContext context)
7780
var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<FileContentResult>>();
7881
return executor.ExecuteAsync(context, this);
7982
}
83+
84+
Task IResult.ExecuteAsync(HttpContext httpContext)
85+
{
86+
if (httpContext == null)
87+
{
88+
throw new ArgumentNullException(nameof(httpContext));
89+
}
90+
91+
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
92+
var logger = loggerFactory.CreateLogger<RedirectResult>();
93+
94+
var (range, rangeLength, serveBody) = FileResultExecutorBase.SetHeadersAndLog(
95+
httpContext,
96+
this,
97+
FileContents.Length,
98+
EnableRangeProcessing,
99+
LastModified,
100+
EntityTag,
101+
logger);
102+
103+
if (!serveBody)
104+
{
105+
return Task.CompletedTask;
106+
}
107+
108+
if (range != null && rangeLength == 0)
109+
{
110+
return Task.CompletedTask;
111+
}
112+
113+
if (range != null)
114+
{
115+
logger.WritingRangeToBody();
116+
}
117+
118+
var fileContentStream = new MemoryStream(FileContents);
119+
return FileResultExecutorBase.WriteFileAsyncInternal(httpContext, fileContentStream, range, rangeLength);
120+
}
80121
}
81122
}

src/Mvc/Mvc.Core/src/Infrastructure/FileResultExecutorBase.cs

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,35 @@ protected virtual (RangeItemHeaderValue? range, long rangeLength, bool serveBody
6868
DateTimeOffset? lastModified = null,
6969
EntityTagHeaderValue? etag = null)
7070
{
71-
if (context == null)
71+
return SetHeadersAndLog(
72+
context.HttpContext,
73+
result,
74+
fileLength,
75+
enableRangeProcessing,
76+
lastModified,
77+
etag,
78+
Logger);
79+
}
80+
81+
internal static (RangeItemHeaderValue? range, long rangeLength, bool serveBody) SetHeadersAndLog(
82+
HttpContext httpContext,
83+
FileResult result,
84+
long? fileLength,
85+
bool enableRangeProcessing,
86+
DateTimeOffset? lastModified,
87+
EntityTagHeaderValue? etag,
88+
ILogger logger)
89+
{
90+
if (httpContext == null)
7291
{
73-
throw new ArgumentNullException(nameof(context));
92+
throw new ArgumentNullException(nameof(httpContext));
7493
}
7594
if (result == null)
7695
{
7796
throw new ArgumentNullException(nameof(result));
7897
}
79-
80-
var request = context.HttpContext.Request;
98+
99+
var request = httpContext.Request;
81100
var httpRequestHeaders = request.GetTypedHeaders();
82101

83102
// Since the 'Last-Modified' and other similar http date headers are rounded down to whole seconds,
@@ -87,9 +106,9 @@ protected virtual (RangeItemHeaderValue? range, long rangeLength, bool serveBody
87106
lastModified = RoundDownToWholeSeconds(lastModified.Value);
88107
}
89108

90-
var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag);
109+
var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag, logger);
91110

92-
var response = context.HttpContext.Response;
111+
var response = httpContext.Response;
93112
SetLastModifiedAndEtagHeaders(response, lastModified, etag);
94113

95114
// Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed)
@@ -104,8 +123,8 @@ protected virtual (RangeItemHeaderValue? range, long rangeLength, bool serveBody
104123
return (range: null, rangeLength: 0, serveBody: false);
105124
}
106125

107-
SetContentType(context, result);
108-
SetContentDispositionHeader(context, result);
126+
SetContentType(httpContext, result);
127+
SetContentDispositionHeader(httpContext, result);
109128

110129
if (fileLength.HasValue)
111130
{
@@ -125,27 +144,27 @@ protected virtual (RangeItemHeaderValue? range, long rangeLength, bool serveBody
125144
// range should be processed and Range headers should be set
126145
if ((HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))
127146
&& (preconditionState == PreconditionState.Unspecified || preconditionState == PreconditionState.ShouldProcess)
128-
&& (IfRangeValid(httpRequestHeaders, lastModified, etag)))
147+
&& (IfRangeValid(httpRequestHeaders, lastModified, etag, logger)))
129148
{
130-
return SetRangeHeaders(context, httpRequestHeaders, fileLength.Value);
149+
return SetRangeHeaders(httpContext, httpRequestHeaders, fileLength.Value, logger);
131150
}
132151
}
133152
else
134153
{
135-
Logger.NotEnabledForRangeProcessing();
154+
logger.NotEnabledForRangeProcessing();
136155
}
137156
}
138157

139158
return (range: null, rangeLength: 0, serveBody: !HttpMethods.IsHead(request.Method));
140159
}
141160

142-
private static void SetContentType(ActionContext context, FileResult result)
161+
private static void SetContentType(HttpContext httpContext, FileResult result)
143162
{
144-
var response = context.HttpContext.Response;
163+
var response = httpContext.Response;
145164
response.ContentType = result.ContentType;
146165
}
147166

148-
private static void SetContentDispositionHeader(ActionContext context, FileResult result)
167+
private static void SetContentDispositionHeader(HttpContext httpContext, FileResult result)
149168
{
150169
if (!string.IsNullOrEmpty(result.FileDownloadName))
151170
{
@@ -156,7 +175,7 @@ private static void SetContentDispositionHeader(ActionContext context, FileResul
156175
// basis for the actual filename, where possible.
157176
var contentDisposition = new ContentDispositionHeaderValue("attachment");
158177
contentDisposition.SetHttpFileName(result.FileDownloadName);
159-
context.HttpContext.Response.Headers.ContentDisposition = contentDisposition.ToString();
178+
httpContext.Response.Headers.ContentDisposition = contentDisposition.ToString();
160179
}
161180
}
162181

@@ -178,10 +197,11 @@ private static void SetAcceptRangeHeader(HttpResponse response)
178197
response.Headers.AcceptRanges = AcceptRangeHeaderValue;
179198
}
180199

181-
internal bool IfRangeValid(
200+
internal static bool IfRangeValid(
182201
RequestHeaders httpRequestHeaders,
183202
DateTimeOffset? lastModified,
184-
EntityTagHeaderValue? etag)
203+
EntityTagHeaderValue? etag,
204+
ILogger logger)
185205
{
186206
// 14.27 If-Range
187207
var ifRange = httpRequestHeaders.IfRange;
@@ -196,13 +216,13 @@ internal bool IfRangeValid(
196216
{
197217
if (lastModified.HasValue && lastModified > ifRange.LastModified)
198218
{
199-
Logger.IfRangeLastModifiedPreconditionFailed(lastModified, ifRange.LastModified);
219+
logger.IfRangeLastModifiedPreconditionFailed(lastModified, ifRange.LastModified);
200220
return false;
201221
}
202222
}
203223
else if (etag != null && ifRange.EntityTag != null && !ifRange.EntityTag.Compare(etag, useStrongComparison: true))
204224
{
205-
Logger.IfRangeETagPreconditionFailed(etag, ifRange.EntityTag);
225+
logger.IfRangeETagPreconditionFailed(etag, ifRange.EntityTag);
206226
return false;
207227
}
208228
}
@@ -211,10 +231,11 @@ internal bool IfRangeValid(
211231
}
212232

213233
// Internal for testing
214-
internal PreconditionState GetPreconditionState(
234+
internal static PreconditionState GetPreconditionState(
215235
RequestHeaders httpRequestHeaders,
216236
DateTimeOffset? lastModified,
217-
EntityTagHeaderValue? etag)
237+
EntityTagHeaderValue? etag,
238+
ILogger logger)
218239
{
219240
var ifMatchState = PreconditionState.Unspecified;
220241
var ifNoneMatchState = PreconditionState.Unspecified;
@@ -234,7 +255,7 @@ internal PreconditionState GetPreconditionState(
234255

235256
if (ifMatchState == PreconditionState.PreconditionFailed)
236257
{
237-
Logger.IfMatchPreconditionFailed(etag);
258+
logger.IfMatchPreconditionFailed(etag);
238259
}
239260
}
240261

@@ -269,7 +290,7 @@ internal PreconditionState GetPreconditionState(
269290

270291
if (ifUnmodifiedSinceState == PreconditionState.PreconditionFailed)
271292
{
272-
Logger.IfUnmodifiedSincePreconditionFailed(lastModified, ifUnmodifiedSince);
293+
logger.IfUnmodifiedSincePreconditionFailed(lastModified, ifUnmodifiedSince);
273294
}
274295
}
275296

@@ -316,22 +337,23 @@ private static PreconditionState GetMaxPreconditionState(params PreconditionStat
316337
return max;
317338
}
318339

319-
private (RangeItemHeaderValue? range, long rangeLength, bool serveBody) SetRangeHeaders(
320-
ActionContext context,
340+
private static (RangeItemHeaderValue? range, long rangeLength, bool serveBody) SetRangeHeaders(
341+
HttpContext httpContext,
321342
RequestHeaders httpRequestHeaders,
322-
long fileLength)
343+
long fileLength,
344+
ILogger logger)
323345
{
324-
var response = context.HttpContext.Response;
346+
var response = httpContext.Response;
325347
var httpResponseHeaders = response.GetTypedHeaders();
326-
var serveBody = !HttpMethods.IsHead(context.HttpContext.Request.Method);
348+
var serveBody = !HttpMethods.IsHead(httpContext.Request.Method);
327349

328350
// Range may be null for empty range header, invalid ranges, parsing errors, multiple ranges
329351
// and when the file length is zero.
330352
var (isRangeRequest, range) = RangeHelper.ParseRange(
331-
context.HttpContext,
353+
httpContext,
332354
httpRequestHeaders,
333355
fileLength,
334-
Logger);
356+
logger);
335357

336358
if (!isRangeRequest)
337359
{
@@ -397,6 +419,11 @@ protected static ILogger CreateLogger<T>(ILoggerFactory factory)
397419
/// <param name="rangeLength">The range length.</param>
398420
/// <returns>The async task.</returns>
399421
protected static async Task WriteFileAsync(HttpContext context, Stream fileStream, RangeItemHeaderValue? range, long rangeLength)
422+
{
423+
await WriteFileAsyncInternal(context, fileStream, range, rangeLength);
424+
}
425+
426+
internal static async Task WriteFileAsyncInternal(HttpContext context, Stream fileStream, RangeItemHeaderValue? range, long rangeLength)
400427
{
401428
var outputStream = context.Response.Body;
402429
using (fileStream)

src/Mvc/Mvc.Core/src/Infrastructure/PhysicalFileResultExecutor.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33

44
using System;
55
using System.IO;
6-
using System.Threading;
76
using System.Threading.Tasks;
87
using Microsoft.AspNetCore.Http;
9-
using Microsoft.AspNetCore.Http.Features;
108
using Microsoft.AspNetCore.Mvc.Core;
119
using Microsoft.Extensions.Logging;
1210
using Microsoft.Net.Http.Headers;
@@ -69,9 +67,19 @@ public virtual Task ExecuteAsync(ActionContext context, PhysicalFileResult resul
6967
/// <inheritdoc/>
7068
protected virtual Task WriteFileAsync(ActionContext context, PhysicalFileResult result, RangeItemHeaderValue? range, long rangeLength)
7169
{
72-
if (context == null)
70+
return WriteFileAsyncInternal(context.HttpContext, result, range, rangeLength, Logger);
71+
}
72+
73+
internal static Task WriteFileAsyncInternal(
74+
HttpContext httpContext,
75+
PhysicalFileResult result,
76+
RangeItemHeaderValue? range,
77+
long rangeLength,
78+
ILogger logger)
79+
{
80+
if (httpContext == null)
7381
{
74-
throw new ArgumentNullException(nameof(context));
82+
throw new ArgumentNullException(nameof(httpContext));
7583
}
7684

7785
if (result == null)
@@ -84,15 +92,15 @@ protected virtual Task WriteFileAsync(ActionContext context, PhysicalFileResult
8492
return Task.CompletedTask;
8593
}
8694

87-
var response = context.HttpContext.Response;
95+
var response = httpContext.Response;
8896
if (!Path.IsPathRooted(result.FileName))
8997
{
9098
throw new NotSupportedException(Resources.FormatFileResult_PathNotRooted(result.FileName));
9199
}
92100

93101
if (range != null)
94102
{
95-
Logger.WritingRangeToBody();
103+
logger.WritingRangeToBody();
96104
}
97105

98106
if (range != null)

src/Mvc/Mvc.Core/src/Infrastructure/VirtualFileResultExecutor.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public virtual Task ExecuteAsync(ActionContext context, VirtualFileResult result
5050
throw new ArgumentNullException(nameof(result));
5151
}
5252

53-
var fileInfo = GetFileInformation(result);
53+
var fileInfo = GetFileInformation(result, _hostingEnvironment);
5454
if (!fileInfo.Exists)
5555
{
5656
throw new FileNotFoundException(
@@ -89,16 +89,26 @@ protected virtual Task WriteFileAsync(ActionContext context, VirtualFileResult r
8989
throw new ArgumentNullException(nameof(result));
9090
}
9191

92+
return WriteFileAsyncInternal(context.HttpContext, fileInfo, range, rangeLength, Logger);
93+
}
94+
95+
internal static Task WriteFileAsyncInternal(
96+
HttpContext httpContext,
97+
IFileInfo fileInfo,
98+
RangeItemHeaderValue? range,
99+
long rangeLength,
100+
ILogger logger)
101+
{
92102
if (range != null && rangeLength == 0)
93103
{
94104
return Task.CompletedTask;
95105
}
96106

97-
var response = context.HttpContext.Response;
107+
var response = httpContext.Response;
98108

99109
if (range != null)
100110
{
101-
Logger.WritingRangeToBody();
111+
logger.WritingRangeToBody();
102112
}
103113

104114
if (range != null)
@@ -113,9 +123,9 @@ protected virtual Task WriteFileAsync(ActionContext context, VirtualFileResult r
113123
count: null);
114124
}
115125

116-
private IFileInfo GetFileInformation(VirtualFileResult result)
126+
internal static IFileInfo GetFileInformation(VirtualFileResult result, IWebHostEnvironment hostingEnvironment)
117127
{
118-
var fileProvider = GetFileProvider(result);
128+
var fileProvider = GetFileProvider(result, hostingEnvironment);
119129
if (fileProvider is NullFileProvider)
120130
{
121131
throw new InvalidOperationException(Resources.VirtualFileResultExecutor_NoFileProviderConfigured);
@@ -131,14 +141,14 @@ private IFileInfo GetFileInformation(VirtualFileResult result)
131141
return fileInfo;
132142
}
133143

134-
private IFileProvider GetFileProvider(VirtualFileResult result)
144+
internal static IFileProvider GetFileProvider(VirtualFileResult result, IWebHostEnvironment hostingEnvironment)
135145
{
136146
if (result.FileProvider != null)
137147
{
138148
return result.FileProvider;
139149
}
140150

141-
result.FileProvider = _hostingEnvironment.WebRootFileProvider;
151+
result.FileProvider = hostingEnvironment.WebRootFileProvider;
142152
return result.FileProvider;
143153
}
144154

0 commit comments

Comments
 (0)