Handle OPTIONS requests without a handler in Razor Pages (#8528)

* Handle OPTIONS requests without a handler in Razor Pages

Fixes #7438
This commit is contained in:
Pranav K 2018-10-04 12:25:36 -07:00 committed by GitHub
parent e2594c6a2b
commit 153165f9ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 396 additions and 25 deletions

View File

@ -3,36 +3,43 @@
using System;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
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.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Options;
using Resources = Microsoft.AspNetCore.Mvc.RazorPages.Resources;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
public class DefaultPageApplicationModelProvider : IPageApplicationModelProvider
internal class DefaultPageApplicationModelProvider : IPageApplicationModelProvider
{
private const string ModelPropertyName = "Model";
private readonly PageHandlerPageFilter _pageHandlerPageFilter = new PageHandlerPageFilter();
private readonly PageHandlerResultFilter _pageHandlerResultFilter = new PageHandlerResultFilter();
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly MvcOptions _options;
private readonly MvcOptions _mvcOptions;
private readonly RazorPagesOptions _razorPagesOptions;
private readonly Func<ActionContext, bool> _supportsAllRequests;
private readonly Func<ActionContext, bool> _supportsNonGetRequests;
private readonly HandleOptionsRequestsPageFilter _handleOptionsRequestsFilter;
public DefaultPageApplicationModelProvider(
IModelMetadataProvider modelMetadataProvider,
IOptions<MvcOptions> options)
IOptions<MvcOptions> options,
IOptions<RazorPagesOptions> razorPagesOptions)
{
_modelMetadataProvider = modelMetadataProvider;
_options = options.Value;
_mvcOptions = options.Value;
_razorPagesOptions = razorPagesOptions.Value;
_supportsAllRequests = _ => true;
_supportsNonGetRequests = context => !string.Equals(context.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase);
_supportsNonGetRequests = context => !HttpMethods.IsGet(context.HttpContext.Request.Method);
_handleOptionsRequestsFilter = new HandleOptionsRequestsPageFilter();
}
/// <inheritdoc />
@ -175,6 +182,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
pageModel.Filters.Add(_pageHandlerResultFilter);
}
if (_razorPagesOptions.AllowDefaultHandlingForOptionsRequests)
{
pageModel.Filters.Add(_handleOptionsRequestsFilter);
}
}
/// <summary>
@ -237,7 +249,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var attributes = parameter.GetCustomAttributes(inherit: true);
BindingInfo bindingInfo;
if (_options.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
if (_mvcOptions.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
{
var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameter);
bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);

View File

@ -0,0 +1,55 @@
// 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.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
/// <summary>
/// A filter that handles OPTIONS requests page when no handler method is available.
/// <para>
/// a) MVC treats no handler being selected no differently than a page having no handler, both execute the
/// page.
/// b) A common model for programming Razor Pages is to initialize content required by a page in the
/// <c>OnGet</c> handler. Executing a page without running the handler may result in runtime exceptions -
/// e.g. null ref or out of bounds exception if you expected a property or collection to be initialized.
/// </para>
/// <para>
/// Some web crawlers use OPTIONS request when probing servers. In the absence of an uncommon <c>OnOptions</c>
/// handler, executing the page will likely result in runtime errors as described in earlier. This filter
/// attempts to avoid this pit of failure by handling OPTIONS requests and returning a 200 if no handler is selected.
/// </para>
/// </summary>
internal sealed class HandleOptionsRequestsPageFilter : IPageFilter, IOrderedFilter
{
/// <summary>
/// Ordered to run after filters with default order.
/// </summary>
public int Order => 1000;
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.HandlerMethod == null &&
context.Result == null &&
HttpMethods.IsOptions(context.HttpContext.Request.Method))
{
context.Result = new OkResult();
}
}
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
}
}

View File

@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
{
private readonly CompatibilitySwitch<bool> _allowAreas;
private readonly CompatibilitySwitch<bool> _allowMappingHeadRequestsToGetHandler;
private readonly CompatibilitySwitch<bool> _allowsDefaultHandlingForOptionsRequests;
private readonly ICompatibilitySwitch[] _switches;
private string _root = "/Pages";
@ -24,11 +25,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
{
_allowAreas = new CompatibilitySwitch<bool>(nameof(AllowAreas));
_allowMappingHeadRequestsToGetHandler = new CompatibilitySwitch<bool>(nameof(AllowMappingHeadRequestsToGetHandler));
_allowsDefaultHandlingForOptionsRequests = new CompatibilitySwitch<bool>(nameof(AllowDefaultHandlingForOptionsRequests));
_switches = new ICompatibilitySwitch[]
{
_allowAreas,
_allowMappingHeadRequestsToGetHandler,
_allowsDefaultHandlingForOptionsRequests,
};
}
@ -134,6 +137,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
set => _allowMappingHeadRequestsToGetHandler.Value = value;
}
/// <summary>
/// Gets or sets a value that determines if HTTP requests with the OPTIONS method are handled by default, if
/// no handler is available.
/// </summary>
/// <value>
/// The default value is <see langword="true"/> if the version is
/// <see cref="CompatibilityVersion.Version_2_2"/> or later; <see langword="false"/> otherwise.
/// </value>
/// <remarks>
/// <para>
/// Razor Pages uses the current request's HTTP method to select a handler method. When no handler is available or selected,
/// the page is immediately executed. This may cause runtime errors if the page relies on the handler method to execute
/// and initialize some state. This setting attempts to avoid this class of error for HTTP <c>OPTIONS</c> requests by
/// returning a <c>200 OK</c> response.
/// </para>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired of the value compatibility switch by calling this property's setter will take precedence
/// over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> then
/// this setting will have value <c>true</c> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// lower then this setting will have value <c>true</c> unless explicitly configured.
/// </para>
/// </remarks>
public bool AllowDefaultHandlingForOptionsRequests
{
get => _allowsDefaultHandlingForOptionsRequests.Value;
set => _allowsDefaultHandlingForOptionsRequests.Value = value;
}
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();

