Merge branch 'release/2.1' into dev
This commit is contained in:
commit
b56436b5c6
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Task ExecuteAsync(ActionContext context, FileStreamResult result)
|
||||
public virtual async Task ExecuteAsync(ActionContext context, FileStreamResult result)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
|
|
@ -29,31 +29,38 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
Logger.ExecutingFileResult(result);
|
||||
|
||||
long? fileLength = null;
|
||||
if (result.FileStream.CanSeek)
|
||||
using (result.FileStream)
|
||||
{
|
||||
fileLength = result.FileStream.Length;
|
||||
Logger.ExecutingFileResult(result);
|
||||
|
||||
long? fileLength = null;
|
||||
if (result.FileStream.CanSeek)
|
||||
{
|
||||
fileLength = result.FileStream.Length;
|
||||
}
|
||||
|
||||
var (range, rangeLength, serveBody) = SetHeadersAndLog(
|
||||
context,
|
||||
result,
|
||||
fileLength,
|
||||
result.EnableRangeProcessing,
|
||||
result.LastModified,
|
||||
result.EntityTag);
|
||||
|
||||
if (!serveBody)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await WriteFileAsync(context, result, range, rangeLength);
|
||||
}
|
||||
|
||||
var (range, rangeLength, serveBody) = SetHeadersAndLog(
|
||||
context,
|
||||
result,
|
||||
fileLength,
|
||||
result.EnableRangeProcessing,
|
||||
result.LastModified,
|
||||
result.EntityTag);
|
||||
|
||||
if (!serveBody)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return WriteFileAsync(context, result, range, rangeLength);
|
||||
}
|
||||
|
||||
protected virtual Task WriteFileAsync(ActionContext context, FileStreamResult result, RangeItemHeaderValue range, long rangeLength)
|
||||
protected virtual Task WriteFileAsync(
|
||||
ActionContext context,
|
||||
FileStreamResult result,
|
||||
RangeItemHeaderValue range,
|
||||
long rangeLength)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ using System.Threading.Tasks;
|
|||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that asynchronously surrounds execution of the page handler method.
|
||||
/// A filter that asynchronously surrounds execution of a page handler method. This filter is executed only when
|
||||
/// decorated on a handler's type and not on individual handler methods.
|
||||
/// </summary>
|
||||
public interface IAsyncPageFilter : IFilterMetadata
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that surrounds execution of a page handler method.
|
||||
/// A filter that surrounds execution of a page handler method. This filter is executed only when decorated on a
|
||||
/// handler's type and not on individual handler methods.
|
||||
/// </summary>
|
||||
public interface IPageFilter : IFilterMetadata
|
||||
{
|
||||
|
|
@ -21,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
|||
void OnPageHandlerExecuting(PageHandlerExecutingContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called after the handler method executes, before the action method is invoked.
|
||||
/// Called after the handler method executes, before the action result executes.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="PageHandlerExecutedContext"/>.</param>
|
||||
void OnPageHandlerExecuted(PageHandlerExecutedContext context);
|
||||
|
|
|
|||
|
|
@ -128,7 +128,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
_page = (PageBase)CacheEntry.PageFactory(_pageContext, _viewContext);
|
||||
}
|
||||
|
||||
pageResult.Page = _page;
|
||||
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
|
||||
}
|
||||
|
|
@ -278,6 +277,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
_result = new PageResult();
|
||||
}
|
||||
|
||||
// Ensure ViewData is set on PageResult for backwards compatibility (For example, Identity UI accesses
|
||||
// ViewData in a PageFilter's PageHandlerExecutedMethod)
|
||||
if (_result is PageResult pageResult)
|
||||
{
|
||||
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
|
||||
}
|
||||
}
|
||||
|
||||
private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
|
||||
|
|
|
|||
|
|
@ -1675,7 +1675,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the handler method executes, before the action method is invoked.
|
||||
/// Called after the handler method executes, before the action result executes.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="PageHandlerExecutedContext"/>.</param>
|
||||
public virtual void OnPageHandlerExecuted(PageHandlerExecutedContext context)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
<Target Name="_ResolveMvcTestProjectReferences" DependsOnTargets="ResolveReferences">
|
||||
<ItemGroup>
|
||||
<_ContentRootProjectReferences Include="@(ReferencePath)" Condition="'%(ReferencePath.ReferenceSourceTarget)' == 'ProjectReference'" />
|
||||
<_ContentRootProjectReferences
|
||||
Include="@(ReferencePath)"
|
||||
Condition="'%(ReferencePath.ReferenceSourceTarget)' == 'ProjectReference' and '%(ReferencePath.TargetFrameworkIdentifier)' != '.NETStandard'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(contentRange.ToString(), httpResponse.Headers[HeaderNames.ContentRange]);
|
||||
Assert.Equal(contentLength, httpResponse.ContentLength);
|
||||
Assert.Equal(expectedString, body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -174,6 +175,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(contentRange.ToString(), httpResponse.Headers[HeaderNames.ContentRange]);
|
||||
Assert.Equal(5, httpResponse.ContentLength);
|
||||
Assert.Equal("Hello", body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -217,6 +219,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(lastModified.ToString("R"), httpResponse.Headers[HeaderNames.LastModified]);
|
||||
Assert.Equal(entityTag.ToString(), httpResponse.Headers[HeaderNames.ETag]);
|
||||
Assert.Equal("Hello World", body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -261,6 +264,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(lastModified.ToString("R"), httpResponse.Headers[HeaderNames.LastModified]);
|
||||
Assert.Equal(entityTag.ToString(), httpResponse.Headers[HeaderNames.ETag]);
|
||||
Assert.Equal("Hello World", body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -303,6 +307,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(lastModified.ToString("R"), httpResponse.Headers[HeaderNames.LastModified]);
|
||||
Assert.Equal(entityTag.ToString(), httpResponse.Headers[HeaderNames.ETag]);
|
||||
Assert.Equal("Hello World", body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -346,6 +351,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(contentRange.ToString(), httpResponse.Headers[HeaderNames.ContentRange]);
|
||||
Assert.Equal(11, httpResponse.ContentLength);
|
||||
Assert.Empty(body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -389,6 +395,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Empty(httpResponse.Headers[HeaderNames.ContentRange]);
|
||||
Assert.NotEmpty(httpResponse.Headers[HeaderNames.LastModified]);
|
||||
Assert.Empty(body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -432,6 +439,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Empty(httpResponse.Headers[HeaderNames.ContentRange]);
|
||||
Assert.NotEmpty(httpResponse.Headers[HeaderNames.LastModified]);
|
||||
Assert.Empty(body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -480,6 +488,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]);
|
||||
Assert.Equal(contentRange.ToString(), httpResponse.Headers[HeaderNames.ContentRange]);
|
||||
Assert.Empty(body);
|
||||
Assert.False(readStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -541,6 +550,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
// Assert
|
||||
var outBytes = outStream.ToArray();
|
||||
Assert.True(originalBytes.SequenceEqual(outBytes));
|
||||
Assert.False(originalStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -570,6 +580,31 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
var outBytes = outStream.ToArray();
|
||||
Assert.True(originalBytes.SequenceEqual(outBytes));
|
||||
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
||||
Assert.False(originalStream.CanSeek);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeadRequest_DoesNotWriteToBody_AndClosesReadStream()
|
||||
{
|
||||
// Arrange
|
||||
var readStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello, World!"));
|
||||
|
||||
var httpContext = GetHttpContext();
|
||||
httpContext.Request.Method = "HEAD";
|
||||
var outStream = new MemoryStream();
|
||||
httpContext.Response.Body = outStream;
|
||||
|
||||
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
|
||||
var result = new FileStreamResult(readStream, "text/plain");
|
||||
|
||||
// Act
|
||||
await result.ExecuteResultAsync(actionContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(readStream.CanSeek);
|
||||
Assert.Equal(200, httpContext.Response.StatusCode);
|
||||
Assert.Equal(0, httpContext.Response.Body.Length);
|
||||
}
|
||||
|
||||
private static IServiceCollection CreateServices()
|
||||
|
|
|
|||
|
|
@ -1295,6 +1295,16 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore.InjectedPa
|
|||
Assert.Equal("From ShortCircuitPageAtPageFilter.cshtml", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewDataAvaialableInPageFilter_AfterHandlerMethod_ReturnsPageResult()
|
||||
{
|
||||
// Act
|
||||
var content = await Client.GetStringAsync("http://localhost/Pages/ViewDataAvailableAfterHandlerExecuted");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ViewData: Bar", content);
|
||||
}
|
||||
|
||||
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
|
||||
{
|
||||
var getResponse = await Client.GetAsync(request.RequestUri);
|
||||
|
|
|
|||
|
|
@ -473,6 +473,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
#region Page Filters
|
||||
|
||||
[Fact]
|
||||
public async Task ViewDataIsSet_AfterHandlerMethodIsExecuted()
|
||||
{
|
||||
// Arrange
|
||||
var pageHandlerExecutedCalled = false;
|
||||
var pageFilter = new Mock<IPageFilter>();
|
||||
AllowSelector(pageFilter);
|
||||
pageFilter
|
||||
.Setup(f => f.OnPageHandlerExecuted(It.IsAny<PageHandlerExecutedContext>()))
|
||||
.Callback<PageHandlerExecutedContext>(c =>
|
||||
{
|
||||
pageHandlerExecutedCalled = true;
|
||||
var result = c.Result;
|
||||
var pageResult = Assert.IsType<PageResult>(result);
|
||||
Assert.IsType<ViewDataDictionary<TestPage>>(pageResult.ViewData);
|
||||
Assert.IsType<TestPage>(pageResult.Model);
|
||||
Assert.Null(pageResult.Page);
|
||||
});
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { pageFilter.Object }, result: new PageResult());
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(pageHandlerExecutedCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_InvokesPageFilter()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
@model ViewDataAvailableAfterHandlerExecutedModel
|
||||
@{
|
||||
}
|
||||
ViewData: @ViewData["Foo"]
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace RazorPagesWebSite.Pages
|
||||
{
|
||||
[TestPageFilter]
|
||||
public class ViewDataAvailableAfterHandlerExecutedModel : PageModel
|
||||
{
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
return Page();
|
||||
}
|
||||
|
||||
private class TestPageFilterAttribute : Attribute, IPageFilter
|
||||
{
|
||||
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
|
||||
{
|
||||
// This usage mimics Identity UI where it sets data into ViewData in a PageFilters's
|
||||
// PageHandlerExecuted method.
|
||||
if (context.Result is PageResult pageResult)
|
||||
{
|
||||
pageResult.ViewData["Foo"] = "Bar";
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue