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:
parent
e2594c6a2b
commit
153165f9ad
|
|
@ -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);
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
Loading…
Reference in New Issue