View File

@ -29,6 +29,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
values[nameof(RazorPagesOptions.AllowMappingHeadRequestsToGetHandler)] = true;
}
if (Version >= CompatibilityVersion.Version_2_2)
{
values[nameof(RazorPagesOptions.AllowDefaultHandlingForOptionsRequests)] = true;
}
return values;
}
}

View File

@ -1431,6 +1431,54 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore.InjectedPa
Assert.Equal("ViewData: Bar", content);
}
[Fact]
public async Task OptionsRequest_WithoutHandler_Returns200_WithoutExecutingPage()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Options, "http://localhost/HelloWorld");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Empty(content.Trim());
}
[Fact]
public async Task PageWithOptionsHandler_ExecutesGetRequest()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/HelloWorldWithOptionsHandler");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello from OnGet!", content.Trim());
}
[Fact]
public async Task PageWithOptionsHandler_ExecutesOptionsRequest()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Options, "http://localhost/HelloWorldWithOptionsHandler");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello from OnOptions!", content.Trim());
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);

View File

@ -6,15 +6,16 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
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.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
public class DefaultPageApplicationModelProviderTest
{
@ -887,7 +888,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange
var provider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false }));
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false }),
Options.Create(new RazorPagesOptions()));
var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo();
var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost));
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]);
@ -921,11 +923,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { }
}
// We're using PropertyHelper from Common to find the properties here, which implements
// We're using PropertyHelper from Common to find the properties here, which implements
// out standard set of semantics for properties that the framework interacts with.
//
// One of the desirable consequences of that is we only find 'visible' properties. We're not
// retesting all of the details of PropertyHelper here, just the visibility part as a quick check
//
// One of the desirable consequences of that is we only find 'visible' properties. We're not
// retesting all of the details of PropertyHelper here, just the visibility part as a quick check
// that we're using PropertyHelper as expected.
[Fact]
public void PopulateHandlerProperties_UsesPropertyHelpers_ToFindProperties()
@ -1071,6 +1073,41 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public void OnGetUser() { }
}
[Fact]
public void PopulateFilters_With21CompatBehavior_DoesNotAddDisallowOptionsRequestsPageFilter()
{
// Arrange
var provider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions()),
Options.Create(new RazorPagesOptions()));
var typeInfo = typeof(object).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true));
// Act
provider.PopulateFilters(pageModel);
// Assert
Assert.Empty(pageModel.Filters);
}
[Fact]
public void PopulateFilters_AddsDisallowOptionsRequestsPageFilter()
{
// Arrange
var provider = CreateProvider();
var typeInfo = typeof(object).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true));
// Act
provider.PopulateFilters(pageModel);
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<HandleOptionsRequestsPageFilter>(filter));
}
[Fact]
public void PopulateFilters_AddsIFilterMetadataAttributesToModel()
{
@ -1085,7 +1122,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<TypeFilterAttribute>(filter));
filter => Assert.IsType<TypeFilterAttribute>(filter),
filter => Assert.IsType<HandleOptionsRequestsPageFilter>(filter));
}
[PageModel]
@ -1109,7 +1147,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<PageHandlerPageFilter>(filter));
filter => Assert.IsType<PageHandlerPageFilter>(filter),
filter => Assert.IsType<HandleOptionsRequestsPageFilter>(filter));
}
private class ModelImplementingAsyncPageFilter : IAsyncPageFilter
@ -1139,7 +1178,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<PageHandlerPageFilter>(filter));
filter => Assert.IsType<PageHandlerPageFilter>(filter),
filter => Assert.IsType<HandleOptionsRequestsPageFilter>(filter));
}
private class ModelImplementingPageFilter : IPageFilter
@ -1175,7 +1215,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<ServiceFilterAttribute>(filter),
filter => Assert.IsType<PageHandlerPageFilter>(filter));
filter => Assert.IsType<PageHandlerPageFilter>(filter),
filter => Assert.IsType<HandleOptionsRequestsPageFilter>(filter));
}
[ServiceFilter(typeof(IServiceProvider))]
@ -1185,7 +1226,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
return new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }));
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }),
Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true }));
}
}
}

