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
|
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="row">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<h2>RazorPages Test</h2>
|
<h2>RazorPages says Hello @Model.Name!</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>This file should give you a quick view of a Mvc Raor Page in action.</li>
|
<li>This file should give you a quick view of a Mvc Raor Page in action.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<label>Say hello to <input type="text" name="name" /></label>
|
||||||
|
<input type="submit" value="Say" />
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows customization of the of the <see cref="PageModel"/>.
|
/// Allows customization of the of the <see cref="PageApplicationModel"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPageModelConvention
|
public interface IPageModelConvention
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called to apply the convention to the <see cref="PageModel"/>.
|
/// Called to apply the convention to the <see cref="PageApplicationModel"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The <see cref="PageModel"/>.</param>
|
/// <param name="model">The <see cref="PageApplicationModel"/>.</param>
|
||||||
void Apply(PageModel model);
|
void Apply(PageApplicationModel model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,14 +11,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application model component for RazorPages.
|
/// Application model component for RazorPages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PageModel
|
public class PageApplicationModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="PageModel"/>.
|
/// Initializes a new instance of <see cref="PageApplicationModel"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="relativePath">The application relative path of the page.</param>
|
/// <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>
|
/// <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)
|
if (relativePath == null)
|
||||||
{
|
{
|
||||||
|
|
@ -39,10 +39,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A copy constructor for <see cref="PageModel"/>.
|
/// A copy constructor for <see cref="PageApplicationModel"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other">The <see cref="PageModel"/> to copy from.</param>
|
/// <param name="other">The <see cref="PageApplicationModel"/> to copy from.</param>
|
||||||
public PageModel(PageModel other)
|
public PageApplicationModel(PageApplicationModel other)
|
||||||
{
|
{
|
||||||
if (other == null)
|
if (other == null)
|
||||||
{
|
{
|
||||||
|
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
public IList<IFilterMetadata> Filters { get; }
|
public IList<IFilterMetadata> Filters { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores arbitrary metadata properties associated with the <see cref="PageModel"/>.
|
/// Stores arbitrary metadata properties associated with the <see cref="PageApplicationModel"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<object, object> Properties { get; }
|
public IDictionary<object, object> Properties { get; }
|
||||||
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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 System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
{
|
{
|
||||||
|
|
@ -36,5 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
/// Gets or sets the <see cref="TypeInfo"/> of the model.
|
/// Gets or sets the <see cref="TypeInfo"/> of the model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TypeInfo ModelTypeInfo { get; set; }
|
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<IPageLoader, DefaultPageLoader>();
|
||||||
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
|
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
|
||||||
services.TryAddSingleton<PageResultExecutor>();
|
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)
|
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;
|
var routePrefix = item.BasePath == "/" ? item.PathWithoutExtension : item.BasePath + item.PathWithoutExtension;
|
||||||
model.Selectors.Add(CreateSelectorModel(routePrefix, template));
|
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;
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
// 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 class DefaultPageHandlerMethodSelector : IPageHandlerMethodSelector
|
||||||
{
|
{
|
||||||
public HandlerMethodDescriptor Select(PageContext context)
|
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;
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
@ -11,7 +12,227 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
public static Func<Page, object, Task<IActionResult>> Create(MethodInfo method)
|
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;
|
var actionDescriptor = _pageContext.ActionDescriptor;
|
||||||
_page = (Page)CacheEntry.PageFactory(_pageContext);
|
_page = (Page)CacheEntry.PageFactory(_pageContext);
|
||||||
_pageContext.Page = _page;
|
_pageContext.Page = _page;
|
||||||
|
_pageContext.ValueProviderFactories = _valueProviderFactories;
|
||||||
|
|
||||||
IRazorPage[] pageStarts;
|
IRazorPage[] pageStarts;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
IRazorPageFactoryProvider razorPageFactoryProvider,
|
IRazorPageFactoryProvider razorPageFactoryProvider,
|
||||||
IActionDescriptorCollectionProvider collectionProvider,
|
IActionDescriptorCollectionProvider collectionProvider,
|
||||||
IEnumerable<IFilterProvider> filterProviders,
|
IEnumerable<IFilterProvider> filterProviders,
|
||||||
IEnumerable<IValueProviderFactory> valueProviderFactories,
|
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
ITempDataDictionaryFactory tempDataFactory,
|
ITempDataDictionaryFactory tempDataFactory,
|
||||||
|
IOptions<MvcOptions> mvcOptions,
|
||||||
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
||||||
IPageHandlerMethodSelector selector,
|
IPageHandlerMethodSelector selector,
|
||||||
RazorProject razorProject,
|
RazorProject razorProject,
|
||||||
|
|
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
_razorPageFactoryProvider = razorPageFactoryProvider;
|
_razorPageFactoryProvider = razorPageFactoryProvider;
|
||||||
_collectionProvider = collectionProvider;
|
_collectionProvider = collectionProvider;
|
||||||
_filterProviders = filterProviders.ToArray();
|
_filterProviders = filterProviders.ToArray();
|
||||||
_valueProviderFactories = valueProviderFactories.ToArray();
|
_valueProviderFactories = mvcOptions.Value.ValueProviderFactories.ToArray();
|
||||||
_modelMetadataProvider = modelMetadataProvider;
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
_tempDataFactory = tempDataFactory;
|
_tempDataFactory = tempDataFactory;
|
||||||
_htmlHelperOptions = htmlHelperOptions.Value;
|
_htmlHelperOptions = htmlHelperOptions.Value;
|
||||||
|
|
@ -180,6 +180,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor);
|
modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor);
|
||||||
modelReleaser = _modelFactoryProvider.CreateModelDisposer(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);
|
var pageStartFactories = GetPageStartFactories(compiledActionDescriptor);
|
||||||
|
|
@ -210,6 +216,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
return pageStartFactories;
|
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
|
private class InnerCache
|
||||||
{
|
{
|
||||||
public InnerCache(int version)
|
public InnerCache(int version)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Html;
|
using Microsoft.AspNetCore.Html;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
public abstract class Page : RazorPageBase, IRazorPage
|
public abstract class Page : RazorPageBase, IRazorPage
|
||||||
{
|
{
|
||||||
private IUrlHelper _urlHelper;
|
private IUrlHelper _urlHelper;
|
||||||
|
private PageArgumentBinder _binder;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IHtmlContent BodyContent { get; set; }
|
public IHtmlContent BodyContent { get; set; }
|
||||||
|
|
@ -61,6 +63,29 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
[RazorInject]
|
[RazorInject]
|
||||||
public HtmlEncoder HtmlEncoder { get; set; }
|
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 HtmlEncoder Encoder => HtmlEncoder;
|
||||||
|
|
||||||
protected override TextWriter Writer => ViewContext.Writer;
|
protected override TextWriter Writer => ViewContext.Writer;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
|
@ -19,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
{
|
{
|
||||||
private CompiledPageActionDescriptor _actionDescriptor;
|
private CompiledPageActionDescriptor _actionDescriptor;
|
||||||
private Page _page;
|
private Page _page;
|
||||||
|
private IList<IValueProviderFactory> _valueProviderFactories;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an empty <see cref="PageContext"/>.
|
/// Creates an empty <see cref="PageContext"/>.
|
||||||
|
|
@ -80,5 +82,30 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
/// Gets or sets the applicable _PageStart instances.
|
/// Gets or sets the applicable _PageStart instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<IRazorPage> PageStarts { get; set; }
|
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();
|
var options = new MvcOptions();
|
||||||
options.Filters.Add(globalFilter);
|
options.Filters.Add(globalFilter);
|
||||||
var convention = new Mock<IPageModelConvention>();
|
var convention = new Mock<IPageModelConvention>();
|
||||||
convention.Setup(c => c.Apply(It.IsAny<PageModel>()))
|
convention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
|
||||||
.Callback((PageModel model) =>
|
.Callback((PageApplicationModel model) =>
|
||||||
{
|
{
|
||||||
model.Filters.Add(localFilter);
|
model.Filters.Add(localFilter);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -278,9 +278,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
razorPageFactoryProvider ?? Mock.Of<IRazorPageFactoryProvider>(),
|
razorPageFactoryProvider ?? Mock.Of<IRazorPageFactoryProvider>(),
|
||||||
actionDescriptorProvider,
|
actionDescriptorProvider,
|
||||||
new IFilterProvider[0],
|
new IFilterProvider[0],
|
||||||
new IValueProviderFactory[0],
|
|
||||||
new EmptyModelMetadataProvider(),
|
new EmptyModelMetadataProvider(),
|
||||||
tempDataFactory.Object,
|
tempDataFactory.Object,
|
||||||
|
new TestOptionsManager<MvcOptions>(),
|
||||||
new TestOptionsManager<HtmlHelperOptions>(),
|
new TestOptionsManager<HtmlHelperOptions>(),
|
||||||
Mock.Of<IPageHandlerMethodSelector>(),
|
Mock.Of<IPageHandlerMethodSelector>(),
|
||||||
razorProject,
|
razorProject,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue