Combine attributes specified on Page and PageModel (#12332)

Fixes https://github.com/aspnet/AspNetCore/issues/11296
This commit is contained in:
Pranav K 2019-07-19 13:15:02 -07:00 committed by GitHub
parent b23ea5b668
commit a77738e52f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 163 additions and 5 deletions

View File

@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;

View File

@ -2,11 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Internal;
@ -98,16 +98,23 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Now we want figure out which type is the handler type.
TypeInfo handlerType;
var pageTypeAttributes = pageTypeInfo.GetCustomAttributes(inherit: true);
object[] handlerTypeAttributes;
if (modelProperty.PropertyType.IsDefined(typeof(PageModelAttribute), inherit: true))
{
handlerType = modelTypeInfo;
// If a PageModel is specified, combine the attributes specified on the Page and the Model type.
// Attributes that appear earlier in the are more significant. In this case, we'll treat attributes on the model (code)
// to be more signficant than the page (code-generated).
handlerTypeAttributes = modelTypeInfo.GetCustomAttributes(inherit: true).Concat(pageTypeAttributes).ToArray();
}
else
{
handlerType = pageTypeInfo;
handlerTypeAttributes = pageTypeInfo.GetCustomAttributes(inherit: true);
}
var handlerTypeAttributes = handlerType.GetCustomAttributes(inherit: true);
var pageModel = new PageApplicationModel(
actionDescriptor,
declaredModelType,

View File

@ -525,6 +525,43 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
}
[Fact]
public void OnProvidersExecuting_CombinesFilters_OnPageAndPageModel()
{
// Arrange
var provider = CreateProvider();
var typeInfo = typeof(PageWithFilters).GetTypeInfo();
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
var pageModel = context.PageApplicationModel;
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<TypeFilterAttribute>(filter),
filter => Assert.IsType<ServiceFilterAttribute>(filter),
filter => Assert.IsType<PageHandlerPageFilter>(filter),
filter => Assert.IsType<HandleOptionsRequestsPageFilter>(filter));
}
[ServiceFilter(typeof(Guid))]
private class PageWithFilters : Page
{
public PageWithFilterModel Model { get; }
public override Task ExecuteAsync() => throw new NotImplementedException();
}
[TypeFilter(typeof(string))]
private class PageWithFilterModel : PageModel
{
}
[ServiceFilter(typeof(IServiceProvider))]
private class FiltersOnPageAndPageModel : PageModel { }
[Fact] // If the model has handler methods, we prefer those.
public void CreateDescriptor_FindsHandlerMethod_OnModel()
{

View File

@ -695,6 +695,40 @@ Hello from /Pages/Shared/";
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
}
[Fact]
public async Task AuthAttribute_AppliedOnPageWorks()
{
// Act
using var response = await Client.GetAsync("/Filters/AuthFilterOnPage");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/Login?ReturnUrl=%2FFilters%2FAuthFilterOnPage", response.Headers.Location.PathAndQuery);
}
[Fact]
public async Task AuthAttribute_AppliedOnPageWithModelWorks()
{
// Act
using var response = await Client.GetAsync("/Filters/AuthFilterOnPageWithModel");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/Login?ReturnUrl=%2FFilters%2FAuthFilterOnPageWithModel", response.Headers.Location.PathAndQuery);
}
[Fact]
public async Task FiltersAppliedToPageAndPageModelAreExecuted()
{
// Act
using var response = await Client.GetAsync("/Filters/FiltersAppliedToPageAndPageModel");
// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
Assert.Equal(new[] { "PageModelFilterValue" }, response.Headers.GetValues("PageModelFilterKey"));
Assert.Equal(new[] { "PageFilterValue" }, response.Headers.GetValues("PageFilterKey"));
}
private async Task AddAntiforgeryHeadersAsync(HttpRequestMessage request)
{
var response = await Client.GetAsync(request.RequestUri);

View File

@ -0,0 +1,3 @@
@page
@attribute [Authorize]

View File

@ -0,0 +1,14 @@
// 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 Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite.Pages.Filters
{
[SkipStatusCodePages]
public class AuthFilterOnPageWithModel : PageModel
{
public IActionResult OnGet() => Page();
}
}

View File

@ -0,0 +1,3 @@
@page
@model AuthFilterOnPageWithModel
@attribute [Authorize]

View File

@ -0,0 +1,14 @@
// 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 Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite.Pages.Filters
{
[TestPageModelFilter]
public class FiltersAppliedToPageAndPageModel : PageModel
{
public IActionResult OnGet() => Page();
}
}

View File

@ -0,0 +1,3 @@
@page
@model FiltersAppliedToPageAndPageModel
@attribute [TestPageFilter]

View File

@ -0,0 +1,24 @@
// 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.Filters;
namespace RazorPagesWebSite.Pages.Filters
{
public class TestPageFilter : Attribute, IPageFilter
{
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
context.HttpContext.Response.Headers["PageFilterKey"] = "PageFilterValue";
}
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Filters;
namespace RazorPagesWebSite.Pages.Filters
{
public class TestPageModelFilter : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Response.Headers["PageModelFilterKey"] = "PageModelFilterValue";
}
}
}

View File

@ -0,0 +1 @@
@using Microsoft.AspNetCore.Authorization