View File

@ -0,0 +1,135 @@
// 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 System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class DisallowOptionsRequestsPageFilterTest
{
[Fact]
public void OnPageHandlerExecuting_DoesNothing_IfHandlerIsSelected()
{
// Arrange
var context = GetContext(new HandlerMethodDescriptor());
var filter = new HandleOptionsRequestsPageFilter();
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Null(context.Result);
}
[Fact]
public void OnPageHandlerExecuting_DoesNotOverwriteResult_IfHandlerIsSelected()
{
// Arrange
var expected = new PageResult();
var context = GetContext(new HandlerMethodDescriptor());
context.Result = expected;
var filter = new HandleOptionsRequestsPageFilter();
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Same(expected, context.Result);
}
[Fact]
public void OnPageHandlerExecuting_DoesNothing_IfHandlerIsNotSelected_WhenRequestsIsNotOptions()
{
// Arrange
var context = GetContext(handlerMethodDescriptor: null);
context.HttpContext.Request.Method = "PUT";
var filter = new HandleOptionsRequestsPageFilter();
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Null(context.Result);
}
[Fact]
public void OnPageHandlerExecuting_DoesNotOverwriteResult_IfHandlerIsNotSelected_WhenRequestsIsNotOptions()
{
// Arrange
var expected = new PageResult();
var context = GetContext(handlerMethodDescriptor: null);
context.HttpContext.Request.Method = "DELETE";
context.Result = expected;
var filter = new HandleOptionsRequestsPageFilter();
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Same(expected, context.Result);
}
[Fact]
public void OnPageHandlerExecuting_DoesNothing_ForOptionsRequestWhenHandlerIsSelected()
{
// Arrange
var context = GetContext(new HandlerMethodDescriptor());
context.HttpContext.Request.Method = "Options";
var filter = new HandleOptionsRequestsPageFilter();
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Null(context.Result);
}
[Fact]
public void OnPageHandlerExecuting_DoesNotOverwriteResult_ForOptionsRequestWhenNoHandler()
{
// Arrange
var expected = new NotFoundResult();
var context = GetContext(new HandlerMethodDescriptor());
context.Result = expected;
context.HttpContext.Request.Method = "Options";
var filter = new HandleOptionsRequestsPageFilter();
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Same(expected, context.Result);
}
[Fact]
public void OnPageHandlerExecuting_SetsResult_ForOptionsRequestWhenNoHandlerIsSelected()
{
// Arrange
var context = GetContext(handlerMethodDescriptor: null);
context.HttpContext.Request.Method = "Options";
var filter = new HandleOptionsRequestsPageFilter();
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.IsType<OkResult>(context.Result);
}
private static PageHandlerExecutingContext GetContext(HandlerMethodDescriptor handlerMethodDescriptor)
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new PageActionDescriptor());
var pageContext = new PageContext(actionContext);
return new PageHandlerExecutingContext(pageContext, Array.Empty<IFilterMetadata>(), handlerMethodDescriptor, new Dictionary<string, object>(), new object());
}
}
}

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Options;
using Xunit;
@ -30,7 +31,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f));
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f));
}
private class PageWithAuthorizeHandlers : Page
@ -63,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f),
f => Assert.IsType<AuthorizeFilter>(f));
}
@ -102,6 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f),
f => authorizeFilter = Assert.IsType<AuthorizeFilter>(f));
// Basic + Basic2 + Derived authorize
@ -143,6 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f),
f => Assert.IsType<AllowAnonymousFilter>(f));
}
@ -163,7 +168,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
var defaultProvider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }));
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }),
Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true }));
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
defaultProvider.OnProvidersExecuting(context);
return context;

View File

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
@ -31,7 +32,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f));
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f));
}
private class PageWithoutResponseCache : Page
@ -66,6 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
context.PageApplicationModel.Filters,
f => { },
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f),
f =>
{
var filter = Assert.IsType<ResponseCacheFilter>(f);
@ -112,6 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
context.PageApplicationModel.Filters,
f => { },
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f),
f =>
{
var filter = Assert.IsType<ResponseCacheFilter>(f);
@ -139,7 +143,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
var defaultProvider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions()));
Options.Create(new MvcOptions()),
Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true }));
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
defaultProvider.OnProvidersExecuting(context);
return context;

View File

@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
}
[Fact]
@ -80,6 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
}
[Fact]
@ -111,6 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
}
[Fact]
@ -142,6 +145,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
}
// This just does the minimum needed to be able to resolve these options.

View File

@ -0,0 +1,17 @@
@page
@functions {
public string Source { get; set; }
public void OnGet()
{
Source = "OnGet";
}
public void OnOptions(string message)
{
Source = "OnOptions";
}
}
Hello from @Source!