diff --git a/samples/MvcSandbox/Models/TestModel.cs b/samples/MvcSandbox/Models/TestModel.cs
index 49c72dc2aa..a3233fd0e8 100644
--- a/samples/MvcSandbox/Models/TestModel.cs
+++ b/samples/MvcSandbox/Models/TestModel.cs
@@ -1,9 +1,19 @@
-
+// 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 MvcSandbox
{
- public class TestModel
+ public class TestModel : PageModel
{
- public string Name { get; set; }
+ public string Name { get; private set; } = "World";
+ public IActionResult OnPost(string name)
+ {
+ Name = name;
+ return View();
+ }
}
}
diff --git a/samples/MvcSandbox/Pages/Index.cshtml b/samples/MvcSandbox/Pages/Index.cshtml
index 1cc8dea003..702b1005ce 100644
--- a/samples/MvcSandbox/Pages/Index.cshtml
+++ b/samples/MvcSandbox/Pages/Index.cshtml
@@ -3,9 +3,15 @@
-
RazorPages Test
+
RazorPages says Hello @Model.Name!
This file should give you a quick view of a Mvc Raor Page in action.
+
+
+
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageModelConvention.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs
similarity index 65%
rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageModelConvention.cs
rename to src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs
index 752cd81f49..2dbe608d8c 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageModelConvention.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs
@@ -4,14 +4,14 @@
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
///
- /// Allows customization of the of the .
+ /// Allows customization of the of the .
///
public interface IPageModelConvention
{
///
- /// Called to apply the convention to the .
+ /// Called to apply the convention to the .
///
- /// The .
- void Apply(PageModel model);
+ /// The .
+ void Apply(PageApplicationModel model);
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs
similarity index 84%
rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageModel.cs
rename to src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs
index 9c080aed1c..a5ff863070 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageModel.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs
@@ -11,14 +11,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
///
/// Application model component for RazorPages.
///
- public class PageModel
+ public class PageApplicationModel
{
///
- /// Initializes a new instance of .
+ /// Initializes a new instance of .
///
/// The application relative path of the page.
/// The path relative to the base path for page discovery.
- public PageModel(string relativePath, string viewEnginePath)
+ public PageApplicationModel(string relativePath, string viewEnginePath)
{
if (relativePath == null)
{
@@ -39,10 +39,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
}
///
- /// A copy constructor for .
+ /// A copy constructor for .
///
- /// The to copy from.
- public PageModel(PageModel other)
+ /// The to copy from.
+ public PageApplicationModel(PageApplicationModel other)
{
if (other == null)
{
@@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public IList Filters { get; }
///
- /// Stores arbitrary metadata properties associated with the .
+ /// Stores arbitrary metadata properties associated with the .
///
public IDictionary Properties { get; }
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs
index e879bb4a0b..feb6298f9b 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs
@@ -1,7 +1,9 @@
// 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.Collections.Generic;
using System.Reflection;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
@@ -36,5 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Gets or sets the of the model.
///
public TypeInfo ModelTypeInfo { get; set; }
+
+ public IList HandlerMethods { get; } = new List();
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs
index 7c2da38b2b..826f154af7 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs
@@ -66,6 +66,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
+ services.TryAddSingleton();
}
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs
index a64ee5cdf7..b9333afe6c 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs
@@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
private void AddActionDescriptors(IList actions, RazorProjectItem item, string template)
{
- var model = new PageModel(item.CombinedPath, item.PathWithoutExtension);
+ var model = new PageApplicationModel(item.CombinedPath, item.PathWithoutExtension);
var routePrefix = item.BasePath == "/" ? item.PathWithoutExtension : item.BasePath + item.PathWithoutExtension;
model.Selectors.Add(CreateSelectorModel(routePrefix, template));
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs
new file mode 100644
index 0000000000..4b76587999
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs
@@ -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 System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
+{
+ public abstract class PageArgumentBinder
+ {
+ public async Task BindModelAsync(PageContext context, Type type, object defaultValue, string name)
+ {
+ var result = await BindAsync(context, value: null, name: name, type: type);
+ return result.IsModelSet ? result.Model : defaultValue;
+ }
+
+ protected abstract Task BindAsync(PageContext context, object value, string name, Type type);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs
new file mode 100644
index 0000000000..9f40c7544d
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs
@@ -0,0 +1,82 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class DefaultPageArgumentBinder : PageArgumentBinder
+ {
+ private readonly IModelMetadataProvider _modelMetadataProvider;
+ private readonly IModelBinderFactory _modelBinderFactory;
+ private readonly IObjectModelValidator _validator;
+
+ public DefaultPageArgumentBinder(
+ IModelMetadataProvider modelMetadataProvider,
+ IModelBinderFactory modelBinderFactory,
+ IObjectModelValidator validator)
+ {
+ _modelMetadataProvider = modelMetadataProvider;
+ _modelBinderFactory = modelBinderFactory;
+ _validator = validator;
+ }
+
+ protected override async Task BindAsync(PageContext pageContext, object value, string name, Type type)
+ {
+ var factories = pageContext.ValueProviderFactories;
+ var valueProviderFactoryContext = new ValueProviderFactoryContext(pageContext);
+ for (var i = 0; i < factories.Count; i++)
+ {
+ var factory = factories[i];
+ await factory.CreateValueProviderAsync(valueProviderFactoryContext);
+ }
+
+ var valueProvider = new CompositeValueProvider(valueProviderFactoryContext.ValueProviders);
+
+ var metadata = _modelMetadataProvider.GetMetadataForType(type);
+ var binder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext()
+ {
+ BindingInfo = null,
+ Metadata = metadata,
+ CacheToken = null,
+ });
+
+ var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
+ pageContext,
+ valueProvider,
+ metadata,
+ null,
+ name);
+ modelBindingContext.Model = value;
+
+ if (modelBindingContext.ValueProvider.ContainsPrefix(name))
+ {
+ // We have a match for the parameter name, use that as that prefix.
+ modelBindingContext.ModelName = name;
+ }
+ else
+ {
+ // No match, fallback to empty string as the prefix.
+ modelBindingContext.ModelName = string.Empty;
+ }
+
+ await binder.BindModelAsync(modelBindingContext);
+
+ var result = modelBindingContext.Result;
+ if (result.IsModelSet)
+ {
+ _validator.Validate(
+ pageContext,
+ modelBindingContext.ValidationState,
+ modelBindingContext.ModelName,
+ result.Model);
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs
index a53b713aad..3092ce2738 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs
@@ -1,13 +1,143 @@
-using System;
-using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+// 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.
-namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class DefaultPageHandlerMethodSelector : IPageHandlerMethodSelector
{
public HandlerMethodDescriptor Select(PageContext context)
{
+ var handlers = new List(context.ActionDescriptor.HandlerMethods.Count);
+ for (var i = 0; i < context.ActionDescriptor.HandlerMethods.Count; i++)
+ {
+ handlers.Add(HandlerMethodAndMetadata.Create(context.ActionDescriptor.HandlerMethods[i]));
+ }
+
+ for (var i = handlers.Count - 1; i >= 0; i--)
+ {
+ var handler = handlers[i];
+
+ if (handler.HttpMethod != null &&
+ !string.Equals(handler.HttpMethod, context.HttpContext.Request.Method, StringComparison.OrdinalIgnoreCase))
+ {
+ handlers.RemoveAt(i);
+ }
+ }
+
+ 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)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)
+ {
+ best = handler;
+ continue;
+ }
+
+ if (ambiguousMatches == null)
+ {
+ ambiguousMatches = new List();
+ ambiguousMatches.Add(best.Value.Handler);
+ }
+
+ ambiguousMatches.Add(handler.Handler);
+ }
+ }
+
+ if (ambiguousMatches != null)
+ {
+ throw new InvalidOperationException($"Selecting a handler is ambiguous! Matches: {string.Join(", ", ambiguousMatches)}");
+ }
+
+ if (best != null)
+ {
+ return best.Value.Handler;
+ }
+ }
+
return null;
}
+
+ // Bad prototype substring implementation :)
+ private struct HandlerMethodAndMetadata
+ {
+ public static HandlerMethodAndMetadata Create(HandlerMethodDescriptor handler)
+ {
+ var name = handler.Method.Name;
+
+ string httpMethod;
+ if (name.StartsWith("OnGet", StringComparison.Ordinal))
+ {
+ httpMethod = "GET";
+ }
+ else if (name.StartsWith("OnPost", StringComparison.Ordinal))
+ {
+ httpMethod = "POST";
+ }
+ else
+ {
+ httpMethod = null;
+ }
+
+ var formactionStart = httpMethod?.Length + 2 ?? 0;
+ var formactionLength = name.EndsWith("Async", StringComparison.Ordinal)
+ ? name.Length - formactionStart - "Async".Length
+ : name.Length - formactionStart;
+
+ var formaction = formactionLength == 0 ? null : name.Substring(formactionStart, formactionLength);
+
+ return new HandlerMethodAndMetadata(handler, httpMethod, formaction);
+ }
+
+ public HandlerMethodAndMetadata(HandlerMethodDescriptor handler, string httpMethod, string formaction)
+ {
+ Handler = handler;
+ HttpMethod = httpMethod;
+ Formaction = formaction;
+ }
+
+ public HandlerMethodDescriptor Handler { get; }
+
+ public string HttpMethod { get; }
+
+ public string Formaction { get; }
+
+ public int GetScore()
+ {
+ if (Formaction != null)
+ {
+ return 2;
+ }
+ else if (HttpMethod != null)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs
index 25c07afe09..1a535a0e90 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
@@ -11,7 +12,227 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public static Func> Create(MethodInfo method)
{
- throw new NotImplementedException();
+ return new Executor()
+ {
+ Method = method,
+ }.Execute;
+ }
+
+ private class Executor
+ {
+ public MethodInfo Method { get; set; }
+
+ public async Task Execute(Page page, object model)
+ {
+ var handler = HandlerMethod.Create(Method);
+
+ var receiver = Method.DeclaringType.IsAssignableFrom(page.GetType()) ? page : model;
+
+ var arguments = new object[handler.Parameters.Length];
+ for (var i = 0; i < handler.Parameters.Length; i++)
+ {
+ var parameter = handler.Parameters[i];
+ arguments[i] = await page.Binder.BindModelAsync(
+ page.PageContext,
+ parameter.Type,
+ parameter.DefaultValue,
+ parameter.Name);
+ }
+
+ var result = await handler.Execute(receiver, arguments);
+ return result;
+ }
+ }
+
+ private class HandlerParameter
+ {
+ public string Name { get; set; }
+
+ public Type Type { get; set; }
+
+ public object DefaultValue { get; set; }
+ }
+
+ private abstract class HandlerMethod
+ {
+ public static HandlerMethod Create(MethodInfo method)
+ {
+ var methodParameters = method.GetParameters();
+ var parameters = new HandlerParameter[methodParameters.Length];
+
+ for (var i = 0; i < methodParameters.Length; i++)
+ {
+ parameters[i] = new HandlerParameter()
+ {
+ DefaultValue = methodParameters[i].HasDefaultValue ? methodParameters[i].DefaultValue : null,
+ Name = methodParameters[i].Name,
+ Type = methodParameters[i].ParameterType,
+ };
+ }
+
+ if (method.ReturnType == typeof(Task))
+ {
+ return new NonGenericTaskHandlerMethod(parameters, method);
+ }
+ else if (method.ReturnType == typeof(void))
+ {
+ return new VoidHandlerMethod(parameters, method);
+ }
+ else if (
+ method.ReturnType.IsConstructedGenericType &&
+ method.ReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) &&
+ typeof(IActionResult).IsAssignableFrom(method.ReturnType.GetTypeInfo().GetGenericArguments()[0]))
+ {
+ return new GenericTaskHandlerMethod(parameters, method);
+ }
+ else if (typeof(IActionResult).IsAssignableFrom(method.ReturnType))
+ {
+ return new ActionResultHandlerMethod(parameters, method);
+ }
+ else
+ {
+ throw new InvalidOperationException("unsupported handler method return type");
+ }
+ }
+
+ protected static Expression[] Unpack(Expression arguments, HandlerParameter[] parameters)
+ {
+ var unpackExpressions = new Expression[parameters.Length];
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ unpackExpressions[i] = Expression.Convert(Expression.ArrayIndex(arguments, Expression.Constant(i)), parameters[i].Type);
+ }
+
+ return unpackExpressions;
+ }
+
+ protected HandlerMethod(HandlerParameter[] parameters)
+ {
+ Parameters = parameters;
+ }
+
+ public HandlerParameter[] Parameters { get; }
+
+ public abstract Task Execute(object receiver, object[] arguments);
+ }
+
+ private class NonGenericTaskHandlerMethod : HandlerMethod
+ {
+ private readonly Func _thunk;
+
+ public NonGenericTaskHandlerMethod(HandlerParameter[] parameters, MethodInfo method)
+ : base(parameters)
+ {
+ var receiver = Expression.Parameter(typeof(object), "receiver");
+ var arguments = Expression.Parameter(typeof(object[]), "arguments");
+
+ _thunk = Expression.Lambda>(
+ Expression.Call(
+ Expression.Convert(receiver, method.DeclaringType),
+ method,
+ Unpack(arguments, parameters)),
+ receiver,
+ arguments).Compile();
+ }
+
+ public override async Task Execute(object receiver, object[] arguments)
+ {
+ await _thunk(receiver, arguments);
+ return null;
+ }
+ }
+
+ private class GenericTaskHandlerMethod : HandlerMethod
+ {
+ private static readonly MethodInfo ConvertMethod = typeof(GenericTaskHandlerMethod).GetMethod(
+ nameof(Convert),
+ BindingFlags.NonPublic | BindingFlags.Static);
+
+ private readonly Func> _thunk;
+
+ public GenericTaskHandlerMethod(HandlerParameter[] parameters, MethodInfo method)
+ : base(parameters)
+ {
+ var receiver = Expression.Parameter(typeof(object), "receiver");
+ var arguments = Expression.Parameter(typeof(object[]), "arguments");
+
+ _thunk = Expression.Lambda>>(
+ Expression.Call(
+ ConvertMethod.MakeGenericMethod(method.ReturnType.GenericTypeArguments),
+ Expression.Convert(
+ Expression.Call(
+ Expression.Convert(receiver, method.DeclaringType),
+ method,
+ Unpack(arguments, parameters)),
+ typeof(object))),
+ receiver,
+ arguments).Compile();
+ }
+
+ public override async Task Execute(object receiver, object[] arguments)
+ {
+ var result = await _thunk(receiver, arguments);
+ return (IActionResult)result;
+ }
+
+ private static async Task Convert(object taskAsObject)
+ {
+ var task = (Task)taskAsObject;
+ return (object)await task;
+ }
+ }
+
+ private class VoidHandlerMethod : HandlerMethod
+ {
+ private readonly Action _thunk;
+
+ public VoidHandlerMethod(HandlerParameter[] parameters, MethodInfo method)
+ : base(parameters)
+ {
+ var receiver = Expression.Parameter(typeof(object), "receiver");
+ var arguments = Expression.Parameter(typeof(object[]), "arguments");
+
+ _thunk = Expression.Lambda>(
+ Expression.Call(
+ Expression.Convert(receiver, method.DeclaringType),
+ method,
+ Unpack(arguments, parameters)),
+ receiver,
+ arguments).Compile();
+ }
+
+ public override Task Execute(object receiver, object[] arguments)
+ {
+ _thunk(receiver, arguments);
+ return Task.FromResult(null);
+ }
+ }
+
+ private class ActionResultHandlerMethod : HandlerMethod
+ {
+ private readonly Func _thunk;
+
+ public ActionResultHandlerMethod(HandlerParameter[] parameters, MethodInfo method)
+ : base(parameters)
+ {
+ var receiver = Expression.Parameter(typeof(object), "receiver");
+ var arguments = Expression.Parameter(typeof(object[]), "arguments");
+
+ _thunk = Expression.Lambda>(
+ Expression.Convert(
+ Expression.Call(
+ Expression.Convert(receiver, method.DeclaringType),
+ method,
+ Unpack(arguments, parameters)),
+ typeof(IActionResult)),
+ receiver,
+ arguments).Compile();
+ }
+
+ public override Task Execute(object receiver, object[] arguments)
+ {
+ return Task.FromResult(_thunk(receiver, arguments));
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
index 1683f61130..15f39db944 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
@@ -304,6 +304,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var actionDescriptor = _pageContext.ActionDescriptor;
_page = (Page)CacheEntry.PageFactory(_pageContext);
_pageContext.Page = _page;
+ _pageContext.ValueProviderFactories = _valueProviderFactories;
IRazorPage[] pageStarts;
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
index 49a479077f..b0ccfb16fa 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
@@ -48,9 +48,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
IRazorPageFactoryProvider razorPageFactoryProvider,
IActionDescriptorCollectionProvider collectionProvider,
IEnumerable filterProviders,
- IEnumerable valueProviderFactories,
IModelMetadataProvider modelMetadataProvider,
ITempDataDictionaryFactory tempDataFactory,
+ IOptions mvcOptions,
IOptions htmlHelperOptions,
IPageHandlerMethodSelector selector,
RazorProject razorProject,
@@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_razorPageFactoryProvider = razorPageFactoryProvider;
_collectionProvider = collectionProvider;
_filterProviders = filterProviders.ToArray();
- _valueProviderFactories = valueProviderFactories.ToArray();
+ _valueProviderFactories = mvcOptions.Value.ValueProviderFactories.ToArray();
_modelMetadataProvider = modelMetadataProvider;
_tempDataFactory = tempDataFactory;
_htmlHelperOptions = htmlHelperOptions.Value;
@@ -180,6 +180,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor);
modelReleaser = _modelFactoryProvider.CreateModelDisposer(compiledActionDescriptor);
+
+ if (modelType != compiledType)
+ {
+ // If the model and page type are different discover handler methods on the model as well.
+ PopulateHandlerMethodDescriptors(modelType, compiledActionDescriptor);
+ }
}
var pageStartFactories = GetPageStartFactories(compiledActionDescriptor);
@@ -210,6 +216,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return pageStartFactories;
}
+ private static void PopulateHandlerMethodDescriptors(TypeInfo type, CompiledPageActionDescriptor actionDescriptor)
+ {
+ var methods = type.GetMethods();
+ for (var i = 0; i < methods.Length; i++)
+ {
+ var method = methods[i];
+ if (method.Name.StartsWith("OnGet", StringComparison.Ordinal) ||
+ method.Name.StartsWith("OnPost", StringComparison.Ordinal))
+ {
+ actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor()
+ {
+ Method = method,
+ });
+ }
+ }
+ }
+
private class InnerCache
{
public InnerCache(int version)
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
index bc926861a0..5514035d79 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
@@ -9,6 +9,7 @@ using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
@@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public abstract class Page : RazorPageBase, IRazorPage
{
private IUrlHelper _urlHelper;
+ private PageArgumentBinder _binder;
///
public IHtmlContent BodyContent { get; set; }
@@ -61,6 +63,29 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
[RazorInject]
public HtmlEncoder HtmlEncoder { get; set; }
+ public PageArgumentBinder Binder
+ {
+ get
+ {
+ if (_binder == null)
+ {
+ _binder = PageContext.HttpContext.RequestServices.GetRequiredService();
+ }
+
+ return _binder;
+ }
+
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _binder = value;
+ }
+ }
+
protected override HtmlEncoder Encoder => HtmlEncoder;
protected override TextWriter Writer => ViewContext.Writer;
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs
index 576d62092e..e45a6e94e1 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@@ -19,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
{
private CompiledPageActionDescriptor _actionDescriptor;
private Page _page;
+ private IList _valueProviderFactories;
///
/// Creates an empty .
@@ -80,5 +82,30 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Gets or sets the applicable _PageStart instances.
///
public IReadOnlyList PageStarts { get; set; }
+
+ ///
+ /// Gets or sets the list of instances for the current request.
+ ///
+ public virtual IList ValueProviderFactories
+ {
+ get
+ {
+ if (_valueProviderFactories == null)
+ {
+ _valueProviderFactories = new List();
+ }
+
+ return _valueProviderFactories;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _valueProviderFactories = value;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
new file mode 100644
index 0000000000..5688c20d9a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
@@ -0,0 +1,58 @@
+// 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.ModelBinding;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages
+{
+ public abstract class PageModel
+ {
+ private PageArgumentBinder _binder;
+
+ public PageArgumentBinder Binder
+ {
+ get
+ {
+ if (_binder == null)
+ {
+ _binder = PageContext.HttpContext.RequestServices.GetRequiredService();
+ }
+
+ return _binder;
+ }
+
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _binder = value;
+ }
+ }
+
+ public Page Page => PageContext.Page;
+
+ [PageContext]
+ public PageContext PageContext { get; set; }
+
+ public ModelStateDictionary ModelState => PageContext.ModelState;
+
+ public ViewDataDictionary ViewData => PageContext?.ViewData;
+
+ protected IActionResult Redirect(string url)
+ {
+ return new RedirectResult(url);
+ }
+
+ protected IActionResult View()
+ {
+ return new PageViewResult(Page);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs
index 91b75d9a6e..7d2b91048b 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs
@@ -242,8 +242,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
var options = new MvcOptions();
options.Filters.Add(globalFilter);
var convention = new Mock();
- convention.Setup(c => c.Apply(It.IsAny()))
- .Callback((PageModel model) =>
+ convention.Setup(c => c.Apply(It.IsAny()))
+ .Callback((PageApplicationModel model) =>
{
model.Filters.Add(localFilter);
});
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
index e2482230d1..1616c1112d 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
@@ -278,9 +278,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
razorPageFactoryProvider ?? Mock.Of(),
actionDescriptorProvider,
new IFilterProvider[0],
- new IValueProviderFactory[0],
new EmptyModelMetadataProvider(),
tempDataFactory.Object,
+ new TestOptionsManager(),
new TestOptionsManager(),
Mock.Of(),
razorProject,