Skip to content

Commit ceffcc9

Browse files
Add OnPrepareResponseAsync dotnet#44932 (dotnet#45062)
1 parent fada626 commit ceffcc9

File tree

4 files changed

+160
-9
lines changed

4 files changed

+160
-9
lines changed
+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Builder.StaticFileOptions.OnPrepareResponseAsync.get -> System.Func<Microsoft.AspNetCore.StaticFiles.StaticFileResponseContext!, System.Threading.Tasks.Task!>!
3+
Microsoft.AspNetCore.Builder.StaticFileOptions.OnPrepareResponseAsync.set -> void

Diff for: src/Middleware/StaticFiles/src/StaticFileContext.cs

+11-9
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ private void ComputeRange()
240240
IsRangeRequest = isRangeRequest;
241241
}
242242

243-
public void ApplyResponseHeaders(int statusCode)
243+
public Task ApplyResponseHeadersAsync(int statusCode)
244244
{
245245
_response.StatusCode = statusCode;
246246
if (statusCode < 400)
@@ -265,10 +265,13 @@ public void ApplyResponseHeaders(int statusCode)
265265
_response.ContentLength = _length;
266266
}
267267

268-
if (_options.OnPrepareResponse != StaticFileOptions._defaultOnPrepareResponse)
268+
if (_options.OnPrepareResponse != StaticFileOptions._defaultOnPrepareResponse || _options.OnPrepareResponseAsync != StaticFileOptions._defaultOnPrepareResponseAsync)
269269
{
270-
_options.OnPrepareResponse(new StaticFileResponseContext(_context, _fileInfo));
270+
var context = new StaticFileResponseContext(_context, _fileInfo);
271+
_options.OnPrepareResponse(context);
272+
return _options.OnPrepareResponseAsync(context);
271273
}
274+
return Task.CompletedTask;
272275
}
273276

274277
public PreconditionState GetPreconditionState()
@@ -289,10 +292,9 @@ private static PreconditionState GetMaxPreconditionState(params PreconditionStat
289292

290293
public Task SendStatusAsync(int statusCode)
291294
{
292-
ApplyResponseHeaders(statusCode);
293-
294295
_logger.Handled(statusCode, SubPath);
295-
return Task.CompletedTask;
296+
297+
return ApplyResponseHeadersAsync(statusCode);
296298
}
297299

298300
public async Task ServeStaticFile(HttpContext context, RequestDelegate next)
@@ -344,7 +346,7 @@ public async Task ServeStaticFile(HttpContext context, RequestDelegate next)
344346
public async Task SendAsync()
345347
{
346348
SetCompressionMode();
347-
ApplyResponseHeaders(StatusCodes.Status200OK);
349+
await ApplyResponseHeadersAsync(StatusCodes.Status200OK);
348350
try
349351
{
350352
await _context.Response.SendFileAsync(_fileInfo, 0, _length, _context.RequestAborted);
@@ -365,7 +367,7 @@ internal async Task SendRangeAsync()
365367
// SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
366368
// the current length of the selected resource. e.g. */length
367369
ResponseHeaders.ContentRange = new ContentRangeHeaderValue(_length);
368-
ApplyResponseHeaders(StatusCodes.Status416RangeNotSatisfiable);
370+
await ApplyResponseHeadersAsync(StatusCodes.Status416RangeNotSatisfiable);
369371

370372
_logger.RangeNotSatisfiable(SubPath);
371373
return;
@@ -374,7 +376,7 @@ internal async Task SendRangeAsync()
374376
ResponseHeaders.ContentRange = ComputeContentRange(_range, out var start, out var length);
375377
_response.ContentLength = length;
376378
SetCompressionMode();
377-
ApplyResponseHeaders(StatusCodes.Status206PartialContent);
379+
await ApplyResponseHeadersAsync(StatusCodes.Status206PartialContent);
378380

379381
try
380382
{

Diff for: src/Middleware/StaticFiles/src/StaticFileOptions.cs

+14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Builder;
1313
public class StaticFileOptions : SharedOptionsBase
1414
{
1515
internal static readonly Action<StaticFileResponseContext> _defaultOnPrepareResponse = _ => { };
16+
internal static readonly Func<StaticFileResponseContext, Task> _defaultOnPrepareResponseAsync = _ => Task.CompletedTask;
1617

1718
/// <summary>
1819
/// Defaults to all request paths
@@ -28,6 +29,7 @@ public StaticFileOptions() : this(new SharedOptions())
2829
public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions)
2930
{
3031
OnPrepareResponse = _defaultOnPrepareResponse;
32+
OnPrepareResponseAsync = _defaultOnPrepareResponseAsync;
3133
}
3234

3335
/// <summary>
@@ -61,5 +63,17 @@ public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions)
6163
/// Called after the status code and headers have been set, but before the body has been written.
6264
/// This can be used to add or change the response headers.
6365
/// </summary>
66+
/// <remarks>
67+
/// <see cref="OnPrepareResponse" /> is called before <see cref="OnPrepareResponseAsync" />.
68+
/// </remarks>
6469
public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }
70+
71+
/// <summary>
72+
/// Called after the status code and headers have been set, but before the body has been written.
73+
/// This can be used to add or change the response headers.
74+
/// </summary>
75+
/// <remarks>
76+
/// <see cref="OnPrepareResponse" /> is called before <see cref="OnPrepareResponseAsync" />.
77+
/// </remarks>
78+
public Func<StaticFileResponseContext, Task> OnPrepareResponseAsync { get; set; }
6579
}

Diff for: src/Middleware/StaticFiles/test/UnitTests/StaticFileMiddlewareTests.cs

+133
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,139 @@ private async Task FoundFile_Served(string baseUrl, string baseDir, string reque
201201
}
202202
}
203203

204+
[Fact]
205+
public async Task OnPrepareResponse_Executed_Test()
206+
{
207+
var baseUrl = "";
208+
var baseDir = @".";
209+
var requestUrl = "/TestDocument.txt";
210+
211+
var onPrepareResponseExecuted = false;
212+
213+
using var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir));
214+
using var host = await StaticFilesTestServer.Create(app => app.UseStaticFiles(new StaticFileOptions
215+
{
216+
RequestPath = new PathString(baseUrl),
217+
FileProvider = fileProvider,
218+
OnPrepareResponse = context =>
219+
{
220+
onPrepareResponseExecuted = true;
221+
}
222+
}));
223+
using var server = host.GetTestServer();
224+
var fileInfo = fileProvider.GetFileInfo(Path.GetFileName(requestUrl));
225+
var response = await server.CreateRequest(requestUrl).GetAsync();
226+
var responseContent = await response.Content.ReadAsByteArrayAsync();
227+
228+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
229+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
230+
Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
231+
Assert.Equal(response.Content.Headers.ContentLength, responseContent.Length);
232+
Assert.NotNull(response.Headers.ETag);
233+
234+
using (var stream = fileInfo.CreateReadStream())
235+
{
236+
var fileContents = new byte[stream.Length];
237+
stream.Read(fileContents, 0, (int)stream.Length);
238+
Assert.True(responseContent.SequenceEqual(fileContents));
239+
}
240+
241+
Assert.True(onPrepareResponseExecuted);
242+
}
243+
244+
[Fact]
245+
public async Task OnPrepareResponseAsync_Executed_Test()
246+
{
247+
var baseUrl = "";
248+
var baseDir = @".";
249+
var requestUrl = "/TestDocument.txt";
250+
251+
var onPrepareResponseExecuted = false;
252+
253+
using var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir));
254+
using var host = await StaticFilesTestServer.Create(app => app.UseStaticFiles(new StaticFileOptions
255+
{
256+
RequestPath = new PathString(baseUrl),
257+
FileProvider = fileProvider,
258+
OnPrepareResponseAsync = context =>
259+
{
260+
onPrepareResponseExecuted = true;
261+
262+
return Task.CompletedTask;
263+
}
264+
}));
265+
using var server = host.GetTestServer();
266+
var fileInfo = fileProvider.GetFileInfo(Path.GetFileName(requestUrl));
267+
var response = await server.CreateRequest(requestUrl).GetAsync();
268+
var responseContent = await response.Content.ReadAsByteArrayAsync();
269+
270+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
271+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
272+
Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
273+
Assert.Equal(response.Content.Headers.ContentLength, responseContent.Length);
274+
Assert.NotNull(response.Headers.ETag);
275+
276+
using (var stream = fileInfo.CreateReadStream())
277+
{
278+
var fileContents = new byte[stream.Length];
279+
stream.Read(fileContents, 0, (int)stream.Length);
280+
Assert.True(responseContent.SequenceEqual(fileContents));
281+
}
282+
283+
Assert.True(onPrepareResponseExecuted);
284+
}
285+
286+
[Fact]
287+
public async Task OnPrepareResponse_Execution_Order_Test()
288+
{
289+
var baseUrl = "";
290+
var baseDir = @".";
291+
var requestUrl = "/TestDocument.txt";
292+
293+
var syncCallbackInvoked = false;
294+
var asyncCallbackInvoked = false;
295+
296+
using var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir));
297+
using var host = await StaticFilesTestServer.Create(app => app.UseStaticFiles(new StaticFileOptions
298+
{
299+
RequestPath = new PathString(baseUrl),
300+
FileProvider = fileProvider,
301+
OnPrepareResponse = context =>
302+
{
303+
Assert.False(syncCallbackInvoked);
304+
Assert.False(asyncCallbackInvoked);
305+
syncCallbackInvoked = true;
306+
},
307+
OnPrepareResponseAsync = context =>
308+
{
309+
Assert.True(syncCallbackInvoked);
310+
Assert.False(asyncCallbackInvoked);
311+
asyncCallbackInvoked = true;
312+
return Task.CompletedTask;
313+
}
314+
}));
315+
using var server = host.GetTestServer();
316+
var fileInfo = fileProvider.GetFileInfo(Path.GetFileName(requestUrl));
317+
var response = await server.CreateRequest(requestUrl).GetAsync();
318+
var responseContent = await response.Content.ReadAsByteArrayAsync();
319+
320+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
321+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
322+
Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
323+
Assert.Equal(response.Content.Headers.ContentLength, responseContent.Length);
324+
Assert.NotNull(response.Headers.ETag);
325+
326+
using (var stream = fileInfo.CreateReadStream())
327+
{
328+
var fileContents = new byte[stream.Length];
329+
stream.Read(fileContents, 0, (int)stream.Length);
330+
Assert.True(responseContent.SequenceEqual(fileContents));
331+
}
332+
333+
Assert.True(syncCallbackInvoked);
334+
Assert.True(asyncCallbackInvoked);
335+
}
336+
204337
[Fact]
205338
public async Task File_Served_If_Endpoint_With_Null_RequestDelegate_Is_Active()
206339
{

0 commit comments

Comments
 (0)