Cache more things in HandlerMethodDescriptor

Add tests for DefaultPageHandlerMethodSelector
This commit is contained in:
Pranav K 2017-02-28 17:42:24 -08:00
parent 7b53ba1f6b
commit 4faef7afaf
7 changed files with 727 additions and 97 deletions

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
@ -12,5 +13,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public MethodInfo Method { get; set; } public MethodInfo Method { get; set; }
public Func<Page, object, Task<IActionResult>> Executor { get; set; } public Func<Page, object, Task<IActionResult>> Executor { get; set; }
public string HttpMethod { get; set; }
public StringSegment FormAction { get; set; }
} }
} }

View File

@ -3,140 +3,106 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
public class DefaultPageHandlerMethodSelector : IPageHandlerMethodSelector public class DefaultPageHandlerMethodSelector : IPageHandlerMethodSelector
{ {
private const string FormAction = "formaction";
public HandlerMethodDescriptor Select(PageContext context) public HandlerMethodDescriptor Select(PageContext context)
{ {
var handlers = new List<HandlerMethodAndMetadata>(context.ActionDescriptor.HandlerMethods.Count); var handlers = SelectHandlers(context);
for (var i = 0; i < context.ActionDescriptor.HandlerMethods.Count; i++) if (handlers == null || handlers.Count == 0)
{ {
handlers.Add(HandlerMethodAndMetadata.Create(context.ActionDescriptor.HandlerMethods[i])); return null;
} }
for (var i = handlers.Count - 1; i >= 0; i--) List<HandlerMethodDescriptor> ambiguousMatches = null;
HandlerMethodDescriptor bestMatch = null;
for (var score = 2; score >= 0; score--)
{ {
var handler = handlers[i]; for (var i = 0; i < handlers.Count; i++)
if (handler.HttpMethod != null &&
!string.Equals(handler.HttpMethod, context.HttpContext.Request.Method, StringComparison.OrdinalIgnoreCase))
{ {
handlers.RemoveAt(i); var handler = handlers[i];
} if (GetScore(handler) == score)
}
var formaction = Convert.ToString(context.RouteData.Values["formaction"]);
for (var i = handlers.Count - 1; i >= 0; i--)
{
var handler = handlers[i];
if (handler.Formaction != null &&
!string.Equals(handler.Formaction, formaction, StringComparison.OrdinalIgnoreCase))
{
handlers.RemoveAt(i);
}
}
var ambiguousMatches = (List<HandlerMethodDescriptor>)null;
var best = (HandlerMethodAndMetadata?)null;
for (var i = 2; i >= 0; i--)
{
for (var j = 0; j < handlers.Count; j++)
{
var handler = handlers[j];
if (handler.GetScore() == i)
{ {
if (best == null) if (bestMatch == null)
{ {
best = handler; bestMatch = handler;
continue; continue;
} }
if (ambiguousMatches == null) if (ambiguousMatches == null)
{ {
ambiguousMatches = new List<HandlerMethodDescriptor>(); ambiguousMatches = new List<HandlerMethodDescriptor>();
ambiguousMatches.Add(best.Value.Handler); ambiguousMatches.Add(bestMatch);
} }
ambiguousMatches.Add(handler.Handler); ambiguousMatches.Add(handler);
} }
} }
if (ambiguousMatches != null) if (ambiguousMatches != null)
{ {
throw new InvalidOperationException($"Selecting a handler is ambiguous! Matches: {string.Join(", ", ambiguousMatches)}"); var ambiguousMethods = string.Join(", ", ambiguousMatches.Select(m => m.Method));
throw new InvalidOperationException(Resources.FormatAmbiguousHandler(Environment.NewLine, ambiguousMethods));
} }
if (best != null) if (bestMatch != null)
{ {
return best.Value.Handler; return bestMatch;
} }
} }
return null; return null;
} }
// Bad prototype substring implementation :) private static List<HandlerMethodDescriptor> SelectHandlers(PageContext context)
private struct HandlerMethodAndMetadata
{ {
public static HandlerMethodAndMetadata Create(HandlerMethodDescriptor handler) var handlers = context.ActionDescriptor.HandlerMethods;
List<HandlerMethodDescriptor> handlersToConsider = null;
var formAction = Convert.ToString(context.RouteData.Values[FormAction]);
for (var i = 0; i < handlers.Count; i++)
{ {
var name = handler.Method.Name; var handler = handlers[i];
if (handler.HttpMethod != null &&
string httpMethod; !string.Equals(handler.HttpMethod, context.HttpContext.Request.Method, StringComparison.OrdinalIgnoreCase))
if (name.StartsWith("OnGet", StringComparison.Ordinal))
{ {
httpMethod = "GET"; continue;
} }
else if (name.StartsWith("OnPost", StringComparison.Ordinal)) else if (handler.FormAction.HasValue &&
!handler.FormAction.Equals(formAction, StringComparison.OrdinalIgnoreCase))
{ {
httpMethod = "POST"; continue;
}
else
{
httpMethod = null;
} }
var formactionStart = httpMethod?.Length + 2 ?? 0; if (handlersToConsider == null)
var formactionLength = name.EndsWith("Async", StringComparison.Ordinal) {
? name.Length - formactionStart - "Async".Length handlersToConsider = new List<HandlerMethodDescriptor>();
: name.Length - formactionStart; }
var formaction = formactionLength == 0 ? null : name.Substring(formactionStart, formactionLength); handlersToConsider.Add(handler);
return new HandlerMethodAndMetadata(handler, httpMethod, formaction);
} }
public HandlerMethodAndMetadata(HandlerMethodDescriptor handler, string httpMethod, string formaction) return handlersToConsider;
}
private static int GetScore(HandlerMethodDescriptor descriptor)
{
if (descriptor.FormAction != null)
{ {
Handler = handler; return 2;
HttpMethod = httpMethod;
Formaction = formaction;
} }
else if (descriptor.HttpMethod != null)
public HandlerMethodDescriptor Handler { get; }
public string HttpMethod { get; }
public string Formaction { get; }
public int GetScore()
{ {
if (Formaction != null) return 1;
{ }
return 2; else
} {
else if (HttpMethod != null) return 0;
{
return 1;
}
else
{
return 0;
}
} }
} }
} }

