Merge branch 'release/2.1' into dev

This commit is contained in:
Kiran Challa 2018-04-18 11:49:14 -07:00
commit b56436b5c6
11 changed files with 162 additions and 28 deletions

View File

@ -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)
{

View File

@ -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
{

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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()

View File

@ -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);

View File

@ -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()
{

View File

@ -0,0 +1,5 @@
@page
@model ViewDataAvailableAfterHandlerExecutedModel
@{
}
ViewData: @ViewData["Foo"]

View File

@ -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)
{
}
}
}
}