Porting DefaultPageHandlerMethodSelector and ExecutorFactory
This commit is contained in:
parent
624909763b
commit
2ff80ffb49
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,15 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<h2>RazorPages Test</h2>
|
||||
<h2>RazorPages says Hello @Model.Name!</h2>
|
||||
<ul>
|
||||
<li>This file should give you a quick view of a Mvc Raor Page in action.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<label>Say hello to <input type="text" name="name" /></label>
|
||||
<input type="submit" value="Say" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows customization of the of the <see cref="PageModel"/>.
|
||||
/// Allows customization of the of the <see cref="PageApplicationModel"/>.
|
||||
/// </summary>
|
||||
public interface IPageModelConvention
|
||||
{
|
||||
/// <summary>
|
||||
/// Called to apply the convention to the <see cref="PageModel"/>.
|
||||
/// Called to apply the convention to the <see cref="PageApplicationModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="PageModel"/>.</param>
|
||||
void Apply(PageModel model);
|
||||
/// <param name="model">The <see cref="PageApplicationModel"/>.</param>
|
||||
void Apply(PageApplicationModel model);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,14 +11,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
/// <summary>
|
||||
/// Application model component for RazorPages.
|
||||
/// </summary>
|
||||
public class PageModel
|
||||
public class PageApplicationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="PageModel"/>.
|
||||
/// Initializes a new instance of <see cref="PageApplicationModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">The application relative path of the page.</param>
|
||||
/// <param name="viewEnginePath">The path relative to the base path for page discovery.</param>
|
||||
public PageModel(string relativePath, string viewEnginePath)
|
||||
public PageApplicationModel(string relativePath, string viewEnginePath)
|
||||
{
|
||||
if (relativePath == null)
|
||||
{
|
||||
|
|
@ -39,10 +39,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// A copy constructor for <see cref="PageModel"/>.
|
||||
/// A copy constructor for <see cref="PageApplicationModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="PageModel"/> to copy from.</param>
|
||||
public PageModel(PageModel other)
|
||||
/// <param name="other">The <see cref="PageApplicationModel"/> to copy from.</param>
|
||||
public PageApplicationModel(PageApplicationModel other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
|
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public IList<IFilterMetadata> Filters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores arbitrary metadata properties associated with the <see cref="PageModel"/>.
|
||||
/// Stores arbitrary metadata properties associated with the <see cref="PageApplicationModel"/>.
|
||||
/// </summary>
|
||||
public IDictionary<object, object> Properties { get; }
|
||||
|
||||
|
|
@ -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 <see cref="TypeInfo"/> of the model.
|
||||
/// </summary>
|
||||
public TypeInfo ModelTypeInfo { get; set; }
|
||||
|
||||
public IList<HandlerMethodDescriptor> HandlerMethods { get; } = new List<HandlerMethodDescriptor>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<IPageLoader, DefaultPageLoader>();
|
||||
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
|
||||
services.TryAddSingleton<PageResultExecutor>();
|
||||
services.TryAddSingleton<PageArgumentBinder, DefaultPageArgumentBinder>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
private void AddActionDescriptors(IList<ActionDescriptor> 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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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<object> 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<ModelBindingResult> BindAsync(PageContext context, object value, string name, Type type);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ModelBindingResult> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<HandlerMethodAndMetadata>(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<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)
|
||||
{
|
||||
best = handler;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ambiguousMatches == null)
|
||||
{
|
||||
ambiguousMatches = new List<HandlerMethodDescriptor>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Page, object, Task<IActionResult>> Create(MethodInfo method)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new Executor()
|
||||
{
|
||||
Method = method,
|
||||
}.Execute;
|
||||
}
|
||||
|
||||
private class Executor
|
||||
{
|
||||
public MethodInfo Method { get; set; }
|
||||
|
||||
public async Task<IActionResult> 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<IActionResult> Execute(object receiver, object[] arguments);
|
||||
}
|
||||
|
||||
private class NonGenericTaskHandlerMethod : HandlerMethod
|
||||
{
|
||||
private readonly Func<object, object[], Task> _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<Func<object, object[], Task>>(
|
||||
Expression.Call(
|
||||
Expression.Convert(receiver, method.DeclaringType),
|
||||
method,
|
||||
Unpack(arguments, parameters)),
|
||||
receiver,
|
||||
arguments).Compile();
|
||||
}
|
||||
|
||||
public override async Task<IActionResult> 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<object, object[], Task<object>> _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<Func<object, object[], Task<object>>>(
|
||||
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<IActionResult> Execute(object receiver, object[] arguments)
|
||||
{
|
||||
var result = await _thunk(receiver, arguments);
|
||||
return (IActionResult)result;
|
||||
}
|
||||
|
||||
private static async Task<object> Convert<T>(object taskAsObject)
|
||||
{
|
||||
var task = (Task<T>)taskAsObject;
|
||||
return (object)await task;
|
||||
}
|
||||
}
|
||||
|
||||
private class VoidHandlerMethod : HandlerMethod
|
||||
{
|
||||
private readonly Action<object, object[]> _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<Action<object, object[]>>(
|
||||
Expression.Call(
|
||||
Expression.Convert(receiver, method.DeclaringType),
|
||||
method,
|
||||
Unpack(arguments, parameters)),
|
||||
receiver,
|
||||
arguments).Compile();
|
||||
}
|
||||
|
||||
public override Task<IActionResult> Execute(object receiver, object[] arguments)
|
||||
{
|
||||
_thunk(receiver, arguments);
|
||||
return Task.FromResult<IActionResult>(null);
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionResultHandlerMethod : HandlerMethod
|
||||
{
|
||||
private readonly Func<object, object[], IActionResult> _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<Func<object, object[], IActionResult>>(
|
||||
Expression.Convert(
|
||||
Expression.Call(
|
||||
Expression.Convert(receiver, method.DeclaringType),
|
||||
method,
|
||||
Unpack(arguments, parameters)),
|
||||
typeof(IActionResult)),
|
||||
receiver,
|
||||
arguments).Compile();
|
||||
}
|
||||
|
||||
public override Task<IActionResult> Execute(object receiver, object[] arguments)
|
||||
{
|
||||
return Task.FromResult(_thunk(receiver, arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
IRazorPageFactoryProvider razorPageFactoryProvider,
|
||||
IActionDescriptorCollectionProvider collectionProvider,
|
||||
IEnumerable<IFilterProvider> filterProviders,
|
||||
IEnumerable<IValueProviderFactory> valueProviderFactories,
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
ITempDataDictionaryFactory tempDataFactory,
|
||||
IOptions<MvcOptions> mvcOptions,
|
||||
IOptions<HtmlHelperOptions> 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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <inheritdoc />
|
||||
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<PageArgumentBinder>();
|
||||
}
|
||||
|
||||
return _binder;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_binder = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override HtmlEncoder Encoder => HtmlEncoder;
|
||||
|
||||
protected override TextWriter Writer => ViewContext.Writer;
|
||||
|
|
|
|||
|
|
@ -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<IValueProviderFactory> _valueProviderFactories;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty <see cref="PageContext"/>.
|
||||
|
|
@ -80,5 +82,30 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
/// Gets or sets the applicable _PageStart instances.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IRazorPage> PageStarts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of <see cref="IValueProviderFactory"/> instances for the current request.
|
||||
/// </summary>
|
||||
public virtual IList<IValueProviderFactory> ValueProviderFactories
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_valueProviderFactories == null)
|
||||
{
|
||||
_valueProviderFactories = new List<IValueProviderFactory>();
|
||||
}
|
||||
|
||||
return _valueProviderFactories;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_valueProviderFactories = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<PageArgumentBinder>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,8 +242,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var options = new MvcOptions();
|
||||
options.Filters.Add(globalFilter);
|
||||
var convention = new Mock<IPageModelConvention>();
|
||||
convention.Setup(c => c.Apply(It.IsAny<PageModel>()))
|
||||
.Callback((PageModel model) =>
|
||||
convention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
|
||||
.Callback((PageApplicationModel model) =>
|
||||
{
|
||||
model.Filters.Add(localFilter);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -278,9 +278,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
razorPageFactoryProvider ?? Mock.Of<IRazorPageFactoryProvider>(),
|
||||
actionDescriptorProvider,
|
||||
new IFilterProvider[0],
|
||||
new IValueProviderFactory[0],
|
||||
new EmptyModelMetadataProvider(),
|
||||
tempDataFactory.Object,
|
||||
new TestOptionsManager<MvcOptions>(),
|
||||
new TestOptionsManager<HtmlHelperOptions>(),
|
||||
Mock.Of<IPageHandlerMethodSelector>(),
|
||||
razorProject,
|
||||
|
|
|
|||
Loading…
Reference in New Issue