View File

@ -18,12 +18,12 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.Evolution; using Microsoft.AspNetCore.Razor.Evolution;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
public class PageActionInvokerProvider : IActionInvokerProvider public class PageActionInvokerProvider : IActionInvokerProvider
{ {
private static readonly string[] _handlerMethodNames = new string[] { "OnGet", "OnPost" };
private const string PageStartFileName = "_PageStart.cshtml"; private const string PageStartFileName = "_PageStart.cshtml";
private readonly IPageLoader _loader; private readonly IPageLoader _loader;
private readonly IPageFactoryProvider _pageFactoryProvider; private readonly IPageFactoryProvider _pageFactoryProvider;
@ -204,7 +204,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var pageStartItems = _razorProject.FindHierarchicalItems(descriptor.ViewEnginePath, PageStartFileName); var pageStartItems = _razorProject.FindHierarchicalItems(descriptor.ViewEnginePath, PageStartFileName);
foreach (var item in pageStartItems) foreach (var item in pageStartItems)
{ {
if(item.Exists) if (item.Exists)
{ {
var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path); var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path);
if (factoryResult.Success) if (factoryResult.Success)
@ -220,21 +220,91 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Internal for testing. // Internal for testing.
internal static void PopulateHandlerMethodDescriptors(TypeInfo type, CompiledPageActionDescriptor actionDescriptor) internal static void PopulateHandlerMethodDescriptors(TypeInfo type, CompiledPageActionDescriptor actionDescriptor)
{ {
for (var i = 0; i < _handlerMethodNames.Length; i++) var methods = type.GetMethods();
for (var i = 0; i < methods.Length; i++)
{ {
var methodName = _handlerMethodNames[i]; var method = methods[i];
var method = type.GetMethod(methodName); if (!IsValidHandler(method))
if (method != null && !method.IsGenericMethod)
{ {
actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor() continue;
{
Method = method,
Executor = ExecutorFactory.CreateExecutor(actionDescriptor, method),
});
} }
string httpMethod;
int formActionStart;
if (method.Name.StartsWith("OnGet", StringComparison.Ordinal))
{
httpMethod = "GET";
formActionStart = "OnGet".Length;
}
else if (method.Name.StartsWith("OnPost", StringComparison.Ordinal))
{
httpMethod = "POST";
formActionStart = "OnPost".Length;
}
else
{
continue;
}
var formActionLength = method.Name.Length - formActionStart;
if (method.Name.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
{
formActionLength -= "Async".Length;
}
var formAction = new StringSegment(method.Name, formActionStart, formActionLength);
var handlerMethodDescriptor = new HandlerMethodDescriptor
{
Method = method,
Executor = ExecutorFactory.CreateExecutor(actionDescriptor, method),
FormAction = formAction,
HttpMethod = httpMethod,
};
actionDescriptor.HandlerMethods.Add(handlerMethodDescriptor);
} }
} }
private static bool IsValidHandler(MethodInfo methodInfo)
{
// The SpecialName bit is set to flag members that are treated in a special way by some compilers
// (such as property accessors and operator overloading methods).
if (methodInfo.IsSpecialName)
{
return false;
}
// Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
{
return false;
}
if (methodInfo.IsStatic)
{
return false;
}
if (methodInfo.IsAbstract)
{
return false;
}
if (methodInfo.IsConstructor)
{
return false;
}
if (methodInfo.IsGenericMethod)
{
return false;
}
return methodInfo.IsPublic;
}
internal class InnerCache internal class InnerCache
{ {
public InnerCache(int version) public InnerCache(int version)

View File

@ -106,6 +106,22 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
return string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedHandlerMethodType"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedHandlerMethodType"), p0);
} }
/// <summary>
/// Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1}
/// </summary>
internal static string AmbiguousHandler
{
get { return GetString("AmbiguousHandler"); }
}
/// <summary>
/// Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1}
/// </summary>
internal static string FormatAmbiguousHandler(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AmbiguousHandler"), p0, p1);
}
/// <summary> /// <summary>
/// Path must be an application relative path that starts with a forward slash '/'. /// Path must be an application relative path that starts with a forward slash '/'.
/// </summary> /// </summary>

View File

@ -135,6 +135,9 @@
<data name="UnsupportedHandlerMethodType" xml:space="preserve"> <data name="UnsupportedHandlerMethodType" xml:space="preserve">
<value>Unsupported handler method return type '{0}'.</value> <value>Unsupported handler method return type '{0}'.</value>
</data> </data>
<data name="AmbiguousHandler" xml:space="preserve">
<value>Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1}</value>
</data>
<data name="PathMustBeAnAppRelativePath" xml:space="preserve"> <data name="PathMustBeAnAppRelativePath" xml:space="preserve">
<value>Path must be an application relative path that starts with a forward slash '/'.</value> <value>Path must be an application relative path that starts with a forward slash '/'.</value>
</data> </data>

View File

@ -0,0 +1,406 @@
// 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.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class DefaultPageHandlerMethodSelectorTest
{
[Fact]
public void Select_ReturnsNull_WhenNoHandlerMatchesHttpMethod()
{
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "GET"
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST"
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor1,
descriptor2,
},
},
RouteData = new RouteData(),
HttpContext = new DefaultHttpContext
{
Request =
{
Method = "PUT"
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act
var actual = selector.Select(pageContext);
// Assert
Assert.Null(actual);
}
[Fact]
public void Select_ReturnsOnlyHandler()
{
// Arrange
var descriptor = new HandlerMethodDescriptor
{
HttpMethod = "GET"
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor,
},
},
RouteData = new RouteData(),
HttpContext = new DefaultHttpContext
{
Request =
{
Method = "GET"
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act
var actual = selector.Select(pageContext);
// Assert
Assert.Same(descriptor, actual);
}
[Theory]
[InlineData("GET")]
[InlineData("POST")]
public void Select_ReturnsHandlerWithMatchingHttpRequestMethod(string httpMethod)
{
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "PUT",
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = httpMethod,
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor1,
descriptor2,
},
},
RouteData = new RouteData(),
HttpContext = new DefaultHttpContext
{
Request =
{
Method = httpMethod,
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act
var actual = selector.Select(pageContext);
// Assert
Assert.Same(descriptor2, actual);
}
[Fact]
public void Select_ReturnsNullWhenNoHandlerMatchesFormAction()
{
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Delete"),
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor1,
descriptor2,
},
},
RouteData = new RouteData
{
Values =
{
{ "formaction", "update" }
}
},
HttpContext = new DefaultHttpContext
{
Request =
{
Method = "POST"
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act
var actual = selector.Select(pageContext);
// Assert
Assert.Null(actual);
}
[Fact]
public void Select_ReturnsHandlerThatMatchesFormAction()
{
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Delete"),
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor1,
descriptor2,
},
},
RouteData = new RouteData
{
Values =
{
{ "formaction", "Add" }
}
},
HttpContext = new DefaultHttpContext
{
Request =
{
Method = "Post"
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act
var actual = selector.Select(pageContext);
// Assert
Assert.Same(descriptor1, actual);
}
[Fact]
public void Select_ReturnsHandlerWithMatchingHttpMethodWithoutAFormAction()
{
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Subscribe"),
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor1,
descriptor2,
},
},
RouteData = new RouteData
{
Values =
{
{ "formaction", "Add" }
}
},
HttpContext = new DefaultHttpContext
{
Request =
{
Method = "Post"
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act
var actual = selector.Select(pageContext);
// Assert
Assert.Same(descriptor2, actual);
}
[Fact]
public void Select_WithoutFormAction_ThrowsIfMoreThanOneHandlerMatches()
{
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(Post)),
HttpMethod = "POST",
};
var descriptor2 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(PostAsync)),
HttpMethod = "POST",
};
var descriptor3 = new HandlerMethodDescriptor
{
HttpMethod = "GET",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor1,
descriptor2,
descriptor3,
},
},
RouteData = new RouteData(),
HttpContext = new DefaultHttpContext
{
Request =
{
Method = "Post"
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => selector.Select(pageContext));
var methods = descriptor1.Method + ", " + descriptor2.Method;
var message = "Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:" +
Environment.NewLine + Environment.NewLine + methods;
Assert.Equal(message, ex.Message);
}
[Fact]
public void Select_WithFormAction_ThrowsIfMoreThanOneHandlerMatches()
{
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(Post)),
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
};
var descriptor2 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(PostAsync)),
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
};
var descriptor3 = new HandlerMethodDescriptor
{
HttpMethod = "GET",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
{
descriptor1,
descriptor2,
descriptor3,
},
},
RouteData = new RouteData
{
Values =
{
{ "formaction", "Add" }
}
},
HttpContext = new DefaultHttpContext
{
Request =
{
Method = "Post"
},
},
};
var selector = new DefaultPageHandlerMethodSelector();
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => selector.Select(pageContext));
var methods = descriptor1.Method + ", " + descriptor2.Method;
var message = "Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:" +
Environment.NewLine + Environment.NewLine + methods;
Assert.Equal(message, ex.Message);
}
public void Post()
{
}
public void PostAsync()
{
}
}
}

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
@ -382,6 +383,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(typeof(InheritsMethods), handler.Method.DeclaringType); Assert.Equal(typeof(InheritsMethods), handler.Method.DeclaringType);
}, },
(handler) => (handler) =>
{
Assert.Equal("OnGet", handler.Method.Name);
Assert.Equal(typeof(TestSetPageModel), handler.Method.DeclaringType);
},
(handler) =>
{ {
Assert.Equal("OnPost", handler.Method.Name); Assert.Equal("OnPost", handler.Method.Name);
Assert.Equal(typeof(TestSetPageModel), handler.Method.DeclaringType); Assert.Equal(typeof(TestSetPageModel), handler.Method.DeclaringType);
@ -389,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
[Fact] [Fact]
public void PopulateHandlerMethodDescriptors_ProtectedMethodsNotFound() public void PopulateHandlerMethodDescriptors_IgnoresNonPublicMethods()
{ {
// Arrange // Arrange
var descriptor = new PageActionDescriptor() var descriptor = new PageActionDescriptor()
@ -432,6 +438,113 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Empty(actionDescriptor.HandlerMethods); Assert.Empty(actionDescriptor.HandlerMethods);
} }
[Fact]
public void PopulateHandlerMethodDescriptors_IgnoresStaticMethods()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Index.cshtml"
};
var modelTypeInfo = typeof(PageModelWithStaticHandler).GetTypeInfo();
var expected = modelTypeInfo.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance);
var actionDescriptor = new CompiledPageActionDescriptor(descriptor)
{
ModelTypeInfo = modelTypeInfo,
PageTypeInfo = typeof(object).GetTypeInfo(),
};
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(modelTypeInfo, actionDescriptor);
// Assert
Assert.Collection(actionDescriptor.HandlerMethods,
handler => Assert.Same(expected, handler.Method));
}
[Fact]
public void PopulateHandlerMethodDescriptors_IgnoresAbstractMethods()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Index.cshtml"
};
var modelTypeInfo = typeof(PageModelWithAbstractMethod).GetTypeInfo();
var expected = modelTypeInfo.GetMethod(nameof(PageModelWithAbstractMethod.OnGet));
var actionDescriptor = new CompiledPageActionDescriptor(descriptor)
{
ModelTypeInfo = modelTypeInfo,
PageTypeInfo = typeof(object).GetTypeInfo(),
};
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(modelTypeInfo, actionDescriptor);
// Assert
Assert.Collection(actionDescriptor.HandlerMethods,
handler => Assert.Same(expected, handler.Method));
}
[Fact]
public void PopulateHandlerMethodDescriptors_DiscoversMethodsWithFormActions()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Index.cshtml"
};
var modelTypeInfo = typeof(PageModelWithFormActions).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor(descriptor)
{
ModelTypeInfo = modelTypeInfo,
PageTypeInfo = typeof(object).GetTypeInfo(),
};
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(modelTypeInfo, actionDescriptor);
// Assert
Assert.Collection(actionDescriptor.HandlerMethods.OrderBy(h => h.Method.Name),
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnGet)), handler.Method);
Assert.Equal("GET", handler.HttpMethod);
Assert.Equal(0, handler.FormAction.Length);
Assert.NotNull(handler.Executor);
},
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnPostAdd)), handler.Method);
Assert.Equal("POST", handler.HttpMethod);
Assert.Equal("Add", handler.FormAction.ToString());
Assert.NotNull(handler.Executor);
},
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnPostAddCustomer)), handler.Method);
Assert.Equal("POST", handler.HttpMethod);
Assert.Equal("AddCustomer", handler.FormAction.ToString());
Assert.NotNull(handler.Executor);
},
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnPostDeleteAsync)), handler.Method);
Assert.Equal("POST", handler.HttpMethod);
Assert.Equal("Delete", handler.FormAction.ToString());
Assert.NotNull(handler.Executor);
});
}
[Fact] [Fact]
public void PopulateHandlerMethodDescriptors_AllowOnlyOneMethod() public void PopulateHandlerMethodDescriptors_AllowOnlyOneMethod()
{ {
@ -635,6 +748,57 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
} }
private class PageModelWithStaticHandler
{
public static void OnGet(string name)
{
}
public void OnGet()
{
}
}
private abstract class PageModelWithAbstractMethod
{
public abstract void OnPost(string name);
public void OnGet()
{
}
}
private class PageModelWithFormActions
{
public void OnGet()
{
}
public void OnPostAdd()
{
}
public void OnPostAddCustomer()
{
}
public void OnPostDeleteAsync()
{
}
protected void OnPostDelete()
{
}
}
private class ProtectedModel private class ProtectedModel
{ {
protected void OnGet() protected void OnGet()