diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs
new file mode 100644
index 0000000000..cfa332a465
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs
@@ -0,0 +1,17 @@
+// 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.ApplicationModels
+{
+ ///
+ /// Allows customization of the of the .
+ ///
+ public interface IPageRouteModelConvention
+ {
+ ///
+ /// Called to apply the convention to the .
+ ///
+ /// The .
+ void Apply(PageRouteModel model);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs
new file mode 100644
index 0000000000..300d8ecf9c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs
@@ -0,0 +1,44 @@
+// 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.ApplicationModels
+{
+ ///
+ /// Builds or modifies an for Razor Page routing.
+ ///
+ public interface IPageRouteModelProvider
+ {
+ ///
+ /// Gets the order value for determining the order of execution of providers. Providers execute in
+ /// ascending numeric value of the property.
+ ///
+ ///
+ ///
+ /// Providers are executed in an ordering determined by an ascending sort of the property.
+ /// A provider with a lower numeric value of will have its
+ /// called before that of a provider with a higher numeric value of
+ /// . The method is called in the reverse ordering after
+ /// all calls to . A provider with a lower numeric value of
+ /// will have its method called after that of a provider
+ /// with a higher numeric value of .
+ ///
+ ///
+ /// If two providers have the same numeric value of , then their relative execution order
+ /// is undefined.
+ ///
+ ///
+ int Order { get; }
+
+ ///
+ /// Executed for the first pass of building instances. See .
+ ///
+ /// The .
+ void OnProvidersExecuting(PageRouteModelProviderContext context);
+
+ ///
+ /// Executed for the second pass of building instances. See .
+ ///
+ /// The .
+ void OnProvidersExecuted(PageRouteModelProviderContext context);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs
index dd075bc91b..dee4f299fd 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs
@@ -4,7 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
@@ -16,26 +19,21 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
///
/// Initializes a new instance of .
///
- /// The application relative path of the page.
- /// The path relative to the base path for page discovery.
- public PageApplicationModel(string relativePath, string viewEnginePath)
+ public PageApplicationModel(
+ PageActionDescriptor actionDescriptor,
+ TypeInfo handlerType,
+ IReadOnlyList handlerAttributes)
{
- if (relativePath == null)
- {
- throw new ArgumentNullException(nameof(relativePath));
- }
-
- if (viewEnginePath == null)
- {
- throw new ArgumentNullException(nameof(viewEnginePath));
- }
-
- RelativePath = relativePath;
- ViewEnginePath = viewEnginePath;
+ ActionDescriptor = actionDescriptor ?? throw new ArgumentNullException(nameof(actionDescriptor));
+ HandlerType = handlerType;
Filters = new List();
- Properties = new Dictionary();
- Selectors = new List();
+ Properties = new CopyOnWriteDictionary(
+ actionDescriptor.Properties,
+ EqualityComparer.Default);
+ HandlerMethods = new List();
+ HandlerProperties = new List();
+ HandlerTypeAttributes = handlerAttributes;
}
///
@@ -49,24 +47,38 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
throw new ArgumentNullException(nameof(other));
}
- RelativePath = other.RelativePath;
- ViewEnginePath = other.ViewEnginePath;
+ ActionDescriptor = other.ActionDescriptor;
+ HandlerType = other.HandlerType;
+ PageType = other.PageType;
+ ModelType = other.ModelType;
Filters = new List(other.Filters);
Properties = new Dictionary(other.Properties);
- Selectors = new List(other.Selectors.Select(m => new SelectorModel(m)));
+ HandlerMethods = new List(other.HandlerMethods.Select(m => new PageHandlerModel(m)));
+ HandlerProperties = new List(other.HandlerProperties.Select(p => new PagePropertyModel(p)));
+ HandlerTypeAttributes = other.HandlerTypeAttributes;
}
+ ///
+ /// Gets the .
+ ///
+ public PageActionDescriptor ActionDescriptor { get; }
+
///
/// Gets the application root relative path for the page.
///
- public string RelativePath { get; }
+ public string RelativePath => ActionDescriptor.RelativePath;
///
/// Gets the path relative to the base path for page discovery.
///
- public string ViewEnginePath { get; }
+ public string ViewEnginePath => ActionDescriptor.ViewEnginePath;
+
+ ///
+ /// Gets the route template for the page.
+ ///
+ public string RouteTemplate => ActionDescriptor.AttributeRouteInfo?.Template;
///
/// Gets the applicable instances.
@@ -79,8 +91,33 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public IDictionary Properties { get; }
///
- /// Gets the instances.
+ /// Gets or sets the of the Razor page.
///
- public IList Selectors { get; }
+ public TypeInfo PageType { get; set; }
+
+ ///
+ /// Gets or sets the of the Razor page model.
+ ///
+ public TypeInfo ModelType { get; set; }
+
+ ///
+ /// Gets the of the handler.
+ ///
+ public TypeInfo HandlerType { get; }
+
+ ///
+ /// Gets the sequence of attributes declared on .
+ ///
+ public IReadOnlyList HandlerTypeAttributes { get; }
+
+ ///
+ /// Gets the sequence of instances.
+ ///
+ public IList HandlerMethods { get; }
+
+ ///
+ /// Gets the sequence of instances on .
+ ///
+ public IList HandlerProperties { get; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs
index b73f88c168..cccb6824e5 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs
@@ -1,7 +1,8 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
@@ -10,9 +11,25 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
///
public class PageApplicationModelProviderContext
{
+ public PageApplicationModelProviderContext(PageActionDescriptor descriptor, TypeInfo pageTypeInfo)
+ {
+ ActionDescriptor = descriptor;
+ PageType = pageTypeInfo;
+ }
+
///
- /// Gets the instances.
+ /// Gets the .
///
- public IList Results { get; } = new List();
+ public PageActionDescriptor ActionDescriptor { get; }
+
+ ///
+ /// Gets the page .
+ ///
+ public TypeInfo PageType { get; }
+
+ ///
+ /// Gets or sets the .
+ ///
+ public PageApplicationModel PageApplicationModel { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs
new file mode 100644
index 0000000000..baf996f919
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs
@@ -0,0 +1,98 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationModels
+{
+ ///
+ /// Represents a handler in a .
+ ///
+ [DebuggerDisplay("PageHandlerModel: Name={" + nameof(PageHandlerModel.Name) + "}")]
+ public class PageHandlerModel : ICommonModel
+ {
+ ///
+ /// Creates a new .
+ ///
+ /// The for the handler.
+ /// Any attributes annotated on the handler method.
+ public PageHandlerModel(
+ MethodInfo handlerMethod,
+ IReadOnlyList attributes)
+ {
+ MethodInfo = handlerMethod ?? throw new ArgumentNullException(nameof(handlerMethod));
+ Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes));
+
+ Parameters = new List();
+ Properties = new Dictionary();
+ }
+
+ ///
+ /// Creats a new instance of from a given .
+ ///
+ /// The which needs to be copied.
+ public PageHandlerModel(PageHandlerModel other)
+ {
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ MethodInfo = other.MethodInfo;
+ HandlerName = other.HandlerName;
+ HttpMethod = other.HttpMethod;
+ Name = other.Name;
+
+ Page = other.Page;
+
+ // These are just metadata, safe to create new collections
+ Attributes = new List(other.Attributes);
+ Properties = new Dictionary(other.Properties);
+
+ // Make a deep copy of other 'model' types.
+ Parameters = new List(other.Parameters.Select(p => new PageParameterModel(p) { Handler = this }));
+ }
+
+ ///
+ /// Gets the for the handler.
+ ///
+ public MethodInfo MethodInfo { get; }
+
+ ///
+ /// Gets or sets the HTTP method supported by this handler.
+ ///
+ public string HttpMethod { get; set; }
+
+ ///
+ /// Gets or sets the handler method name.
+ ///
+ public string HandlerName { get; set; }
+
+ ///
+ /// Gets or sets a descriptive name for the handler.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Gets the sequence of instances.
+ ///
+ public IList Parameters { get; }
+
+ ///
+ /// Gets or sets the .
+ ///
+ public PageApplicationModel Page { get; set; }
+
+ ///
+ public IReadOnlyList Attributes { get; }
+
+ ///
+ public IDictionary Properties { get; }
+
+ MemberInfo ICommonModel.MemberInfo => MethodInfo;
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs
new file mode 100644
index 0000000000..c4547411c0
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationModels
+{
+ [DebuggerDisplay("PageParameterModel: Name={ParameterName}")]
+ public class PageParameterModel : ICommonModel, IBindingModel
+ {
+ public PageParameterModel(
+ ParameterInfo parameterInfo,
+ IReadOnlyList attributes)
+ {
+ ParameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
+
+ if (attributes == null)
+ {
+ throw new ArgumentNullException(nameof(attributes));
+ }
+
+ Properties = new Dictionary();
+ Attributes = new List(attributes);
+ }
+
+ public PageParameterModel(PageParameterModel other)
+ {
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ Handler = other.Handler;
+ Attributes = new List(other.Attributes);
+ BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
+ ParameterInfo = other.ParameterInfo;
+ ParameterName = other.ParameterName;
+ Properties = new Dictionary(other.Properties);
+ }
+
+ public PageHandlerModel Handler { get; set; }
+
+ public IReadOnlyList Attributes { get; }
+
+ public IDictionary Properties { get; }
+
+ MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member;
+
+ string ICommonModel.Name => ParameterName;
+
+ public ParameterInfo ParameterInfo { get; }
+
+ public string ParameterName { get; set; }
+
+ public BindingInfo BindingInfo { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs
new file mode 100644
index 0000000000..ac31e8d6bd
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs
@@ -0,0 +1,83 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationModels
+{
+ ///
+ /// Represents a property in a .
+ ///
+ [DebuggerDisplay("PagePropertyModel: Name={PropertyName}")]
+ public class PagePropertyModel : ICommonModel, IBindingModel
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The for the underlying property.
+ /// Any attributes which are annotated on the property.
+ public PagePropertyModel(
+ PropertyInfo propertyInfo,
+ IReadOnlyList attributes)
+ {
+ PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
+ Properties = new Dictionary();
+ Attributes = new List(attributes) ?? throw new ArgumentNullException(nameof(attributes));
+ }
+
+ ///
+ /// Creats a new instance of from a given .
+ ///
+ /// The which needs to be copied.
+ public PagePropertyModel(PagePropertyModel other)
+ {
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ Page = other.Page;
+ Attributes = new List(other.Attributes);
+ BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
+ PropertyInfo = other.PropertyInfo;
+ PropertyName = other.PropertyName;
+ Properties = new Dictionary(other.Properties);
+ }
+
+ ///
+ /// Gets or sets the this is associated with.
+ ///
+ public PageApplicationModel Page { get; set; }
+
+ ///
+ /// Gets any attributes which are annotated on the property.
+ ///
+ public IReadOnlyList Attributes { get; }
+
+ ///
+ public IDictionary Properties { get; }
+
+ ///
+ /// Gets or sets the associated with this model.
+ ///
+ public BindingInfo BindingInfo { get; set; }
+
+ ///
+ /// Gets the underlying .
+ ///
+ public PropertyInfo PropertyInfo { get; }
+
+ ///
+ /// Gets or sets the name of the property represented by this model.
+ ///
+ public string PropertyName { get; set; }
+
+ MemberInfo ICommonModel.MemberInfo => PropertyInfo;
+
+ string ICommonModel.Name => PropertyName;
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs
new file mode 100644
index 0000000000..8cc6c413af
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationModels
+{
+ ///
+ /// A model component for routing RazorPages.
+ ///
+ public class PageRouteModel
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The application relative path of the page.
+ /// The path relative to the base path for page discovery.
+ public PageRouteModel(string relativePath, string viewEnginePath)
+ {
+ if (relativePath == null)
+ {
+ throw new ArgumentNullException(nameof(relativePath));
+ }
+
+ if (viewEnginePath == null)
+ {
+ throw new ArgumentNullException(nameof(viewEnginePath));
+ }
+
+ RelativePath = relativePath;
+ ViewEnginePath = viewEnginePath;
+
+ Properties = new Dictionary();
+ Selectors = new List();
+ }
+
+ ///
+ /// A copy constructor for .
+ ///
+ /// The to copy from.
+ public PageRouteModel(PageRouteModel other)
+ {
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ RelativePath = other.RelativePath;
+ ViewEnginePath = other.ViewEnginePath;
+
+ Properties = new Dictionary(other.Properties);
+ Selectors = new List(other.Selectors.Select(m => new SelectorModel(m)));
+ }
+
+ ///
+ /// Gets the application root relative path for the page.
+ ///
+ public string RelativePath { get; }
+
+ ///
+ /// Gets the path relative to the base path for page discovery.
+ ///
+ public string ViewEnginePath { get; }
+
+ ///
+ /// Stores arbitrary metadata properties associated with the .
+ ///
+ public IDictionary Properties { get; }
+
+ ///
+ /// Gets the instances.
+ ///
+ public IList Selectors { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs
new file mode 100644
index 0000000000..966eb3876b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs
@@ -0,0 +1,18 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationModels
+{
+ ///
+ /// A context object for .
+ ///
+ public class PageRouteModelProviderContext
+ {
+ ///
+ /// Gets the instances.
+ ///
+ public IList RouteModels { get; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs
index 6df8579ecd..605dd6d9d5 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs
@@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
-using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@@ -88,11 +87,18 @@ namespace Microsoft.Extensions.DependencyInjection
ServiceDescriptor.Singleton());
services.TryAddSingleton();
services.TryAddEnumerable(
- ServiceDescriptor.Singleton());
+ ServiceDescriptor.Singleton());
services.TryAddEnumerable(
- ServiceDescriptor.Singleton());
+ ServiceDescriptor.Singleton());
+
services.TryAddEnumerable(
- ServiceDescriptor.Singleton());
+ ServiceDescriptor.Singleton());
+ services.TryAddEnumerable(
+ ServiceDescriptor.Singleton());
+ services.TryAddEnumerable(
+ ServiceDescriptor.Singleton());
+ services.TryAddEnumerable(
+ ServiceDescriptor.Singleton());
services.TryAddEnumerable(
ServiceDescriptor.Singleton());
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
index c7b46332db..bbc9582f9e 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
@@ -32,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(factory));
}
- options.Conventions.Add(new FolderConvention("/", model => model.Filters.Add(factory(model))));
+ options.ApplicationModelConventions.Add(new FolderApplicationModelConvention("/", model => model.Filters.Add(factory(model))));
return options;
}
@@ -54,7 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(filter));
}
- options.Conventions.Add(new FolderConvention("/", model => model.Filters.Add(filter)));
+ options.ApplicationModelConventions.Add(new FolderApplicationModelConvention("/", model => model.Filters.Add(filter)));
return options;
}
@@ -77,7 +77,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var anonymousFilter = new AllowAnonymousFilter();
- options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(anonymousFilter)));
+ options.ApplicationModelConventions.Add(new PageApplicationModelConvention(pageName, model => model.Filters.Add(anonymousFilter)));
return options;
}
@@ -100,7 +100,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var anonymousFilter = new AllowAnonymousFilter();
- options.Conventions.Add(new FolderConvention(folderPath, model => model.Filters.Add(anonymousFilter)));
+ options.ApplicationModelConventions.Add(new FolderApplicationModelConvention(folderPath, model => model.Filters.Add(anonymousFilter)));
return options;
}
@@ -124,7 +124,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var authorizeFilter = new AuthorizeFilter(policy);
- options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(authorizeFilter)));
+ options.ApplicationModelConventions.Add(new PageApplicationModelConvention(pageName, model => model.Filters.Add(authorizeFilter)));
return options;
}
@@ -157,7 +157,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var authorizeFilter = new AuthorizeFilter(policy);
- options.Conventions.Add(new FolderConvention(folderPath, model => model.Filters.Add(authorizeFilter)));
+ options.ApplicationModelConventions.Add(new FolderApplicationModelConvention(folderPath, model => model.Filters.Add(authorizeFilter)));
return options;
}
@@ -198,7 +198,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(route));
}
- options.Conventions.Add(new PageConvention(pageName, model =>
+ options.RouteModelConventions.Add(new PageRouteModelConvention(pageName, model =>
{
// Use the route specified in MapPageRoute for outbound routing.
foreach (var selector in model.Selectors)
@@ -218,12 +218,54 @@ namespace Microsoft.Extensions.DependencyInjection
return options;
}
- private class PageConvention : IPageApplicationModelConvention
+ private class PageRouteModelConvention : IPageRouteModelConvention
+ {
+ private readonly string _path;
+ private readonly Action _action;
+
+ public PageRouteModelConvention(string path, Action action)
+ {
+ _path = path;
+ _action = action;
+ }
+
+ public void Apply(PageRouteModel model)
+ {
+ if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase))
+ {
+ _action(model);
+ }
+ }
+ }
+
+ private class FolderRouteModelConvention : IPageRouteModelConvention
+ {
+ private readonly string _folderPath;
+ private readonly Action _action;
+
+ public FolderRouteModelConvention(string folderPath, Action action)
+ {
+ _folderPath = folderPath.TrimEnd('/');
+ _action = action;
+ }
+
+ public void Apply(PageRouteModel model)
+ {
+ var viewEnginePath = model.ViewEnginePath;
+
+ if (PathBelongsToFolder(_folderPath, viewEnginePath))
+ {
+ _action(model);
+ }
+ }
+ }
+
+ private class PageApplicationModelConvention : IPageApplicationModelConvention
{
private readonly string _path;
private readonly Action _action;
- public PageConvention(string path, Action action)
+ public PageApplicationModelConvention(string path, Action action)
{
_path = path;
_action = action;
@@ -238,12 +280,12 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
- private class FolderConvention : IPageApplicationModelConvention
+ private class FolderApplicationModelConvention : IPageApplicationModelConvention
{
private readonly string _folderPath;
private readonly Action _action;
- public FolderConvention(string folderPath, Action action)
+ public FolderApplicationModelConvention(string folderPath, Action action)
{
_folderPath = folderPath.TrimEnd('/');
_action = action;
@@ -253,16 +295,24 @@ namespace Microsoft.Extensions.DependencyInjection
{
var viewEnginePath = model.ViewEnginePath;
- var applyConvention = _folderPath == "/" ||
- (viewEnginePath.Length > _folderPath.Length &&
- viewEnginePath.StartsWith(_folderPath, StringComparison.OrdinalIgnoreCase) &&
- viewEnginePath[_folderPath.Length] == '/');
-
- if (applyConvention)
+ if (PathBelongsToFolder(_folderPath, viewEnginePath))
{
_action(model);
}
}
}
+
+ private static bool PathBelongsToFolder(string folderPath, string viewEnginePath)
+ {
+ if (folderPath == "/")
+ {
+ // Root directory covers everything.
+ return true;
+ }
+
+ return viewEnginePath.Length > folderPath.Length &&
+ viewEnginePath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase) &&
+ viewEnginePath[folderPath.Length] == '/';
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs
index 816020e60b..0c4970b5cf 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs
@@ -14,16 +14,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class PageActionDescriptorProvider : IActionDescriptorProvider
{
- private readonly List _applicationModelProviders;
+ private readonly IPageRouteModelProvider[] _routeModelProviders;
private readonly MvcOptions _mvcOptions;
private readonly RazorPagesOptions _pagesOptions;
public PageActionDescriptorProvider(
- IEnumerable pageMetadataProviders,
+ IEnumerable pageRouteModelProviders,
IOptions mvcOptionsAccessor,
IOptions pagesOptionsAccessor)
{
- _applicationModelProviders = pageMetadataProviders.OrderBy(p => p.Order).ToList();
+ _routeModelProviders = pageRouteModelProviders.OrderBy(p => p.Order).ToArray();
_mvcOptions = mvcOptionsAccessor.Value;
_pagesOptions = pagesOptionsAccessor.Value;
}
@@ -32,51 +32,40 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void OnProvidersExecuting(ActionDescriptorProviderContext context)
{
- var pageApplicationModels = BuildModel();
+ var pageRouteModels = BuildModel();
- for (var i = 0; i < pageApplicationModels.Count; i++)
+ for (var i = 0; i < pageRouteModels.Count; i++)
{
- AddActionDescriptors(context.Results, pageApplicationModels[i]);
+ AddActionDescriptors(context.Results, pageRouteModels[i]);
}
}
- protected IList BuildModel()
+ protected IList BuildModel()
{
- var context = new PageApplicationModelProviderContext();
+ var context = new PageRouteModelProviderContext();
- for (var i = 0; i < _applicationModelProviders.Count; i++)
+ for (var i = 0; i < _routeModelProviders.Length; i++)
{
- _applicationModelProviders[i].OnProvidersExecuting(context);
+ _routeModelProviders[i].OnProvidersExecuting(context);
}
- for (var i = _applicationModelProviders.Count - 1; i >= 0; i--)
+ for (var i = _routeModelProviders.Length - 1; i >= 0; i--)
{
- _applicationModelProviders[i].OnProvidersExecuted(context);
+ _routeModelProviders[i].OnProvidersExecuted(context);
}
- return context.Results;
+ return context.RouteModels;
}
public void OnProvidersExecuted(ActionDescriptorProviderContext context)
{
}
- private void AddActionDescriptors(IList actions, PageApplicationModel model)
+ private void AddActionDescriptors(IList actions, PageRouteModel model)
{
- for (var i = 0; i < _pagesOptions.Conventions.Count; i++)
+ for (var i = 0; i < _pagesOptions.RouteModelConventions.Count; i++)
{
- _pagesOptions.Conventions[i].Apply(model);
- }
-
- var filters = new List(_mvcOptions.Filters.Count + model.Filters.Count);
- for (var i = 0; i < _mvcOptions.Filters.Count; i++)
- {
- filters.Add(new FilterDescriptor(_mvcOptions.Filters[i], FilterScope.Global));
- }
-
- for (var i = 0; i < model.Filters.Count; i++)
- {
- filters.Add(new FilterDescriptor(model.Filters[i], FilterScope.Action));
+ _pagesOptions.RouteModelConventions[i].Apply(model);
}
foreach (var selector in model.Selectors)
@@ -92,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching,
},
DisplayName = $"Page: {model.ViewEnginePath}",
- FilterDescriptors = filters,
+ FilterDescriptors = Array.Empty(),
Properties = new Dictionary(model.Properties),
RelativePath = model.RelativePath,
RouteValues = new Dictionary(StringComparer.OrdinalIgnoreCase)
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs
new file mode 100644
index 0000000000..ad047c1ede
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs
@@ -0,0 +1,48 @@
+// 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.Linq;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.Authorization;
+using Microsoft.AspNetCore.Mvc.Internal;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class AuthorizationPageApplicationModelProvider : IPageApplicationModelProvider
+ {
+ private readonly IAuthorizationPolicyProvider _policyProvider;
+
+ public AuthorizationPageApplicationModelProvider(IAuthorizationPolicyProvider policyProvider)
+ {
+ _policyProvider = policyProvider;
+ }
+
+ // The order is set to execute after the DefaultPageApplicationModelProvider.
+ public int Order => -1000 + 10;
+
+ public void OnProvidersExecuting(PageApplicationModelProviderContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var pageModel = context.PageApplicationModel;
+ var authorizeData = pageModel.HandlerTypeAttributes.OfType().ToArray();
+ if (authorizeData.Length > 0)
+ {
+ pageModel.Filters.Add(AuthorizationApplicationModelProvider.GetFilter(_policyProvider, authorizeData));
+ }
+ foreach (var attribute in pageModel.HandlerTypeAttributes.OfType())
+ {
+ pageModel.Filters.Add(new AllowAnonymousFilter());
+ }
+ }
+
+ public void OnProvidersExecuted(PageApplicationModelProviderContext context)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs
new file mode 100644
index 0000000000..2103a4606b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs
@@ -0,0 +1,31 @@
+// 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.ApplicationModels;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class AutoValidateAntiforgeryPageApplicationModelProvider : IPageApplicationModelProvider
+ {
+ // The order is set to execute after the DefaultPageApplicationModelProvider.
+ public int Order => -1000 + 10;
+
+ public void OnProvidersExecuted(PageApplicationModelProviderContext context)
+ {
+ }
+
+ public void OnProvidersExecuting(PageApplicationModelProviderContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var pageApplicationModel = context.PageApplicationModel;
+
+ // Always require an antiforgery token on post
+ pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs
new file mode 100644
index 0000000000..c529c7bb4f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs
@@ -0,0 +1,119 @@
+// 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.Linq;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ ///
+ /// Constructs a from an .
+ ///
+ public static class CompiledPageActionDescriptorBuilder
+ {
+ ///
+ /// Creates a from the specified .
+ ///
+ /// The .
+ /// The .
+ public static CompiledPageActionDescriptor Build(PageApplicationModel applicationModel)
+ {
+ var boundProperties = CreateBoundProperties(applicationModel);
+ var filters = applicationModel.Filters
+ .Select(f => new FilterDescriptor(f, FilterScope.Action))
+ .ToArray();
+ var handlerMethods = CreateHandlerMethods(applicationModel);
+
+ var actionDescriptor = applicationModel.ActionDescriptor;
+ return new CompiledPageActionDescriptor(actionDescriptor)
+ {
+ ActionConstraints = actionDescriptor.ActionConstraints,
+ AttributeRouteInfo = actionDescriptor.AttributeRouteInfo,
+ BoundProperties = boundProperties,
+ FilterDescriptors = filters,
+ HandlerMethods = handlerMethods,
+ HandlerTypeInfo = applicationModel.HandlerType,
+ ModelTypeInfo = applicationModel.ModelType,
+ RouteValues = actionDescriptor.RouteValues,
+ PageTypeInfo = applicationModel.PageType,
+ Properties = applicationModel.Properties,
+ };
+ }
+
+ // Internal for unit testing
+ internal static HandlerMethodDescriptor[] CreateHandlerMethods(PageApplicationModel applicationModel)
+ {
+ var handlerModels = applicationModel.HandlerMethods;
+ var handlerDescriptors = new HandlerMethodDescriptor[handlerModels.Count];
+
+ for (var i = 0; i < handlerDescriptors.Length; i++)
+ {
+ var handlerModel = handlerModels[i];
+
+ handlerDescriptors[i] = new HandlerMethodDescriptor
+ {
+ HttpMethod = handlerModel.HttpMethod,
+ Name = handlerModel.HandlerName,
+ MethodInfo = handlerModel.MethodInfo,
+ Parameters = CreateHandlerParameters(handlerModel),
+ };
+ }
+
+ return handlerDescriptors;
+ }
+
+ // internal for unit testing
+ internal static HandlerParameterDescriptor[] CreateHandlerParameters(PageHandlerModel handlerModel)
+ {
+ var methodParameters = handlerModel.Parameters;
+ var parameters = new HandlerParameterDescriptor[methodParameters.Count];
+
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ var parameterModel = methodParameters[i];
+
+ parameters[i] = new HandlerParameterDescriptor
+ {
+ BindingInfo = parameterModel.BindingInfo,
+ Name = parameterModel.ParameterName,
+ ParameterInfo = parameterModel.ParameterInfo,
+ ParameterType = parameterModel.ParameterInfo.ParameterType,
+ };
+ }
+
+ return parameters;
+ }
+
+ // internal for unit testing
+ internal static PageBoundPropertyDescriptor[] CreateBoundProperties(PageApplicationModel applicationModel)
+ {
+ var results = new List();
+ var properties = applicationModel.HandlerProperties;
+ for (var i = 0; i < properties.Count; i++)
+ {
+ var propertyModel = properties[i];
+
+ // Only add properties which are explicitly marked to bind.
+ if (propertyModel.BindingInfo == null)
+ {
+ continue;
+ }
+
+ var descriptor = new PageBoundPropertyDescriptor
+ {
+ Property = propertyModel.PropertyInfo,
+ Name = propertyModel.PropertyName,
+ BindingInfo = propertyModel.BindingInfo,
+ ParameterType = propertyModel.PropertyInfo.PropertyType,
+ };
+
+ results.Add(descriptor);
+ }
+
+ return results.ToArray();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs
similarity index 80%
rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageApplicationModelProvider.cs
rename to src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs
index 61779b3a03..e8110ecf9f 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageApplicationModelProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs
@@ -12,14 +12,14 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
- public class CompiledPageApplicationModelProvider : IPageApplicationModelProvider
+ public class CompiledPageRouteModelProvider : IPageRouteModelProvider
{
private readonly object _cacheLock = new object();
private readonly ApplicationPartManager _applicationManager;
private readonly RazorPagesOptions _pagesOptions;
- private List _cachedApplicationModels;
+ private List _cachedModels;
- public CompiledPageApplicationModelProvider(
+ public CompiledPageRouteModelProvider(
ApplicationPartManager applicationManager,
IOptions pagesOptionsAccessor)
{
@@ -29,17 +29,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public int Order => -1000;
- public void OnProvidersExecuting(PageApplicationModelProviderContext context)
+ public void OnProvidersExecuting(PageRouteModelProviderContext context)
{
EnsureCache();
- for (var i = 0; i < _cachedApplicationModels.Count; i++)
+ for (var i = 0; i < _cachedModels.Count; i++)
{
- var pageModel = _cachedApplicationModels[i];
- context.Results.Add(new PageApplicationModel(pageModel));
+ var pageModel = _cachedModels[i];
+ context.RouteModels.Add(new PageRouteModel(pageModel));
}
}
- public void OnProvidersExecuted(PageApplicationModelProviderContext context)
+ public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}
@@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
lock (_cacheLock)
{
- if (_cachedApplicationModels != null)
+ if (_cachedModels != null)
{
return;
}
@@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
rootDirectory = rootDirectory + "/";
}
- var cachedApplicationModels = new List();
+ var cachedApplicationModels = new List();
foreach (var viewDescriptor in GetViewDescriptors(_applicationManager))
{
if (!viewDescriptor.RelativePath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase))
@@ -67,14 +67,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
var viewEnginePath = GetViewEnginePath(rootDirectory, viewDescriptor.RelativePath);
- var model = new PageApplicationModel(viewDescriptor.RelativePath, viewEnginePath);
+ var model = new PageRouteModel(viewDescriptor.RelativePath, viewEnginePath);
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
PageSelectorModel.PopulateDefaults(model, pageAttribute.RouteTemplate);
cachedApplicationModels.Add(model);
}
- _cachedApplicationModels = cachedApplicationModels;
+ _cachedModels = cachedApplicationModels;
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs
new file mode 100644
index 0000000000..d14accf218
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs
@@ -0,0 +1,353 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+using Microsoft.Extensions.Internal;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class DefaultPageApplicationModelProvider : IPageApplicationModelProvider
+ {
+ private const string ModelPropertyName = "Model";
+ private readonly FilterCollection _globalFilters;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ ///
+ public DefaultPageApplicationModelProvider(IOptions mvcOptions)
+ {
+ _globalFilters = mvcOptions.Value.Filters;
+ }
+
+ ///
+ public int Order => -1000;
+
+ ///
+ public void OnProvidersExecuting(PageApplicationModelProviderContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ context.PageApplicationModel = CreateModel(context.ActionDescriptor, context.PageType);
+ }
+
+ ///
+ public void OnProvidersExecuted(PageApplicationModelProviderContext context)
+ {
+ }
+
+ ///
+ /// Creates a for the given .
+ ///
+ /// The .
+ /// The .
+ /// A for the given .
+ protected virtual PageApplicationModel CreateModel(
+ PageActionDescriptor actionDescriptor,
+ TypeInfo pageTypeInfo)
+ {
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException(nameof(actionDescriptor));
+ }
+
+ if (pageTypeInfo == null)
+ {
+ throw new ArgumentNullException(nameof(pageTypeInfo));
+ }
+
+ // Pages always have a model type. If it's not set explicitly by the developer using
+ // @model, it will be the same as the page type.
+ var modelTypeInfo = pageTypeInfo.GetProperty(ModelPropertyName)?.PropertyType?.GetTypeInfo();
+
+ // Now we want to find the handler methods. If the model defines any handlers, then we'll use those,
+ // otherwise look at the page itself (unless the page IS the model, in which case we already looked).
+ TypeInfo handlerType;
+
+ var handlerModels = modelTypeInfo == null ? null : CreateHandlerModels(modelTypeInfo);
+ if (handlerModels?.Count > 0)
+ {
+ handlerType = modelTypeInfo;
+ }
+ else
+ {
+ handlerType = pageTypeInfo.GetTypeInfo();
+ handlerModels = CreateHandlerModels(pageTypeInfo);
+ }
+
+ var handlerTypeAttributes = handlerType.GetCustomAttributes(inherit: true);
+ var pageModel = new PageApplicationModel(
+ actionDescriptor,
+ handlerType,
+ handlerTypeAttributes)
+ {
+ PageType = pageTypeInfo,
+ ModelType = modelTypeInfo,
+ };
+
+ for (var i = 0; i < handlerModels.Count; i++)
+ {
+ var handlerModel = handlerModels[i];
+ handlerModel.Page = pageModel;
+ pageModel.HandlerMethods.Add(handlerModel);
+ }
+
+ PopulateHandlerProperties(pageModel);
+
+ for (var i = 0; i < _globalFilters.Count; i++)
+ {
+ pageModel.Filters.Add(_globalFilters[i]);
+ }
+
+ for (var i = 0; i < handlerTypeAttributes.Length; i++)
+ {
+ if (handlerTypeAttributes[i] is IFilterMetadata filter)
+ {
+ pageModel.Filters.Add(filter);
+ }
+ }
+
+ return pageModel;
+ }
+
+ // Internal for unit testing
+ internal void PopulateHandlerProperties(PageApplicationModel pageModel)
+ {
+ var properties = PropertyHelper.GetVisibleProperties(pageModel.HandlerType.AsType());
+ for (var i = 0; i < properties.Length; i++)
+ {
+ var propertyModel = CreatePropertyModel(properties[i].Property);
+ if (propertyModel != null)
+ {
+ propertyModel.Page = pageModel;
+ pageModel.HandlerProperties.Add(propertyModel);
+ }
+ }
+ }
+
+ // Internal for unit testing
+ internal IList CreateHandlerModels(TypeInfo handlerTypeInfo)
+ {
+ var methods = handlerTypeInfo.GetMethods();
+ var results = new List();
+
+ for (var i = 0; i < methods.Length; i++)
+ {
+ var handler = CreateHandlerModel(methods[i]);
+ if (handler != null)
+ {
+ results.Add(handler);
+ }
+ }
+
+ return results;
+ }
+
+ ///
+ /// Creates a for the specified .s
+ ///
+ /// The .
+ /// The .
+ protected virtual PageHandlerModel CreateHandlerModel(MethodInfo method)
+ {
+ if (method == null)
+ {
+ throw new ArgumentNullException(nameof(method));
+ }
+
+ if (!IsHandler(method))
+ {
+ return null;
+ }
+
+ if (method.IsDefined(typeof(NonHandlerAttribute)))
+ {
+ return null;
+ }
+
+ if (method.DeclaringType.GetTypeInfo().IsDefined(typeof(PagesBaseClassAttribute)))
+ {
+ return null;
+ }
+
+ if (!TryParseHandlerMethod(method.Name, out var httpMethod, out var handlerName))
+ {
+ return null;
+ }
+
+ var handlerModel = new PageHandlerModel(
+ method,
+ method.GetCustomAttributes(inherit: true))
+ {
+ Name = method.Name,
+ HandlerName = handlerName,
+ HttpMethod = httpMethod,
+ };
+
+ var methodParameters = handlerModel.MethodInfo.GetParameters();
+
+ for (var i = 0; i < methodParameters.Length; i++)
+ {
+ var parameter = methodParameters[i];
+ var parameterModel = CreateParameterModel(parameter);
+ parameterModel.Handler = handlerModel;
+
+ handlerModel.Parameters.Add(parameterModel);
+ }
+
+ return handlerModel;
+ }
+
+ ///
+ /// Creates a for the specified .
+ ///
+ /// The .
+ /// The .
+ protected virtual PageParameterModel CreateParameterModel(ParameterInfo parameter)
+ {
+ if (parameter == null)
+ {
+ throw new ArgumentNullException(nameof(parameter));
+ }
+
+ return new PageParameterModel(parameter, parameter.GetCustomAttributes(inherit: true))
+ {
+ BindingInfo = BindingInfo.GetBindingInfo(parameter.GetCustomAttributes()),
+ ParameterName = parameter.Name,
+ };
+ }
+
+ ///
+ /// Creates a for the .
+ ///
+ /// The .
+ /// The .
+ protected virtual PagePropertyModel CreatePropertyModel(PropertyInfo property)
+ {
+ if (property == null)
+ {
+ throw new ArgumentNullException(nameof(property));
+ }
+
+ var attributes = property.GetCustomAttributes(inherit: true);
+ var bindingInfo = BindingInfo.GetBindingInfo(attributes);
+
+ var model = new PagePropertyModel(property, property.GetCustomAttributes(inherit: true))
+ {
+ PropertyName = property.Name,
+ BindingInfo = bindingInfo,
+ };
+
+ return model;
+ }
+
+ ///
+ /// Determines if the specified is a handler.
+ ///
+ /// The .
+ /// true if the is a handler. Otherwise false .
+ ///
+ /// Override this method to provide custom logic to determine which methods are considered handlers.
+ ///
+ protected virtual bool IsHandler(MethodInfo methodInfo)
+ {
+ // The SpecialName bit is set to flag members that are treated in a special way by some compilers
+ // (such as property accessors and operator overloading methods).
+ if (methodInfo.IsSpecialName)
+ {
+ return false;
+ }
+
+ // Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
+ if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
+ {
+ return false;
+ }
+
+ if (methodInfo.IsStatic)
+ {
+ return false;
+ }
+
+ if (methodInfo.IsAbstract)
+ {
+ return false;
+ }
+
+ if (methodInfo.IsConstructor)
+ {
+ return false;
+ }
+
+ if (methodInfo.IsGenericMethod)
+ {
+ return false;
+ }
+
+ return methodInfo.IsPublic;
+ }
+
+ internal static bool TryParseHandlerMethod(string methodName, out string httpMethod, out string handler)
+ {
+ httpMethod = null;
+ handler = null;
+
+ // Handler method names always start with "On"
+ if (!methodName.StartsWith("On") || methodName.Length <= "On".Length)
+ {
+ return false;
+ }
+
+ // Now we parse the method name according to our conventions to determine the required HTTP method
+ // and optional 'handler name'.
+ //
+ // Valid names look like:
+ // - OnGet
+ // - OnPost
+ // - OnFooBar
+ // - OnTraceAsync
+ // - OnPostEditAsync
+
+ var start = "On".Length;
+ var length = methodName.Length;
+ if (methodName.EndsWith("Async", StringComparison.Ordinal))
+ {
+ length -= "Async".Length;
+ }
+
+ if (start == length)
+ {
+ // There are no additional characters. This is "On" or "OnAsync".
+ return false;
+ }
+
+ // The http method follows "On" and is required to be at least one character. We use casing
+ // to determine where it ends.
+ var handlerNameStart = start + 1;
+ for (; handlerNameStart < length; handlerNameStart++)
+ {
+ if (char.IsUpper(methodName[handlerNameStart]))
+ {
+ break;
+ }
+ }
+
+ httpMethod = methodName.Substring(start, handlerNameStart - start);
+
+ // The handler name follows the http method and is optional. It includes everything up to the end
+ // excluding the "Async" suffix (if present).
+ handler = handlerNameStart == length ? null : methodName.Substring(handlerNameStart, length - handlerNameStart);
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs
index df4409b993..f0c588e932 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs
@@ -3,267 +3,61 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
-using Microsoft.Extensions.Internal;
+using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class DefaultPageLoader : IPageLoader
{
- private const string ModelPropertyName = "Model";
-
+ private readonly IPageApplicationModelProvider[] _applicationModelProviders;
private readonly IViewCompilerProvider _viewCompilerProvider;
+ private readonly RazorPagesOptions _options;
- public DefaultPageLoader(IViewCompilerProvider viewCompilerProvider)
+ public DefaultPageLoader(
+ IEnumerable applicationModelProviders,
+ IViewCompilerProvider viewCompilerProvider,
+ IOptions pageOptions)
{
+ _applicationModelProviders = applicationModelProviders.ToArray();
_viewCompilerProvider = viewCompilerProvider;
+ _options = pageOptions.Value;
}
private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler();
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
{
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException(nameof(actionDescriptor));
+ }
+
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
var viewDescriptor = compileTask.GetAwaiter().GetResult();
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
- return CreateDescriptor(actionDescriptor, pageAttribute);
- }
-
- // Internal for unit testing
- internal static CompiledPageActionDescriptor CreateDescriptor(
- PageActionDescriptor actionDescriptor,
- RazorPageAttribute pageAttribute)
- {
- var pageType = pageAttribute.ViewType.GetTypeInfo();
-
- // Pages always have a model type. If it's not set explicitly by the developer using
- // @model, it will be the same as the page type.
- var modelType = pageAttribute.ViewType.GetProperty(ModelPropertyName)?.PropertyType?.GetTypeInfo();
-
- // Now we want to find the handler methods. If the model defines any handlers, then we'll use those,
- // otherwise look at the page itself (unless the page IS the model, in which case we already looked).
- TypeInfo handlerType;
-
- var handlerMethods = modelType == null ? null : CreateHandlerMethods(modelType);
- if (handlerMethods?.Length > 0)
+ var context = new PageApplicationModelProviderContext(actionDescriptor, pageAttribute.ViewType.GetTypeInfo());
+ for (var i = 0; i < _applicationModelProviders.Length; i++)
{
- handlerType = modelType;
- }
- else
- {
- handlerType = pageType;
- handlerMethods = CreateHandlerMethods(pageType);
+ _applicationModelProviders[i].OnProvidersExecuting(context);
}
- var boundProperties = CreateBoundProperties(handlerType);
-
- return new CompiledPageActionDescriptor(actionDescriptor)
+ for (var i = _applicationModelProviders.Length - 1; i >= 0; i--)
{
- ActionConstraints = actionDescriptor.ActionConstraints,
- AttributeRouteInfo = actionDescriptor.AttributeRouteInfo,
- BoundProperties = boundProperties,
- FilterDescriptors = actionDescriptor.FilterDescriptors,
- HandlerMethods = handlerMethods,
- HandlerTypeInfo = handlerType,
- ModelTypeInfo = modelType,
- RouteValues = actionDescriptor.RouteValues,
- PageTypeInfo = pageType,
- Properties = actionDescriptor.Properties,
- };
- }
-
- internal static HandlerMethodDescriptor[] CreateHandlerMethods(TypeInfo type)
- {
- var methods = type.GetMethods();
- var results = new List();
-
- for (var i = 0; i < methods.Length; i++)
- {
- var method = methods[i];
- if (!IsValidHandlerMethod(method))
- {
- continue;
- }
-
- if (method.IsDefined(typeof(NonHandlerAttribute)))
- {
- continue;
- }
-
- if (method.DeclaringType.GetTypeInfo().IsDefined(typeof(PagesBaseClassAttribute)))
- {
- continue;
- }
-
- if (!TryParseHandlerMethod(method.Name, out var httpMethod, out var handler))
- {
- continue;
- }
-
- var parameters = CreateHandlerParameters(method);
-
- var handlerMethodDescriptor = new HandlerMethodDescriptor()
- {
- MethodInfo = method,
- Name = handler,
- HttpMethod = httpMethod,
- Parameters = parameters,
- };
-
- results.Add(handlerMethodDescriptor);
+ _applicationModelProviders[i].OnProvidersExecuted(context);
}
- return results.ToArray();
- }
-
- // Internal for testing
- internal static bool TryParseHandlerMethod(string methodName, out string httpMethod, out string handler)
- {
- httpMethod = null;
- handler = null;
-
- // Handler method names always start with "On"
- if (!methodName.StartsWith("On") || methodName.Length <= "On".Length)
+ for (var i = 0; i < _options.ApplicationModelConventions.Count; i++)
{
- return false;
+ _options.ApplicationModelConventions[i].Apply(context.PageApplicationModel);
}
- // Now we parse the method name according to our conventions to determine the required HTTP method
- // and optional 'handler name'.
- //
- // Valid names look like:
- // - OnGet
- // - OnPost
- // - OnFooBar
- // - OnTraceAsync
- // - OnPostEditAsync
-
- var start = "On".Length;
- var length = methodName.Length;
- if (methodName.EndsWith("Async", StringComparison.Ordinal))
- {
- length -= "Async".Length;
- }
-
- if (start == length)
- {
- // There are no additional characters. This is "On" or "OnAsync".
- return false;
- }
-
- // The http method follows "On" and is required to be at least one character. We use casing
- // to determine where it ends.
- var handlerNameStart = start + 1;
- for (; handlerNameStart < length; handlerNameStart++)
- {
- if (char.IsUpper(methodName[handlerNameStart]))
- {
- break;
- }
- }
-
- httpMethod = methodName.Substring(start, handlerNameStart - start);
-
- // The handler name follows the http method and is optional. It includes everything up to the end
- // excluding the "Async" suffix (if present).
- handler = handlerNameStart == length ? null : methodName.Substring(handlerNameStart, length - handlerNameStart);
- return true;
- }
-
- private static bool IsValidHandlerMethod(MethodInfo methodInfo)
- {
- // The SpecialName bit is set to flag members that are treated in a special way by some compilers
- // (such as property accessors and operator overloading methods).
- if (methodInfo.IsSpecialName)
- {
- return false;
- }
-
- // Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
- if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
- {
- return false;
- }
-
- if (methodInfo.IsStatic)
- {
- return false;
- }
-
- if (methodInfo.IsAbstract)
- {
- return false;
- }
-
- if (methodInfo.IsConstructor)
- {
- return false;
- }
-
- if (methodInfo.IsGenericMethod)
- {
- return false;
- }
-
- return methodInfo.IsPublic;
- }
-
- // Internal for testing
- internal static HandlerParameterDescriptor[] CreateHandlerParameters(MethodInfo methodInfo)
- {
- var methodParameters = methodInfo.GetParameters();
- var parameters = new HandlerParameterDescriptor[methodParameters.Length];
-
- for (var i = 0; i < methodParameters.Length; i++)
- {
- var parameter = methodParameters[i];
-
- parameters[i] = new HandlerParameterDescriptor()
- {
- BindingInfo = BindingInfo.GetBindingInfo(parameter.GetCustomAttributes()),
- Name = parameter.Name,
- ParameterInfo = parameter,
- ParameterType = parameter.ParameterType,
- };
- }
-
- return parameters;
- }
-
- // Internal for testing
- internal static PageBoundPropertyDescriptor[] CreateBoundProperties(TypeInfo type)
- {
- var properties = PropertyHelper.GetVisibleProperties(type.AsType());
-
- var results = new List();
- for (var i = 0; i < properties.Length; i++)
- {
- var property = properties[i];
- var bindingInfo = BindingInfo.GetBindingInfo(property.Property.GetCustomAttributes());
-
- // If there's no binding info then that means there are no model binding attributes on the
- // property. So we won't bind this property.
- if (bindingInfo == null)
- {
- continue;
- }
-
- var descriptor = new PageBoundPropertyDescriptor()
- {
- BindingInfo = bindingInfo,
- Name = property.Name,
- Property = property.Property,
- ParameterType = property.Property.PropertyType,
- };
-
- results.Add(descriptor);
- }
-
- return results.ToArray();
+ return CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
index 99b7a7a991..ff9863135c 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
@@ -94,11 +94,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
var cache = CurrentCache;
- PageActionInvokerCacheEntry cacheEntry;
-
IFilterMetadata[] filters;
- if (!cache.Entries.TryGetValue(actionDescriptor, out cacheEntry))
+ if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
{
+ actionContext.ActionDescriptor = _loader.Load(actionDescriptor);
+
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, actionContext);
filters = filterFactoryResult.Filters;
cacheEntry = CreateCacheEntry(context, filterFactoryResult.CacheableFilters);
@@ -166,8 +166,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
ActionInvokerProviderContext context,
FilterItem[] cachedFilters)
{
- var actionDescriptor = (PageActionDescriptor)context.ActionContext.ActionDescriptor;
- var compiledActionDescriptor = _loader.Load(actionDescriptor);
+ var compiledActionDescriptor = (CompiledPageActionDescriptor)context.ActionContext.ActionDescriptor;
var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(compiledActionDescriptor.ModelTypeInfo);
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageFilterApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageFilterApplicationModelProvider.cs
deleted file mode 100644
index 24ba99ff26..0000000000
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageFilterApplicationModelProvider.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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.ApplicationModels;
-using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
-
-namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
-{
- public class PageFilterApplicationModelProvider : IPageApplicationModelProvider
- {
- /// This order ensures that runs after
- /// and .
- ///
- public int Order => -1000 + 10;
-
- public void OnProvidersExecuted(PageApplicationModelProviderContext context)
- {
- // Do nothing
- }
-
- public void OnProvidersExecuting(PageApplicationModelProviderContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- for (var i = 0; i < context.Results.Count; i++)
- {
- var pageApplicationModel = context.Results[i];
-
- // Support for [TempData] on properties
- pageApplicationModel.Filters.Add(new PageSaveTempDataPropertyFilterFactory());
-
- // Always require an antiforgery token on post
- pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
- }
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSelectorModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSelectorModel.cs
index 35e2bb4bd0..f8db3320ad 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSelectorModel.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSelectorModel.cs
@@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
private const string IndexFileName = "Index.cshtml";
- public static void PopulateDefaults(PageApplicationModel model, string routeTemplate)
+ public static void PopulateDefaults(PageRouteModel model, string routeTemplate)
{
if (AttributeRouteModel.IsOverridePattern(routeTemplate))
{
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs
similarity index 74%
rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageApplicationModelProvider.cs
rename to src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs
index d80651ad27..3b27adf383 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageApplicationModelProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs
@@ -9,29 +9,29 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
- public class RazorProjectPageApplicationModelProvider : IPageApplicationModelProvider
+ public class RazorProjectPageRouteModelProvider : IPageRouteModelProvider
{
private readonly RazorProject _project;
private readonly RazorPagesOptions _pagesOptions;
private readonly ILogger _logger;
- public RazorProjectPageApplicationModelProvider(
+ public RazorProjectPageRouteModelProvider(
RazorProject razorProject,
IOptions pagesOptionsAccessor,
ILoggerFactory loggerFactory)
{
_project = razorProject;
_pagesOptions = pagesOptionsAccessor.Value;
- _logger = loggerFactory.CreateLogger();
+ _logger = loggerFactory.CreateLogger();
}
public int Order => -1000;
- public void OnProvidersExecuted(PageApplicationModelProviderContext context)
+ public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}
- public void OnProvidersExecuting(PageApplicationModelProviderContext context)
+ public void OnProvidersExecuting(PageRouteModelProviderContext context)
{
foreach (var item in _project.EnumerateItems(_pagesOptions.RootDirectory))
{
@@ -47,12 +47,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
continue;
}
- var pageApplicationModel = new PageApplicationModel(
+ var routeModel = new PageRouteModel(
relativePath: item.CombinedPath,
viewEnginePath: item.PathWithoutExtension);
- PageSelectorModel.PopulateDefaults(pageApplicationModel, routeTemplate);
+ PageSelectorModel.PopulateDefaults(routeModel, routeTemplate);
- context.Results.Add(pageApplicationModel);
+ context.RouteModels.Add(routeModel);
}
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs
new file mode 100644
index 0000000000..7cfaaedfbd
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs
@@ -0,0 +1,31 @@
+// 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.ApplicationModels;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class TempDataFilterPageApplicationModelProvider : IPageApplicationModelProvider
+ {
+ // The order is set to execute after the DefaultPageApplicationModelProvider.
+ public int Order => -1000 + 10;
+
+ public void OnProvidersExecuted(PageApplicationModelProviderContext context)
+ {
+ }
+
+ public void OnProvidersExecuting(PageApplicationModelProviderContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var pageApplicationModel = context.PageApplicationModel;
+
+ // Support for [TempData] on properties
+ pageApplicationModel.Filters.Add(new PageSaveTempDataPropertyFilterFactory());
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj b/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj
index 8b5913ca2a..83ab12f8f7 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs
index 5fd0321190..e562414a7a 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs
@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
@@ -15,10 +17,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
private string _root = "/Pages";
///
- /// Gets a list of instances that will be applied to
+ /// Gets a list of instances that will be applied to
/// the when discovering Razor Pages.
///
- public IList Conventions { get; } = new List();
+ public IList RouteModelConventions { get; } = new List();
+
+ ///
+ /// Gets a list of instances that will be applied to
+ /// the when discovering Razor Pages.
+ ///
+ public IList ApplicationModelConventions { get; } = new List();
///
/// Application relative path used as the root of discovery for Razor Page files.
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
index fdbf5893d0..6edff8f491 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
@@ -1050,6 +1050,30 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.StartsWith(expected, response.Trim());
}
+ [Fact]
+ public async Task AuthFiltersAppliedToPageModel_AreExecuted()
+ {
+ // Act
+ var response = await Client.GetAsync("/ModelWithAuthFilter");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+ Assert.Equal("/Login?ReturnUrl=%2FModelWithAuthFilter", response.Headers.Location.PathAndQuery);
+ }
+
+ [Fact]
+ public async Task PageFiltersAppliedToPageModel_AreExecuted()
+ {
+ // Arrange
+ var expected = "Hello from OnGetEdit";
+
+ // Act
+ var response = await Client.GetStringAsync("/ModelWithPageFilter");
+
+ // Assert
+ Assert.Equal(expected, response.Trim());
+ }
+
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs
index 711e221ab3..92bcfb86c9 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs
@@ -115,6 +115,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("/Login?ReturnUrl=%2FConventions%2FAuthFolder", response.Headers.Location.PathAndQuery);
}
+ [Fact]
+ public async Task AuthConvention_AppliedToFolders_CanByOverridenByFiltersOnModel()
+ {
+ // Act
+ var response = await Client.GetStringAsync("/Conventions/AuthFolder/AnonymousViaModel");
+
+ // Assert
+ Assert.Equal("Hello from Anonymous", response.Trim());
+ }
+
[Fact]
public async Task ViewStart_IsDiscoveredWhenRootDirectoryIsSpecified()
{
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs
index 548fc09dbd..0d57132c92 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs
@@ -18,21 +18,23 @@ namespace Microsoft.Extensions.DependencyInjection
{
// Arrange
var services = new ServiceCollection().AddOptions();
- var expected = Mock.Of();
+ var applicationModelConvention = Mock.Of();
+ var routeModelConvention = Mock.Of();
var builder = new MvcBuilder(services, new ApplicationPartManager());
builder.AddRazorPagesOptions(options =>
{
- options.Conventions.Add(expected);
+ options.ApplicationModelConventions.Add(applicationModelConvention);
+ options.RouteModelConventions.Add(routeModelConvention);
});
var serviceProvider = services.BuildServiceProvider();
var accessor = serviceProvider.GetRequiredService>();
- // Act
- var conventions = accessor.Value.Conventions;
+ // Act & Assert
+ var conventions = accessor.Value.ApplicationModelConventions;
// Assert
Assert.Collection(conventions,
- convention => Assert.Same(expected, convention));
+ convention => Assert.Same(applicationModelConvention, convention));
}
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
index 9741a0456f..99d45ad3b0 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
@@ -1,6 +1,7 @@
// 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.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
@@ -21,9 +22,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
- new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
- new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@@ -44,9 +45,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
- new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
- new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@@ -79,9 +80,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
- new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
- new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@@ -117,9 +118,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
- new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
- new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@@ -146,9 +147,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
- new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
- new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@@ -177,9 +178,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
- new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
- new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@@ -214,9 +215,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
- new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
- new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@@ -249,7 +250,7 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
- new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml")
+ new PageRouteModel("/Pages/Index.cshtml", "/Index.cshtml")
{
Selectors =
{
@@ -257,7 +258,7 @@ namespace Microsoft.Extensions.DependencyInjection
CreateSelectorModel(""),
}
},
- new PageApplicationModel("/Pages/About.cshtml", "/About.cshtml")
+ new PageRouteModel("/Pages/About.cshtml", "/About.cshtml")
{
Selectors =
{
@@ -316,9 +317,9 @@ namespace Microsoft.Extensions.DependencyInjection
};
}
- private static void ApplyConventions(RazorPagesOptions options, PageApplicationModel[] models)
+ private static void ApplyConventions(RazorPagesOptions options, PageRouteModel[] models)
{
- foreach (var convention in options.Conventions)
+ foreach (var convention in options.RouteModelConventions)
{
foreach (var model in models)
{
@@ -326,5 +327,26 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
}
+ private static void ApplyConventions(RazorPagesOptions options, PageApplicationModel[] models)
+ {
+ foreach (var convention in options.ApplicationModelConventions)
+ {
+ foreach (var model in models)
+ {
+ convention.Apply(model);
+ }
+ }
+ }
+
+ private PageApplicationModel CreateApplicationModel(string relativePath, string viewEnginePath)
+ {
+ var descriptor = new PageActionDescriptor
+ {
+ ViewEnginePath = viewEnginePath,
+ RelativePath = relativePath,
+ };
+
+ return new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs
index 95069bc9de..03e7ca778e 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs
@@ -4,11 +4,8 @@
using System;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
-using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
-using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
-using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.Options;
using Moq;
@@ -22,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_DoesNotAddDescriptorsIfNoApplicationModelsAreDiscovered()
{
// Arrange
- var applicationModelProvider = new TestPageApplicationModelProvider();
+ var applicationModelProvider = new TestPageRouteModelProvider();
var provider = new PageActionDescriptorProvider(
new[] { applicationModelProvider },
GetAccessor(),
@@ -40,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_AddsDescriptorsForModelWithSelector()
{
// Arrange
- var model = new PageApplicationModel("/Test.cshtml", "/Test")
+ var model = new PageRouteModel("/Test.cshtml", "/Test")
{
Selectors =
{
@@ -53,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
}
}
};
- var applicationModelProvider = new TestPageApplicationModelProvider(model);
+ var applicationModelProvider = new TestPageRouteModelProvider(model);
var provider = new PageActionDescriptorProvider(
new[] { applicationModelProvider },
GetAccessor(),
@@ -75,15 +72,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_AddsActionDescriptorForEachSelector()
{
// Arrange
- var applicationModelProvider = new TestPageApplicationModelProvider(
- new PageApplicationModel("/base-path/Test.cshtml", "/base-path/Test")
+ var applicationModelProvider = new TestPageRouteModelProvider(
+ new PageRouteModel("/base-path/Test.cshtml", "/base-path/Test")
{
Selectors =
{
CreateSelectorModel("base-path/Test/Home")
}
},
- new PageApplicationModel("/base-path/Index.cshtml", "/base-path/Index")
+ new PageRouteModel("/base-path/Index.cshtml", "/base-path/Index")
{
Selectors =
{
@@ -91,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
CreateSelectorModel("base-path/"),
}
},
- new PageApplicationModel("/base-path/Admin/Index.cshtml", "/base-path/Admin/Index")
+ new PageRouteModel("/base-path/Admin/Index.cshtml", "/base-path/Admin/Index")
{
Selectors =
{
@@ -99,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
CreateSelectorModel("base-path/Admin"),
}
},
- new PageApplicationModel("/base-path/Admin/User.cshtml", "/base-path/Admin/User")
+ new PageRouteModel("/base-path/Admin/User.cshtml", "/base-path/Admin/User")
{
Selectors =
{
@@ -143,8 +140,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_AddsMultipleDescriptorsForPageWithMultipleSelectors()
{
// Arrange
- var applicationModelProvider = new TestPageApplicationModelProvider(
- new PageApplicationModel("/Catalog/Details/Index.cshtml", "/Catalog/Details/Index")
+ var applicationModelProvider = new TestPageRouteModelProvider(
+ new PageRouteModel("/Catalog/Details/Index.cshtml", "/Catalog/Details/Index")
{
Selectors =
{
@@ -180,142 +177,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
});
}
- [Fact]
- public void GetDescriptors_ImplicitFilters()
+ private static PageRouteModel CreateModel()
{
- // Arrange
- var options = new MvcOptions();
- var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
- var filterProvider = new PageFilterApplicationModelProvider();
- var provider = new PageActionDescriptorProvider(
- new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
- GetAccessor(options),
- GetRazorPagesOptions());
- var context = new ActionDescriptorProviderContext();
-
- // Act
- provider.OnProvidersExecuting(context);
-
- // Assert
- var result = Assert.Single(context.Results);
- var descriptor = Assert.IsType(result);
- Assert.Collection(
- descriptor.FilterDescriptors,
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
- Assert.IsType(filterDescriptor.Filter);
- },
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
- Assert.IsType(filterDescriptor.Filter);
- });
- }
-
- [Fact]
- public void GetDescriptors_AddsGlobalFilters()
- {
- // Arrange
- var filter1 = Mock.Of();
- var filter2 = Mock.Of();
- var options = new MvcOptions();
- options.Filters.Add(filter1);
- options.Filters.Add(filter2);
- var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
- var filterProvider = new PageFilterApplicationModelProvider();
- var provider = new PageActionDescriptorProvider(
- new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
- GetAccessor(options),
- GetRazorPagesOptions());
- var context = new ActionDescriptorProviderContext();
-
- // Act
- provider.OnProvidersExecuting(context);
-
- // Assert
- var result = Assert.Single(context.Results);
- var descriptor = Assert.IsType(result);
- Assert.Collection(
- descriptor.FilterDescriptors,
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Global, filterDescriptor.Scope);
- Assert.Same(filter1, filterDescriptor.Filter);
- },
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Global, filterDescriptor.Scope);
- Assert.Same(filter2, filterDescriptor.Filter);
- },
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
- Assert.IsType(filterDescriptor.Filter);
- },
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
- Assert.IsType(filterDescriptor.Filter);
- });
- }
-
- [Fact]
- public void GetDescriptors_AddsFiltersAddedByConvention()
- {
- // Arrange
- var globalFilter = Mock.Of();
- var localFilter = Mock.Of();
- var options = new MvcOptions();
- options.Filters.Add(globalFilter);
- var convention = new Mock();
- convention.Setup(c => c.Apply(It.IsAny()))
- .Callback((PageApplicationModel model) =>
- {
- model.Filters.Add(localFilter);
- });
- var razorOptions = GetRazorPagesOptions();
- razorOptions.Value.Conventions.Add(convention.Object);
- var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
- var filterProvider = new PageFilterApplicationModelProvider();
- var provider = new PageActionDescriptorProvider(
- new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
- GetAccessor(options),
- razorOptions);
- var context = new ActionDescriptorProviderContext();
-
- // Act
- provider.OnProvidersExecuting(context);
-
- // Assert
- var result = Assert.Single(context.Results);
- var descriptor = Assert.IsType(result);
- Assert.Collection(descriptor.FilterDescriptors,
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Global, filterDescriptor.Scope);
- Assert.Same(globalFilter, filterDescriptor.Filter);
- },
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
- Assert.IsType(filterDescriptor.Filter);
- },
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
- Assert.IsType(filterDescriptor.Filter);
- },
- filterDescriptor =>
- {
- Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
- Assert.Same(localFilter, filterDescriptor.Filter);
- });
- }
-
- private static PageApplicationModel CreateModel()
- {
- return new PageApplicationModel("/Home.cshtml", "/Home")
+ return new PageRouteModel("/Home.cshtml", "/Home")
{
Selectors =
{
@@ -353,28 +217,27 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
return new FileProviderRazorProjectItem(testFileInfo, basePath, path);
}
- private class TestPageApplicationModelProvider : IPageApplicationModelProvider
+ private class TestPageRouteModelProvider : IPageRouteModelProvider
{
- private readonly PageApplicationModel[] _models;
+ private readonly PageRouteModel[] _models;
- public TestPageApplicationModelProvider(params PageApplicationModel[] models)
+ public TestPageRouteModelProvider(params PageRouteModel[] models)
{
- _models = models ?? Array.Empty();
+ _models = models ?? Array.Empty();
}
public int Order => -1000;
- public void OnProvidersExecuted(PageApplicationModelProviderContext context)
+ public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}
- public void OnProvidersExecuting(PageApplicationModelProviderContext context)
+ public void OnProvidersExecuting(PageRouteModelProviderContext context)
{
foreach (var model in _models)
{
- context.Results.Add(model);
+ context.RouteModels.Add(model);
}
-
}
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs
new file mode 100644
index 0000000000..ef7cc860c1
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs
@@ -0,0 +1,150 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.Authorization;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class AuthorizationPageApplicationModelProviderTest
+ {
+ [Fact]
+ public void OnProvidersExecuting_IgnoresAttributesOnHandlerMethods()
+ {
+ // Arrange
+ var policyProvider = new DefaultAuthorizationPolicyProvider(new TestOptionsManager());
+ var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
+ var typeInfo = typeof(PageWiithAuthorizeHandlers).GetTypeInfo();
+ var context = GetApplicationProviderContext(typeInfo);
+
+ // Act
+ autorizationProvider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.Empty(context.PageApplicationModel.Filters);
+ }
+
+ private class PageWiithAuthorizeHandlers
+ {
+ public ModelWuthAuthorizeHandlers Model => null;
+ }
+
+ public class ModelWuthAuthorizeHandlers
+ {
+ [Authorize]
+ public void OnGet()
+ {
+ }
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_AddsAuthorizeFilter_IfModelHasAuthorizationAttributes()
+ {
+ // Arrange
+ var policyProvider = new DefaultAuthorizationPolicyProvider(new TestOptionsManager());
+ var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
+ var context = GetApplicationProviderContext(typeof(TestPage).GetTypeInfo());
+
+ // Act
+ autorizationProvider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.Collection(
+ context.PageApplicationModel.Filters,
+ f => Assert.IsType(f));
+ }
+
+ private class TestPage
+ {
+ public TestModel Model => null;
+ }
+
+ [Authorize]
+ private class TestModel
+ {
+ public virtual void OnGet()
+ {
+ }
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_CollatesAttributesFromInheritedTypes()
+ {
+ // Arrange
+ var options = new TestOptionsManager();
+ options.Value.AddPolicy("Base", policy => policy.RequireClaim("Basic").RequireClaim("Basic2"));
+ options.Value.AddPolicy("Derived", policy => policy.RequireClaim("Derived"));
+
+ var policyProvider = new DefaultAuthorizationPolicyProvider(options);
+ var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
+
+ var context = GetApplicationProviderContext(typeof(TestPageWithDerivedModel).GetTypeInfo());
+
+ // Act
+ autorizationProvider.OnProvidersExecuting(context);
+
+ // Assert
+ var authorizeFilter = Assert.IsType(Assert.Single(context.PageApplicationModel.Filters));
+ // Basic + Basic2 + Derived authorize
+ Assert.Equal(3, authorizeFilter.Policy.Requirements.Count);
+ }
+
+ private class TestPageWithDerivedModel
+ {
+ public DeriviedModel Model => null;
+ }
+
+ [Authorize(Policy = "Base")]
+ public class BaseModel
+ {
+ }
+
+ [Authorize(Policy = "Derived")]
+ private class DeriviedModel : BaseModel
+ {
+ public virtual void OnGet()
+ {
+ }
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_AddsAllowAnonymousFilter()
+ {
+ // Arrange
+ var policyProvider = new DefaultAuthorizationPolicyProvider(new TestOptionsManager());
+ var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
+ var context = GetApplicationProviderContext(typeof(PageWithAnonymousModel).GetTypeInfo());
+
+ // Act
+ autorizationProvider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.Collection(
+ context.PageApplicationModel.Filters,
+ f => Assert.IsType(f));
+ }
+
+ private class PageWithAnonymousModel
+ {
+ public AnonymousModel Model => null;
+ }
+
+ [AllowAnonymous]
+ public class AnonymousModel
+ {
+ public void OnGet() { }
+ }
+
+ private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo)
+ {
+ var defaultProvider = new DefaultPageApplicationModelProvider(new TestOptionsManager());
+ var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
+ defaultProvider.OnProvidersExecuting(context);
+ return context;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs
new file mode 100644
index 0000000000..20d81e5c69
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs
@@ -0,0 +1,36 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class AutoValidateAntiforgeryPageApplicationModelProviderTest
+ {
+ [Fact]
+ public void OnProvidersExecuting_AddsFiltersToModel()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor();
+ var applicationModel = new PageApplicationModel(
+ actionDescriptor,
+ typeof(object).GetTypeInfo(),
+ new object[0]);
+ var applicationModelProvider = new AutoValidateAntiforgeryPageApplicationModelProvider();
+ var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeof(object).GetTypeInfo())
+ {
+ PageApplicationModel = applicationModel,
+ };
+
+ // Act
+ applicationModelProvider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.Collection(
+ applicationModel.Filters,
+ filter => Assert.IsType(filter));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs
new file mode 100644
index 0000000000..ca13fb3f37
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs
@@ -0,0 +1,311 @@
+// 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.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.ActionConstraints;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.Routing;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class CompiledPageActionDescriptorBuilderTest
+ {
+ [Fact]
+ public void CreateDescriptor_CopiesPropertiesFromPageActionDescriptor()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor
+ {
+ ActionConstraints = new List(),
+ AttributeRouteInfo = new AttributeRouteInfo(),
+ FilterDescriptors = new List(),
+ RelativePath = "/Foo",
+ RouteValues = new Dictionary(),
+ ViewEnginePath = "/Pages/Foo",
+ };
+ var handlerTypeInfo = typeof(object).GetTypeInfo();
+ var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0]);
+
+ // Act
+ var actual = CompiledPageActionDescriptorBuilder.Build(pageApplicationModel);
+
+ // Assert
+ Assert.Same(actionDescriptor.ActionConstraints, actual.ActionConstraints);
+ Assert.Same(actionDescriptor.AttributeRouteInfo, actual.AttributeRouteInfo);
+ Assert.Same(actionDescriptor.RelativePath, actual.RelativePath);
+ Assert.Same(actionDescriptor.RouteValues, actual.RouteValues);
+ Assert.Same(actionDescriptor.ViewEnginePath, actual.ViewEnginePath);
+ }
+
+ [Fact]
+ public void CreateDescriptor_CopiesPropertiesFromPageApplicationModel()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor
+ {
+ ActionConstraints = new List(),
+ AttributeRouteInfo = new AttributeRouteInfo(),
+ FilterDescriptors = new List(),
+ RelativePath = "/Foo",
+ RouteValues = new Dictionary(),
+ ViewEnginePath = "/Pages/Foo",
+ };
+ var handlerTypeInfo = typeof(TestModel).GetTypeInfo();
+ var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
+ {
+ PageType = typeof(TestPage).GetTypeInfo(),
+ ModelType = typeof(TestModel).GetTypeInfo(),
+ Filters =
+ {
+ Mock.Of(),
+ Mock.Of(),
+ },
+ HandlerMethods =
+ {
+ new PageHandlerModel(handlerTypeInfo.GetMethod(nameof(TestModel.OnGet)), new object[0]),
+ },
+ HandlerProperties =
+ {
+ new PagePropertyModel(handlerTypeInfo.GetProperty(nameof(TestModel.Property)), new object[0])
+ {
+ BindingInfo = new BindingInfo(),
+ },
+ }
+ };
+
+ // Act
+ var actual = CompiledPageActionDescriptorBuilder.Build(pageApplicationModel);
+
+ // Assert
+ Assert.Same(pageApplicationModel.PageType, actual.PageTypeInfo);
+ Assert.Same(pageApplicationModel.ModelType, actual.ModelTypeInfo);
+ Assert.Same(pageApplicationModel.HandlerType, actual.HandlerTypeInfo);
+ Assert.Same(pageApplicationModel.Properties, actual.Properties);
+ Assert.Equal(pageApplicationModel.Filters, actual.FilterDescriptors.Select(f => f.Filter));
+ Assert.Equal(pageApplicationModel.HandlerMethods.Select(p => p.MethodInfo), actual.HandlerMethods.Select(p => p.MethodInfo));
+ Assert.Equal(pageApplicationModel.HandlerProperties.Select(p => p.PropertyName), actual.BoundProperties.Select(p => p.Name));
+ }
+
+ private class TestPage
+ {
+ public TestModel Model { get; } = new TestModel();
+
+ [BindProperty]
+ public string Property { get; set; }
+
+ public void OnGet()
+ {
+
+ }
+ }
+
+ private class TestModel
+ {
+ [BindProperty]
+ public string Property { get; set; }
+
+ public void OnGet()
+ {
+
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerMethods_CopiesPropertiesFromHandlerModel()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor();
+ var handlerTypeInfo = typeof(ModelWithHandler).GetTypeInfo();
+ var handlerModel = new PageHandlerModel(handlerTypeInfo.GetMethod(nameof(ModelWithHandler.OnGetCustomerAsync)), new object[0])
+ {
+ HttpMethod = "GET",
+ HandlerName = "Customer",
+ };
+ var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
+ {
+ HandlerMethods =
+ {
+ handlerModel,
+ }
+ };
+
+ // Act
+ var handlerDescriptors = CompiledPageActionDescriptorBuilder.CreateHandlerMethods(pageApplicationModel);
+
+ // Assert
+ Assert.Collection(
+ handlerDescriptors,
+ d =>
+ {
+ Assert.Equal(handlerModel.MethodInfo, d.MethodInfo);
+ Assert.Equal(handlerModel.HttpMethod, d.HttpMethod);
+ Assert.Equal(handlerModel.HandlerName, d.Name);
+ });
+ }
+
+ private class ModelWithHandler
+ {
+ public void OnGetCustomerAsync()
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerMethods_CopiesParameterDecriptorsFromParameterModel()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor();
+ var handlerTypeInfo = typeof(HandlerWithParameters).GetTypeInfo();
+ var handlerMethod = handlerTypeInfo.GetMethod(nameof(HandlerWithParameters.OnPost));
+ var parameters = handlerMethod.GetParameters();
+ var parameterModel1 = new PageParameterModel(parameters[0], new object[0])
+ {
+ ParameterName = "test-name"
+ };
+ var parameterModel2 = new PageParameterModel(parameters[1], new object[0])
+ {
+ BindingInfo = new BindingInfo(),
+ };
+ var handlerModel = new PageHandlerModel(handlerMethod, new object[0])
+ {
+ Parameters =
+ {
+ parameterModel1,
+ parameterModel2,
+ }
+ };
+ var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
+ {
+ HandlerMethods =
+ {
+ handlerModel,
+ }
+ };
+
+ // Act
+ var handlerDescriptors = CompiledPageActionDescriptorBuilder.CreateHandlerMethods(pageApplicationModel);
+
+ // Assert
+ Assert.Collection(
+ Assert.Single(handlerDescriptors).Parameters,
+ p =>
+ {
+ Assert.Equal(parameters[0], p.ParameterInfo);
+ Assert.Equal(typeof(string), p.ParameterType);
+ Assert.Equal(parameterModel1.ParameterName, p.Name);
+ },
+ p =>
+ {
+ Assert.Equal(parameters[1], p.ParameterInfo);
+ Assert.Equal(typeof(int), p.ParameterType);
+ Assert.Same(parameterModel2.BindingInfo, p.BindingInfo);
+ });
+ }
+
+ private class HandlerWithParameters
+ {
+ public void OnPost(string param1, [FromRoute(Name = "id")] int param2)
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateBoundProperties_CopiesPropertyDescriptorsFromPagePropertyModel()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor();
+ var handlerTypeInfo = typeof(HandlerWithProperty).GetTypeInfo();
+ var propertyModel = new PagePropertyModel(
+ handlerTypeInfo.GetProperty(nameof(HandlerWithProperty.Property)),
+ new object[0])
+ {
+ PropertyName = nameof(HandlerWithProperty.Property),
+ BindingInfo = new BindingInfo(),
+ };
+ var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
+ {
+ HandlerProperties =
+ {
+ propertyModel,
+ }
+ };
+
+ // Act
+ var propertyDescriptors = CompiledPageActionDescriptorBuilder.CreateBoundProperties(pageApplicationModel);
+
+ // Assert
+ Assert.Collection(
+ propertyDescriptors,
+ p =>
+ {
+ Assert.Same(propertyModel.PropertyName, p.Name);
+ Assert.Same(typeof(int), p.ParameterType);
+ Assert.Same(propertyModel.PropertyInfo, p.Property);
+ Assert.Same(propertyModel.BindingInfo, p.BindingInfo);
+ });
+ }
+
+ private class HandlerWithProperty
+ {
+ [BindProperty]
+ public int Property { get; set; }
+ }
+
+ [Fact]
+ public void CreateBoundProperties_IgnoresPropertiesWithoutBindingInfo()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor();
+ var handlerTypeInfo = typeof(HandlerWithIgnoredProperties).GetTypeInfo();
+ var propertyModel1 = new PagePropertyModel(
+ handlerTypeInfo.GetProperty(nameof(HandlerWithIgnoredProperties.Property)),
+ new object[0])
+ {
+ PropertyName = nameof(HandlerWithIgnoredProperties.Property),
+ BindingInfo = new BindingInfo(),
+ };
+ var propertyModel2 = new PagePropertyModel(
+ handlerTypeInfo.GetProperty(nameof(HandlerWithIgnoredProperties.IgnoreMe)),
+ new object[0])
+ {
+ PropertyName = nameof(HandlerWithIgnoredProperties.IgnoreMe),
+ };
+ var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
+ {
+ HandlerProperties =
+ {
+ propertyModel1,
+ propertyModel2,
+ }
+ };
+
+ // Act
+ var propertyDescriptors = CompiledPageActionDescriptorBuilder.CreateBoundProperties(pageApplicationModel);
+
+ // Assert
+ Assert.Collection(
+ propertyDescriptors,
+ p =>
+ {
+ Assert.Same(propertyModel1.PropertyName, p.Name);
+ Assert.Same(typeof(int), p.ParameterType);
+ Assert.Same(propertyModel1.PropertyInfo, p.Property);
+ Assert.Same(propertyModel1.BindingInfo, p.BindingInfo);
+ });
+ }
+
+ private class HandlerWithIgnoredProperties
+ {
+ [BindProperty]
+ public int Property { get; set; }
+
+ public string IgnoreMe { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs
similarity index 83%
rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageApplicationModelProviderTest.cs
rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs
index c3807a15d2..c9b298a7df 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageApplicationModelProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs
@@ -11,7 +11,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
- public class CompiledPageApplicationModelProviderTest
+ public class CompiledPageRouteModelProviderTest
{
[Fact]
public void OnProvidersExecuting_AddsModelsForCompiledViews()
@@ -22,14 +22,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/About.cshtml"),
GetDescriptor("/Pages/Home.cshtml", "some-prefix"),
};
- var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
- var context = new PageApplicationModelProviderContext();
+ var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
+ var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
- Assert.Collection(context.Results,
+ Assert.Collection(context.RouteModels,
result =>
{
Assert.Equal("/Pages/About.cshtml", result.RelativePath);
@@ -55,14 +55,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"),
};
- var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions { RootDirectory = "/" });
- var context = new PageApplicationModelProviderContext();
+ var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions { RootDirectory = "/" });
+ var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
- Assert.Collection(context.Results,
+ Assert.Collection(context.RouteModels,
result =>
{
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
@@ -90,14 +90,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"),
};
- var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
- var context = new PageApplicationModelProviderContext();
+ var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
+ var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
- Assert.Collection(context.Results,
+ Assert.Collection(context.RouteModels,
result =>
{
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
@@ -125,8 +125,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Home.cshtml", "/some-prefix"),
};
- var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
- var context = new PageApplicationModelProviderContext();
+ var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
+ var context = new PageRouteModelProviderContext();
// Act & Assert
var ex = Assert.Throws(() => provider.OnProvidersExecuting(context));
@@ -143,11 +143,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
};
}
- public class TestCompiledPageApplicationModelProvider : CompiledPageApplicationModelProvider
+ public class TestCompiledPageRouteModelProvider : CompiledPageRouteModelProvider
{
private readonly IEnumerable _descriptors;
- public TestCompiledPageApplicationModelProvider(IEnumerable descriptors, RazorPagesOptions options)
+ public TestCompiledPageRouteModelProvider(IEnumerable descriptors, RazorPagesOptions options)
: base(new ApplicationPartManager(), new TestOptionsManager(options))
{
_descriptors = descriptors;
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs
new file mode 100644
index 0000000000..d0f3835231
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs
@@ -0,0 +1,788 @@
+// 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.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public partial class DefaultPageApplicationModelProviderTest
+ {
+ [Fact]
+ public void OnProvidersExecuting_SetsPageAsHandlerType_IfModelPropertyDoesNotExist()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(TestPage).GetTypeInfo();
+ var descriptor = new PageActionDescriptor();
+ var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.PageApplicationModel);
+ Assert.Same(context.PageApplicationModel.PageType, context.PageApplicationModel.HandlerType);
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_SetsPageAsHandlerType_IfModelTypeDoesNotHaveAnyHandlers()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithModelWithoutHandlers).GetTypeInfo();
+ var descriptor = new PageActionDescriptor();
+ var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.PageApplicationModel);
+ Assert.Same(context.PageApplicationModel.PageType, context.PageApplicationModel.HandlerType);
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_SetsModelAsHandlerType()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithModel).GetTypeInfo();
+ var descriptor = new PageActionDescriptor();
+ var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.PageApplicationModel);
+ Assert.Same(typeof(TestPageModel).GetTypeInfo(), context.PageApplicationModel.HandlerType);
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_DiscoversPropertiesFromPage()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(TestPage).GetTypeInfo();
+ var descriptor = new PageActionDescriptor();
+ var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.PageApplicationModel);
+ Assert.Collection(
+ context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName),
+ property =>
+ {
+ Assert.Equal(typeInfo.GetProperty(nameof(TestPage.Property1)), property.PropertyInfo);
+ Assert.Null(property.BindingInfo);
+ Assert.Equal(nameof(TestPage.Property1), property.PropertyName);
+ },
+ property =>
+ {
+ Assert.Equal(typeInfo.GetProperty(nameof(TestPage.Property2)), property.PropertyInfo);
+ Assert.Equal(nameof(TestPage.Property2), property.PropertyName);
+ Assert.NotNull(property.BindingInfo);
+ Assert.Equal(BindingSource.Path, property.BindingInfo.BindingSource);
+ });
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_DiscoversHandlersFromPage()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithModelWithoutHandlers).GetTypeInfo();
+ var descriptor = new PageActionDescriptor();
+ var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.PageApplicationModel);
+ Assert.Collection(
+ context.PageApplicationModel.HandlerMethods.OrderBy(p => p.Name),
+ handler =>
+ {
+ var name = nameof(PageWithModelWithoutHandlers.OnGet);
+ Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo);
+ Assert.Equal(name, handler.Name);
+ Assert.Equal("Get", handler.HttpMethod);
+ Assert.Null(handler.HandlerName);
+ },
+ handler =>
+ {
+ var name = nameof(PageWithModelWithoutHandlers.OnPostAsync);
+ Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo);
+ Assert.Equal(name, handler.Name);
+ Assert.Equal("Post", handler.HttpMethod);
+ Assert.Null(handler.HandlerName);
+ },
+ handler =>
+ {
+ var name = nameof(PageWithModelWithoutHandlers.OnPostDeleteCustomerAsync);
+ Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo);
+ Assert.Equal(name, handler.Name);
+ Assert.Equal("Post", handler.HttpMethod);
+ Assert.Equal("DeleteCustomer", handler.HandlerName);
+ });
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_DiscoversPropertiesFromModel()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithModel).GetTypeInfo();
+ var modelType = typeof(TestPageModel);
+ var descriptor = new PageActionDescriptor();
+ var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.PageApplicationModel);
+ Assert.Collection(
+ context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName),
+ property =>
+ {
+ var name = nameof(TestPageModel.Property1);
+ Assert.Equal(modelType.GetProperty(name), property.PropertyInfo);
+ Assert.Null(property.BindingInfo);
+ Assert.Equal(name, property.PropertyName);
+ },
+ property =>
+ {
+ var name = nameof(TestPageModel.Property2);
+ Assert.Equal(modelType.GetProperty(name), property.PropertyInfo);
+ Assert.Equal(name, property.PropertyName);
+ Assert.NotNull(property.BindingInfo);
+ Assert.Equal(BindingSource.Query, property.BindingInfo.BindingSource);
+ });
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_DiscoversHandlersFromModel()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithModel).GetTypeInfo();
+ var modelType = typeof(TestPageModel);
+ var descriptor = new PageActionDescriptor();
+ var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.PageApplicationModel);
+ Assert.Collection(
+ context.PageApplicationModel.HandlerMethods.OrderBy(p => p.Name),
+ handler =>
+ {
+ var name = nameof(TestPageModel.OnGetUser);
+ Assert.Equal(modelType.GetMethod(name), handler.MethodInfo);
+ Assert.Equal(name, handler.Name);
+ Assert.Equal("Get", handler.HttpMethod);
+ Assert.Equal("User", handler.HandlerName);
+ });
+ }
+
+ // We want to test the the 'empty' page has no bound properties, and no handler methods.
+ [Fact]
+ public void OnProvidersExecuting_EmptyPage()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(EmptyPage).GetTypeInfo();
+ var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ var pageModel = context.PageApplicationModel;
+ Assert.Empty(pageModel.HandlerProperties.Where(p => p.BindingInfo != null));
+ Assert.Empty(pageModel.HandlerMethods);
+ Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.HandlerType);
+ Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.ModelType);
+ Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.PageType);
+ }
+
+ // We want to test the the 'empty' page and pagemodel has no bound properties, and no handler methods.
+ [Fact]
+ public void OnProvidersExecuting_EmptyPageModel()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(EmptyPageWithPageModel).GetTypeInfo();
+ var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ var pageModel = context.PageApplicationModel;
+ Assert.Empty(pageModel.HandlerProperties.Where(p => p.BindingInfo != null));
+ Assert.Empty(pageModel.HandlerMethods);
+ Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), pageModel.HandlerType);
+ Assert.Same(typeof(EmptyPageModel).GetTypeInfo(), pageModel.ModelType);
+ Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), pageModel.PageType);
+ }
+
+ private class EmptyPage : Page
+ {
+ // Copied from generated code
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; }
+ public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => null;
+ public EmptyPage Model => ViewData.Model;
+
+ public override Task ExecuteAsync()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class EmptyPageWithPageModel : Page
+ {
+ // Copied from generated code
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
+ [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
+ public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; }
+ public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => null;
+ public EmptyPageModel Model => ViewData.Model;
+
+ public override Task ExecuteAsync()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class EmptyPageModel : PageModel
+ {
+ }
+
+ [Fact] // If the model has handler methods, we prefer those.
+ public void CreateDescriptor_FindsHandlerMethod_OnModel()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo();
+ var modelType = typeof(ModelWithHandler);
+ var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ var pageModel = context.PageApplicationModel;
+ Assert.Collection(
+ pageModel.HandlerProperties,
+ p => Assert.Equal(modelType.GetProperty(nameof(ModelWithHandler.BindMe)), p.PropertyInfo));
+
+ Assert.Collection(
+ pageModel.HandlerMethods,
+ p => Assert.Equal(modelType.GetMethod(nameof(ModelWithHandler.OnGet)), p.MethodInfo));
+
+ Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), pageModel.HandlerType);
+ Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), pageModel.ModelType);
+ Assert.Same(typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo(), pageModel.PageType);
+ }
+
+ private class ModelWithHandler
+ {
+ [ModelBinder]
+ public int BindMe { get; set; }
+
+ public void OnGet() { }
+ }
+
+ private class PageWithHandlerThatGetsIgnored
+ {
+ public ModelWithHandler Model => null;
+
+ [ModelBinder]
+ public int IgnoreMe { get; set; }
+
+ public void OnPost() { }
+ }
+
+
+ [Fact] // If the model has no handler methods, we look at the page instead.
+ public void OnProvidersExecuting_FindsHandlerMethodOnPage_WhenModelHasNoHandlers()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithHandler).GetTypeInfo();
+ var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
+
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ var pageModel = context.PageApplicationModel;
+ Assert.Collection(
+ pageModel.HandlerProperties.OrderBy(p => p.PropertyName),
+ p => Assert.Equal(typeInfo.GetProperty(nameof(PageWithHandler.BindMe)), p.PropertyInfo),
+ p => Assert.Equal(typeInfo.GetProperty(nameof(PageWithHandler.Model)), p.PropertyInfo));
+
+ Assert.Collection(
+ pageModel.HandlerMethods,
+ p => Assert.Equal(typeInfo.GetMethod(nameof(PageWithHandler.OnGet)), p.MethodInfo));
+
+ Assert.Same(typeof(PageWithHandler).GetTypeInfo(), pageModel.HandlerType);
+ Assert.Same(typeof(PocoModel).GetTypeInfo(), pageModel.ModelType);
+ Assert.Same(typeof(PageWithHandler).GetTypeInfo(), pageModel.PageType);
+ }
+
+ private class PageWithHandler
+ {
+ public PocoModel Model => null;
+
+ [ModelBinder]
+ public int BindMe { get; set; }
+
+ public void OnGet() { }
+ }
+
+ [Fact]
+ public void CreateHandlerModels_DiscoversHandlersFromBaseType()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(InheritsMethods).GetTypeInfo();
+ var baseType = typeof(TestSetPageModel);
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ Assert.Collection(
+ handlerModels.OrderBy(h => h.MethodInfo.DeclaringType.Name).ThenBy(h => h.MethodInfo.Name),
+ handler =>
+ {
+ Assert.Equal(nameof(InheritsMethods.OnGet), handler.MethodInfo.Name);
+ Assert.Equal(typeInfo, handler.MethodInfo.DeclaringType.GetTypeInfo());
+ },
+ handler =>
+ {
+ Assert.Equal(nameof(TestSetPageModel.OnGet), handler.MethodInfo.Name);
+ Assert.Equal(baseType, handler.MethodInfo.DeclaringType);
+ },
+ handler =>
+ {
+ Assert.Equal(nameof(TestSetPageModel.OnPost), handler.MethodInfo.Name);
+ Assert.Equal(baseType, handler.MethodInfo.DeclaringType);
+ });
+ }
+
+ private class TestSetPageModel
+ {
+ public void OnGet()
+ {
+ }
+
+ public void OnPost()
+ {
+ }
+ }
+
+ private class InheritsMethods : TestSetPageModel
+ {
+ public new void OnGet()
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerModels_IgnoresNonPublicMethods()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(ProtectedModel).GetTypeInfo();
+ var baseType = typeof(TestSetPageModel);
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ Assert.Empty(handlerModels);
+ }
+
+ private class ProtectedModel
+ {
+ protected void OnGet()
+ {
+ }
+
+ private void OnPost()
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerModels_IgnoreGenericTypeParameters()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(GenericClassModel).GetTypeInfo();
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ Assert.Empty(handlerModels);
+ }
+
+ private class GenericClassModel
+ {
+ public void OnGet()
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerModels_IgnoresStaticMethods()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageModelWithStaticHandler).GetTypeInfo();
+ var expected = typeInfo.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance);
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ Assert.Collection(
+ handlerModels,
+ handler => Assert.Same(expected, handler.MethodInfo));
+ }
+
+ private class PageModelWithStaticHandler
+ {
+ public static void OnGet(string name)
+ {
+ }
+
+ public void OnGet()
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerModels_IgnoresAbstractMethods()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageModelWithAbstractMethod).GetTypeInfo();
+ var expected = typeInfo.GetMethod(nameof(PageModelWithAbstractMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ Assert.Collection(
+ handlerModels,
+ handler => Assert.Same(expected, handler.MethodInfo));
+ }
+
+ private abstract class PageModelWithAbstractMethod
+ {
+ public abstract void OnPost(string name);
+
+ public void OnGet()
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerModels_IgnoresMethodWithNonHandlerAttribute()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithNonHandlerMethod).GetTypeInfo();
+ var expected = typeInfo.GetMethod(nameof(PageWithNonHandlerMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ Assert.Collection(
+ handlerModels,
+ handler => Assert.Same(expected, handler.MethodInfo));
+ }
+
+ private class PageWithNonHandlerMethod
+ {
+ [NonHandler]
+ public void OnPost(string name) { }
+
+ public void OnGet()
+ {
+ }
+ }
+
+ // There are more tests for the parsing elsewhere, this is just testing that it's wired
+ // up to the model.
+ [Fact]
+ public void CreateHandlerModel_ParsesMethod()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageModelWithHandlerNames).GetTypeInfo();
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ Assert.Collection(
+ handlerModels.OrderBy(h => h.MethodInfo.Name),
+ handler =>
+ {
+ Assert.Same(typeInfo.GetMethod(nameof(PageModelWithHandlerNames.OnPutDeleteAsync)), handler.MethodInfo);
+ Assert.Equal("Put", handler.HttpMethod);
+ Assert.Equal("Delete", handler.HandlerName);
+ });
+ }
+
+ private class PageModelWithHandlerNames
+ {
+ public void OnPutDeleteAsync()
+ {
+ }
+
+ public void Foo() // This isn't a valid handler name.
+ {
+ }
+ }
+
+ [Fact]
+ public void CreateHandlerMethods_AddsParameterDescriptors()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo();
+ var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost));
+
+ // Act
+ var handlerModels = provider.CreateHandlerModels(typeInfo);
+
+ // Assert
+ var handler = Assert.Single(handlerModels);
+
+ Assert.Collection(
+ handler.Parameters,
+ p =>
+ {
+ Assert.NotNull(p.ParameterInfo);
+ Assert.Equal(typeof(string), p.ParameterInfo.ParameterType);
+ Assert.Equal("name", p.ParameterName);
+ },
+ p =>
+ {
+ Assert.NotNull(p.ParameterInfo);
+ Assert.Equal(typeof(int), p.ParameterInfo.ParameterType);
+ Assert.Equal("id", p.ParameterName);
+ Assert.Equal("personId", p.BindingInfo.BinderModelName);
+ });
+ }
+
+ private class PageWithHandlerParameters
+ {
+ public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { }
+ }
+
+ // We're using PropertyHelper from Common to find the properties here, which implements
+ // out standard set of semantics for properties that the framework interacts with.
+ //
+ // One of the desirable consequences of that is we only find 'visible' properties. We're not
+ // retesting all of the details of PropertyHelper here, just the visibility part as a quick check
+ // that we're using PropertyHelper as expected.
+ [Fact]
+ public void PopulateHandlerProperties_UsesPropertyHelpers_ToFindProperties()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(HidesAProperty).GetTypeInfo();
+ var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]);
+
+ // Act
+ provider.PopulateHandlerProperties(pageModel);
+
+ // Assert
+ var properties = pageModel.HandlerProperties;
+ Assert.Collection(
+ properties,
+ p =>
+ {
+ Assert.Equal(typeof(HidesAProperty).GetTypeInfo(), p.PropertyInfo.DeclaringType.GetTypeInfo());
+ });
+ }
+
+ private class HasAHiddenProperty
+ {
+ [BindProperty]
+ public int Property { get; set; }
+ }
+
+ private class HidesAProperty : HasAHiddenProperty
+ {
+ [BindProperty]
+ public new int Property { get; set; }
+ }
+
+ [Fact]
+ public void PopulateHandlerProperties_SupportsGet_OnProperty()
+ {
+ // Arrange
+ var provider = new TestPageApplicationModelProvider();
+ var typeInfo = typeof(ModelSupportsGetOnProperty).GetTypeInfo();
+ var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]);
+
+ // Act
+ provider.PopulateHandlerProperties(pageModel);
+
+ // Assert
+ var properties = pageModel.HandlerProperties;
+ Assert.Collection(
+ properties.OrderBy(p => p.PropertyName),
+ p =>
+ {
+ Assert.Equal(typeInfo.GetProperty(nameof(ModelSupportsGetOnProperty.Property)), p.PropertyInfo);
+ Assert.NotNull(p.BindingInfo.RequestPredicate);
+ Assert.True(p.BindingInfo.RequestPredicate(new ActionContext
+ {
+ HttpContext = new DefaultHttpContext
+ {
+ Request =
+ {
+ Method ="GET",
+ }
+ }
+ }));
+ });
+ }
+
+ private class ModelSupportsGetOnProperty
+ {
+ [BindProperty(SupportsGet = true)]
+ public int Property { get; set; }
+ }
+
+ [Theory]
+ [InlineData("Foo")]
+ [InlineData("On")]
+ [InlineData("OnAsync")]
+ [InlineData("Async")]
+ public void TryParseHandler_ParsesHandlerNames_InvalidData(string methodName)
+ {
+ // Act
+ var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
+
+ // Assert
+ Assert.False(result);
+ Assert.Null(httpMethod);
+ Assert.Null(handler);
+ }
+
+ [Theory]
+ [InlineData("OnG", "G", null)]
+ [InlineData("OnGAsync", "G", null)]
+ [InlineData("OnPOST", "P", "OST")]
+ [InlineData("OnPOSTAsync", "P", "OST")]
+ [InlineData("OnDeleteFoo", "Delete", "Foo")]
+ [InlineData("OnDeleteFooAsync", "Delete", "Foo")]
+ [InlineData("OnMadeupLongHandlerName", "Madeup", "LongHandlerName")]
+ [InlineData("OnMadeupLongHandlerNameAsync", "Madeup", "LongHandlerName")]
+ public void TryParseHandler_ParsesHandlerNames_ValidData(string methodName, string expectedHttpMethod, string expectedHandler)
+ {
+ // Arrange
+
+ // Act
+ var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(expectedHttpMethod, httpMethod);
+ Assert.Equal(expectedHandler, handler);
+ }
+
+ private class TestPageApplicationModelProvider : DefaultPageApplicationModelProvider
+ {
+ public TestPageApplicationModelProvider(IOptions mvcOptions = null)
+ : base(mvcOptions : new TestOptionsManager())
+ {
+ }
+ }
+
+ private class TestPage
+ {
+ public string Property1 { get; set; }
+
+ [FromRoute]
+ public object Property2 { get; set; }
+ }
+
+ private class PageWithModelWithoutHandlers : Page
+ {
+ public ModelWithoutHandler Model { get; }
+
+ public override Task ExecuteAsync() => throw new NotImplementedException();
+
+ public void OnGet() { }
+
+ public void OnPostAsync() { }
+
+ public void OnPostDeleteCustomerAsync() { }
+
+ public class ModelWithoutHandler
+ {
+ }
+ }
+
+ private class PageWithModel : Page
+ {
+ public TestPageModel Model { get; }
+
+ public override Task ExecuteAsync() => throw new NotImplementedException();
+ }
+
+ private class TestPageModel
+ {
+ public string Property1 { get; set; }
+
+ [FromQuery]
+ public string Property2 { get; set; }
+
+ public void OnGetUser() { }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs
index 893e241a59..29a7268f97 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs
@@ -1,16 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.ActionConstraints;
-using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
-using Microsoft.AspNetCore.Mvc.Routing;
+using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
@@ -18,600 +13,126 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public class DefaultPageLoaderTest
{
[Fact]
- public void CreateDescriptor_CopiesPropertiesFromBaseClass()
+ public void Load_InvokesApplicationModelProviders()
{
// Arrange
- var expected = new PageActionDescriptor() // We only copy the properties that are meaningful for pages.
+ var descriptor = new PageActionDescriptor();
+
+ var compilerProvider = GetCompilerProvider();
+
+ var options = new TestOptionsManager();
+
+ var provider1 = new Mock();
+ var provider2 = new Mock();
+
+ var sequence = 0;
+ var pageApplicationModel1 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
+ var pageApplicationModel2 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
+
+ provider1.Setup(p => p.OnProvidersExecuting(It.IsAny()))
+ .Callback((PageApplicationModelProviderContext c) =>
+ {
+ Assert.Equal(0, sequence++);
+ Assert.Null(c.PageApplicationModel);
+ c.PageApplicationModel = pageApplicationModel1;
+ })
+ .Verifiable();
+
+ provider2.Setup(p => p.OnProvidersExecuting(It.IsAny()))
+ .Callback((PageApplicationModelProviderContext c) =>
+ {
+ Assert.Equal(1, sequence++);
+ Assert.Same(pageApplicationModel1, c.PageApplicationModel);
+ c.PageApplicationModel = pageApplicationModel2;
+ })
+ .Verifiable();
+
+ provider1.Setup(p => p.OnProvidersExecuted(It.IsAny()))
+ .Callback((PageApplicationModelProviderContext c) =>
+ {
+ Assert.Equal(3, sequence++);
+ Assert.Same(pageApplicationModel2, c.PageApplicationModel);
+ })
+ .Verifiable();
+
+ provider2.Setup(p => p.OnProvidersExecuted(It.IsAny()))
+ .Callback((PageApplicationModelProviderContext c) =>
+ {
+ Assert.Equal(2, sequence++);
+ Assert.Same(pageApplicationModel2, c.PageApplicationModel);
+ })
+ .Verifiable();
+
+ var providers = new[]
{
- ActionConstraints = new List(),
- AttributeRouteInfo = new AttributeRouteInfo(),
- FilterDescriptors = new List(),
- RelativePath = "/Foo",
- RouteValues = new Dictionary(),
- ViewEnginePath = "/Pages/Foo",
+ provider1.Object, provider2.Object
};
- // Act
- var actual = DefaultPageLoader.CreateDescriptor(expected,
- new RazorPageAttribute(expected.RelativePath, typeof(EmptyPage), ""));
-
- // Assert
- Assert.Same(expected.ActionConstraints, actual.ActionConstraints);
- Assert.Same(expected.AttributeRouteInfo, actual.AttributeRouteInfo);
- Assert.Same(expected.FilterDescriptors, actual.FilterDescriptors);
- Assert.Same(expected.Properties, actual.Properties);
- Assert.Same(expected.RelativePath, actual.RelativePath);
- Assert.Same(expected.RouteValues, actual.RouteValues);
- Assert.Same(expected.ViewEnginePath, actual.ViewEnginePath);
- }
-
- // We want to test the the 'empty' page has no bound properties, and no handler methods.
- [Fact]
- public void CreateDescriptor_EmptyPage()
- {
- // Arrange
- var type = typeof(EmptyPage);
+ var loader = new DefaultPageLoader(
+ providers,
+ compilerProvider,
+ options);
// Act
- var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
- new RazorPageAttribute("/Pages/Index", type, ""));
+ var result = loader.Load(new PageActionDescriptor());
// Assert
- Assert.Empty(result.BoundProperties);
- Assert.Empty(result.HandlerMethods);
- Assert.Same(typeof(EmptyPage).GetTypeInfo(), result.HandlerTypeInfo);
- Assert.Same(typeof(EmptyPage).GetTypeInfo(), result.ModelTypeInfo);
- Assert.Same(typeof(EmptyPage).GetTypeInfo(), result.PageTypeInfo);
- }
-
- // We want to test the the 'empty' page and pagemodel has no bound properties, and no handler methods.
- [Fact]
- public void CreateDescriptor_EmptyPageModel()
- {
- // Arrange
- var type = typeof(EmptyPageWithPageModel);
-
- // Act
- var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
- new RazorPageAttribute("/Pages/Index", type, ""));
-
- // Assert
- Assert.Empty(result.BoundProperties);
- Assert.Empty(result.HandlerMethods);
- Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), result.HandlerTypeInfo);
- Assert.Same(typeof(EmptyPageModel).GetTypeInfo(), result.ModelTypeInfo);
- Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), result.PageTypeInfo);
- }
-
- private class EmptyPage : Page
- {
- // Copied from generated code
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; }
- public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => null;
- public EmptyPage Model => ViewData.Model;
-
- public override Task ExecuteAsync()
- {
- throw new NotImplementedException();
- }
- }
-
- private class EmptyPageWithPageModel : Page
- {
- // Copied from generated code
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
- [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
- public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; }
- public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => null;
- public EmptyPageModel Model => ViewData.Model;
-
- public override Task ExecuteAsync()
- {
- throw new NotImplementedException();
- }
- }
-
- private class EmptyPageModel : PageModel
- {
- }
-
- [Fact] // If the model has handler methods, we prefer those.
- public void CreateDescriptor_FindsHandlerMethod_OnModel()
- {
- // Arrange
- var type = typeof(PageWithHandlerThatGetsIgnored);
-
- // Act
- var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
- new RazorPageAttribute("/Pages/Index", type, ""));
-
- // Assert
- Assert.Collection(result.BoundProperties, p => Assert.Equal("BindMe", p.Name));
- Assert.Collection(result.HandlerMethods, h => Assert.Equal("OnGet", h.MethodInfo.Name));
- Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), result.HandlerTypeInfo);
- Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), result.ModelTypeInfo);
- Assert.Same(typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo(), result.PageTypeInfo);
- }
-
- private class ModelWithHandler
- {
- [ModelBinder]
- public int BindMe { get; set; }
-
- public void OnGet() { }
- }
-
- private class PageWithHandlerThatGetsIgnored
- {
- public ModelWithHandler Model => null;
-
- [ModelBinder]
- public int IgnoreMe { get; set; }
-
- public void OnPost() { }
- }
-
-
- [Fact] // If the model has no handler methods, we look at the page instead.
- public void CreateDescriptor_FindsHandlerMethodOnPage_WhenModelHasNoHandlers()
- {
- // Arrange
- var type = typeof(PageWithHandler);
-
- // Act
- var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
- new RazorPageAttribute("/Pages/Index", type, ""));
-
- // Assert
- Assert.Collection(result.BoundProperties, p => Assert.Equal("BindMe", p.Name));
- Assert.Collection(result.HandlerMethods, h => Assert.Equal("OnGet", h.MethodInfo.Name));
- Assert.Same(typeof(PageWithHandler).GetTypeInfo(), result.HandlerTypeInfo);
- Assert.Same(typeof(PocoModel).GetTypeInfo(), result.ModelTypeInfo);
- Assert.Same(typeof(PageWithHandler).GetTypeInfo(), result.PageTypeInfo);
- }
-
- private class PocoModel
- {
- // Just a plain ol' model, nothing to see here.
-
- [ModelBinder]
- public int IgnoreMe { get; set; }
- }
-
- private class PageWithHandler
- {
- public PocoModel Model => null;
-
- [ModelBinder]
- public int BindMe { get; set; }
-
- public void OnGet() { }
+ provider1.Verify();
+ provider2.Verify();
}
[Fact]
- public void CreateHandlerMethods_DiscoversHandlersFromBaseType()
+ public void Load_InvokesApplicationModelConventions()
{
// Arrange
- var type = typeof(InheritsMethods).GetTypeInfo();
+ var descriptor = new PageActionDescriptor();
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- Assert.Collection(
- results.OrderBy(h => h.MethodInfo.Name).ToArray(),
- (handler) =>
+ var compilerProvider = GetCompilerProvider();
+
+ var model = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
+ var provider = new Mock();
+ provider.Setup(p => p.OnProvidersExecuting(It.IsAny()))
+ .Callback((PageApplicationModelProviderContext c) =>
{
- Assert.Equal("OnGet", handler.MethodInfo.Name);
- Assert.Equal(typeof(InheritsMethods), handler.MethodInfo.DeclaringType);
- },
- (handler) =>
- {
- Assert.Equal("OnGet", handler.MethodInfo.Name);
- Assert.Equal(typeof(TestSetPageModel), handler.MethodInfo.DeclaringType);
- },
- (handler) =>
- {
- Assert.Equal("OnPost", handler.MethodInfo.Name);
- Assert.Equal(typeof(TestSetPageModel), handler.MethodInfo.DeclaringType);
+ c.PageApplicationModel = model;
});
- }
+ var providers = new[] { provider.Object };
- private class TestSetPageModel
- {
- public void OnGet()
- {
- }
-
- public void OnPost()
- {
- }
- }
-
- private class TestSetPageWithModel
- {
- public TestSetPageModel Model { get; set; }
- }
-
- private class InheritsMethods : TestSetPageModel
- {
- public new void OnGet()
- {
- }
- }
-
- [Fact]
- public void CreateHandlerMethods_IgnoresNonPublicMethods()
- {
- // Arrange
- var type = typeof(ProtectedModel).GetTypeInfo();
-
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- Assert.Empty(results);
- }
-
- private class ProtectedModel
- {
- protected void OnGet()
- {
- }
-
- private void OnPost()
- {
- }
- }
-
- [Fact]
- public void CreateHandlerMethods_IgnoreGenericTypeParameters()
- {
- // Arrange
- var type = typeof(GenericClassModel).GetTypeInfo();
-
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- Assert.Empty(results);
- }
-
- private class GenericClassModel
- {
- public void OnGet()
- {
- }
- }
-
- [Fact]
- public void CreateHandlerMethods_IgnoresStaticMethods()
- {
- // Arrange
- var type = typeof(PageModelWithStaticHandler).GetTypeInfo();
- var expected = type.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance);
-
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- Assert.Collection(
- results,
- handler => Assert.Same(expected, handler.MethodInfo));
- }
-
- private class PageModelWithStaticHandler
- {
- public static void OnGet(string name)
- {
- }
-
- public void OnGet()
- {
- }
- }
-
- [Fact]
- public void CreateHandlerMethods_IgnoresAbstractMethods()
- {
- // Arrange
- var type = typeof(PageModelWithAbstractMethod).GetTypeInfo();
- var expected = type.GetMethod(nameof(PageModelWithAbstractMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
-
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- Assert.Collection(
- results,
- handler => Assert.Same(expected, handler.MethodInfo));
- }
-
- private abstract class PageModelWithAbstractMethod
- {
- public abstract void OnPost(string name);
-
- public void OnGet()
- {
- }
- }
-
- [Fact]
- public void CreateHandlerMethods_IgnoresMethodWithNonHandlerAttribute()
- {
- // Arrange
- var type = typeof(PageWithNonHandlerMethod).GetTypeInfo();
- var expected = type.GetMethod(nameof(PageWithNonHandlerMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
-
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- Assert.Collection(
- results,
- handler => Assert.Same(expected, handler.MethodInfo));
- }
-
- private class PageWithNonHandlerMethod
- {
- [NonHandler]
- public void OnPost(string name) { }
-
- public void OnGet()
- {
- }
- }
-
- // There are more tests for the parsing elsewhere, this is just testing that it's wired
- // up to the descriptor.
- [Fact]
- public void CreateHandlerMethods_ParsesMethod()
- {
- // Arrange
- var type = typeof(PageModelWithHandlerNames).GetTypeInfo();
-
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- Assert.Collection(
- results.OrderBy(h => h.MethodInfo.Name),
- handler =>
+ var options = new TestOptionsManager();
+ var convention = new Mock();
+ convention.Setup(c => c.Apply(It.IsAny()))
+ .Callback((PageApplicationModel m) =>
{
- Assert.Same(type.GetMethod(nameof(PageModelWithHandlerNames.OnPutDeleteAsync)), handler.MethodInfo);
- Assert.Equal("Put", handler.HttpMethod);
- Assert.Equal("Delete", handler.Name.ToString());
+ Assert.Same(model, m);
});
+ options.Value.ApplicationModelConventions.Add(convention.Object);
+
+ var loader = new DefaultPageLoader(
+ providers,
+ compilerProvider,
+ options);
+
+ // Act
+ var result = loader.Load(new PageActionDescriptor());
+
+ // Assert
+ convention.Verify();
}
- private class PageModelWithHandlerNames
+ private static IViewCompilerProvider GetCompilerProvider()
{
- public void OnPutDeleteAsync()
+ var descriptor = new CompiledViewDescriptor
{
- }
+ ViewAttribute = new RazorPageAttribute("/Views/Index.cshtml", typeof(object), null),
+ };
- public void Foo() // This isn't a valid handler name.
- {
- }
- }
-
- [Fact]
- public void CreateHandlerMethods_AddsParameterDescriptors()
- {
- // Arrange
- var type = typeof(PageWithHandlerParameters).GetTypeInfo();
- var expected = type.GetMethod(nameof(PageWithHandlerParameters.OnPost), BindingFlags.Public | BindingFlags.Instance);
-
- // Act
- var results = DefaultPageLoader.CreateHandlerMethods(type);
-
- // Assert
- var handler = Assert.Single(results);
-
- Assert.Collection(
- handler.Parameters,
- p =>
- {
- Assert.Equal(typeof(string), p.ParameterType);
- Assert.NotNull(p.ParameterInfo);
- Assert.Equal("name", p.Name);
- },
- p =>
- {
- Assert.Equal(typeof(int), p.ParameterType);
- Assert.NotNull(p.ParameterInfo);
- Assert.Equal("id", p.Name);
- Assert.Equal("personId", p.BindingInfo.BinderModelName);
- });
- }
-
- private class PageWithHandlerParameters
- {
- public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { }
- }
-
- // We're using PropertyHelper from Common to find the properties here, which implements
- // out standard set of semantics for properties that the framework interacts with.
- //
- // One of the desirable consequences of that is we only find 'visible' properties. We're not
- // retesting all of the details of PropertyHelper here, just the visibility part as a quick check
- // that we're using PropertyHelper as expected.
- [Fact]
- public void CreateBoundProperties_UsesPropertyHelpers_ToFindProperties()
- {
- // Arrange
- var type = typeof(HidesAProperty).GetTypeInfo();
-
- // Act
- var results = DefaultPageLoader.CreateBoundProperties(type);
-
- // Assert
- Assert.Collection(
- results.OrderBy(p => p.Property.Name),
- p =>
- {
- Assert.Equal(typeof(HidesAProperty).GetTypeInfo(), p.Property.DeclaringType.GetTypeInfo());
- });
- }
-
- private class HasAHiddenProperty
- {
- [BindProperty]
- public int Property { get; set; }
- }
-
- private class HidesAProperty : HasAHiddenProperty
- {
- [BindProperty]
- public new int Property { get; set; }
- }
-
- // We're using BindingInfo to make property binding opt-in here. We're not going to retest
- // all of the semantics of BindingInfo here, as that's covered elsewhere.
- [Fact]
- public void CreateBoundProperties_UsesBindingInfo_ToFindProperties()
- {
- // Arrange
- var type = typeof(ModelWithBindingInfoProperty).GetTypeInfo();
-
- // Act
- var results = DefaultPageLoader.CreateBoundProperties(type);
-
- // Assert
- Assert.Collection(
- results.OrderBy(p => p.Property.Name),
- p =>
- {
- Assert.Equal("Property", p.Property.Name);
- });
- }
-
- private class ModelWithBindingInfoProperty
- {
- [ModelBinder]
- public int Property { get; set; }
-
- public int IgnoreMe { get; set; }
- }
-
- // Additionally [BindProperty] on a property can opt-in a property
- [Fact]
- public void CreateBoundProperties_UsesBindPropertyAttribute_ToFindProperties()
- {
- // Arrange
- var type = typeof(ModelWithBindProperty).GetTypeInfo();
-
- // Act
- var results = DefaultPageLoader.CreateBoundProperties(type);
-
- // Assert
- Assert.Collection(
- results.OrderBy(p => p.Property.Name),
- p =>
- {
- Assert.Equal("Property", p.Property.Name);
- });
- }
-
- private class ModelWithBindProperty
- {
- [BindProperty]
- public int Property { get; set; }
-
- public int IgnoreMe { get; set; }
- }
-
- [Fact]
- public void CreateBoundProperties_SupportsGet_OnProperty()
- {
- // Arrange
- var type = typeof(ModelSupportsGetOnProperty).GetTypeInfo();
-
- // Act
- var results = DefaultPageLoader.CreateBoundProperties(type);
-
- // Assert
- Assert.Collection(
- results.OrderBy(p => p.Property.Name),
- p =>
- {
- Assert.Equal("Property", p.Property.Name);
- Assert.NotNull(p.BindingInfo.RequestPredicate);
- Assert.True(p.BindingInfo.RequestPredicate(new ActionContext()
- {
- HttpContext = new DefaultHttpContext()
- {
- Request =
- {
- Method ="GET",
- }
- }
- }));
- });
- }
-
- private class ModelSupportsGetOnProperty
- {
- [BindProperty(SupportsGet = true)]
- public int Property { get; set; }
-
- public int IgnoreMe { get; set; }
- }
-
- [Theory]
- [InlineData("Foo")]
- [InlineData("On")]
- [InlineData("OnAsync")]
- [InlineData("Async")]
- public void TryParseHandler_ParsesHandlerNames_InvalidData(string methodName)
- {
- // Arrange
-
- // Act
- var result = DefaultPageLoader.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
-
- // Assert
- Assert.False(result);
- Assert.Null(httpMethod);
- Assert.Null(handler);
- }
-
- [Theory]
- [InlineData("OnG", "G", null)]
- [InlineData("OnGAsync", "G", null)]
- [InlineData("OnPOST", "P", "OST")]
- [InlineData("OnPOSTAsync", "P", "OST")]
- [InlineData("OnDeleteFoo", "Delete", "Foo")]
- [InlineData("OnDeleteFooAsync", "Delete", "Foo")]
- [InlineData("OnMadeupLongHandlerName", "Madeup", "LongHandlerName")]
- [InlineData("OnMadeupLongHandlerNameAsync", "Madeup", "LongHandlerName")]
- public void TryParseHandler_ParsesHandlerNames_ValidData(string methodName, string expectedHttpMethod, string expectedHandler)
- {
- // Arrange
-
- // Act
- var result = DefaultPageLoader.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
-
- // Assert
- Assert.True(result);
- Assert.Equal(expectedHttpMethod, httpMethod);
- Assert.Equal(expectedHandler, handler);
+ var compiler = new Mock();
+ compiler.Setup(c => c.CompileAsync(It.IsAny()))
+ .ReturnsAsync(descriptor);
+ var compilerProvider = new Mock();
+ compilerProvider.Setup(p => p.GetCompiler())
+ .Returns(compiler.Object);
+ return compilerProvider.Object;
}
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
index 70b86ff7c6..1d3b7d2185 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
@@ -18,11 +17,9 @@ using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
-using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
@@ -235,7 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
loader.Object,
CreateActionDescriptorCollection(descriptor));
- var context = new ActionInvokerProviderContext(new ActionContext()
+ var context = new ActionInvokerProviderContext(new ActionContext
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
@@ -251,6 +248,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var entry1 = actionInvoker.CacheEntry;
// Act - 2
+ context = new ActionInvokerProviderContext(new ActionContext
+ {
+ ActionDescriptor = descriptor,
+ HttpContext = new DefaultHttpContext(),
+ RouteData = new RouteData(),
+ });
invokerProvider.OnProvidersExecuting(context);
// Assert - 2
@@ -445,6 +448,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
HandlerTypeInfo = modelTypeInfo ?? pageTypeInfo,
ModelTypeInfo = modelTypeInfo ?? pageTypeInfo,
PageTypeInfo = pageTypeInfo,
+ FilterDescriptors = Array.Empty(),
};
}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageFilterApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageFilterApplicationModelProviderTest.cs
deleted file mode 100644
index ddb169f2b0..0000000000
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageFilterApplicationModelProviderTest.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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.ApplicationModels;
-using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
-{
- public class PageFilterApplicationModelProviderTest
- {
- [Fact]
- public void OnProvidersExecuting_AddsFiltersToModels()
- {
- // Arrange
- var applicationModel1 = new PageApplicationModel("/Home.cshtml", "/Home.cshtml");
- var applicationModel2 = new PageApplicationModel("/About.cshtml", "/About.cshtml");
- var modelProvider = new PageFilterApplicationModelProvider();
- var context = new PageApplicationModelProviderContext
- {
- Results =
- {
- applicationModel1,
- applicationModel2,
- }
- };
-
- // Act
- modelProvider.OnProvidersExecuting(context);
-
- // Assert
- Assert.Collection(applicationModel1.Filters,
- filter => Assert.IsType(filter),
- filter => Assert.IsType(filter));
-
- Assert.Collection(applicationModel2.Filters,
- filter => Assert.IsType(filter),
- filter => Assert.IsType(filter));
- }
- }
-}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PocoModel.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PocoModel.cs
new file mode 100644
index 0000000000..1c33b0a5bc
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PocoModel.cs
@@ -0,0 +1,17 @@
+// 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
+{
+ public partial class DefaultPageApplicationModelProviderTest
+ {
+ private class PocoModel
+ {
+ // Just a plain ol' model, nothing to see here.
+
+ [ModelBinder]
+ public int IgnoreMe { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs
similarity index 84%
rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageApplicationModelProviderTest.cs
rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs
index a4f9995f37..95fea9b11f 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageApplicationModelProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs
@@ -10,7 +10,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
- public class RazorProjectPageApplicationModelProviderTest
+ public class RazorProjectPageRouteModelProviderTest
{
[Fact]
public void OnProvidersExecuting_ReturnsPagesWithPageDirective()
@@ -27,14 +27,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager();
optionsManager.Value.RootDirectory = "/";
- var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
- var context = new PageApplicationModelProviderContext();
+ var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
+ var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
- Assert.Collection(context.Results,
+ Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
@@ -61,14 +61,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager();
optionsManager.Value.RootDirectory = "/";
- var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
- var context = new PageApplicationModelProviderContext();
+ var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
+ var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
- Assert.Collection(context.Results,
+ Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Admin/Index.cshtml", model.RelativePath);
@@ -99,12 +99,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager();
optionsManager.Value.RootDirectory = "/";
- var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
- var context = new PageApplicationModelProviderContext();
+ var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
+ var context = new PageRouteModelProviderContext();
// Act & Assert
var ex = Assert.Throws(() => provider.OnProvidersExecuting(context));
- Assert.Equal("The route for the page at '/Index.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
+ Assert.Equal("The route for the page at '/Index.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
ex.Message);
}
@@ -125,14 +125,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager();
optionsManager.Value.RootDirectory = "/";
- var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
- var context = new PageApplicationModelProviderContext();
+ var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
+ var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
- Assert.Collection(context.Results,
+ Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
@@ -163,14 +163,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager();
optionsManager.Value.RootDirectory = "/Pages";
- var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
- var context = new PageApplicationModelProviderContext();
+ var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
+ var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
- Assert.Collection(context.Results,
+ Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Index.cshtml", model.RelativePath);
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/TempDataFilterPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/TempDataFilterPageApplicationModelProviderTest.cs
new file mode 100644
index 0000000000..2f84b6a797
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/TempDataFilterPageApplicationModelProviderTest.cs
@@ -0,0 +1,36 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+{
+ public class TempDataFilterPageApplicationModelProviderTest
+ {
+ [Fact]
+ public void OnProvidersExecuting_AddsFiltersToModel()
+ {
+ // Arrange
+ var actionDescriptor = new PageActionDescriptor();
+ var applicationModel = new PageApplicationModel(
+ actionDescriptor,
+ typeof(object).GetTypeInfo(),
+ new object[0]);
+ var applicationModelProvider = new TempDataFilterPageApplicationModelProvider();
+ var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeof(object).GetTypeInfo())
+ {
+ PageApplicationModel = applicationModel,
+ };
+
+ // Act
+ applicationModelProvider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.Collection(
+ applicationModel.Filters,
+ filter => Assert.IsType(filter));
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
index 058c0dd19f..018fab9678 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
@@ -422,13 +422,22 @@ namespace Microsoft.AspNetCore.Mvc
typeof(JsonPatchOperationsArrayProvider),
}
},
+ {
+ typeof(IPageRouteModelProvider),
+ new[]
+ {
+ typeof(CompiledPageRouteModelProvider),
+ typeof(RazorProjectPageRouteModelProvider),
+ }
+ },
{
typeof(IPageApplicationModelProvider),
new[]
{
- typeof(CompiledPageApplicationModelProvider),
- typeof(RazorProjectPageApplicationModelProvider),
- typeof(PageFilterApplicationModelProvider),
+ typeof(AuthorizationPageApplicationModelProvider),
+ typeof(AuthorizationPageApplicationModelProvider),
+ typeof(DefaultPageApplicationModelProvider),
+ typeof(TempDataFilterPageApplicationModelProvider),
}
},
};
diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs b/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs
index d6622314da..ec8dc27137 100644
--- a/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs
+++ b/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs
@@ -1,6 +1,7 @@
// 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.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
diff --git a/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cs b/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cs
new file mode 100644
index 0000000000..8be7783920
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cs
@@ -0,0 +1,15 @@
+// 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.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace RazorPagesWebSite
+{
+ [Authorize]
+ public class ModelWithAuthFilter : PageModel
+ {
+ public IActionResult OnGet() => Page();
+ }
+}
diff --git a/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cshtml b/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cshtml
new file mode 100644
index 0000000000..8862a69efa
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cshtml
@@ -0,0 +1,4 @@
+@page
+@model RazorPagesWebSite.ModelWithAuthFilter
+
+Can't see me
diff --git a/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cs b/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cs
new file mode 100644
index 0000000000..f56b01d629
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cs
@@ -0,0 +1,44 @@
+// 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.Linq;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace RazorPagesWebSite
+{
+ [HandlerChangingPageFilter]
+ public class ModelWithPageFilter
+ {
+ public string Message { get; private set; }
+
+ public void OnGet()
+ {
+ Message = $"Hello from {nameof(OnGet)}";
+ }
+
+ public void OnGetEdit()
+ {
+ Message = $"Hello from {nameof(OnGetEdit)}";
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Class)]
+ public class HandlerChangingPageFilterAttribute : Attribute, IPageFilter
+ {
+ public void OnPageHandlerSelected(PageHandlerSelectedContext context)
+ {
+ context.HandlerMethod = context.ActionDescriptor.HandlerMethods.First(m => m.Name == "Edit");
+ }
+
+ public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
+ {
+ }
+
+ public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
+ {
+ }
+ }
+}
diff --git a/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cshtml b/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cshtml
new file mode 100644
index 0000000000..05809eaac4
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cshtml
@@ -0,0 +1,4 @@
+@page
+@model RazorPagesWebSite.ModelWithPageFilter
+
+@Model.Message
diff --git a/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousModel.cs b/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousModel.cs
new file mode 100644
index 0000000000..fa96abbdbf
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousModel.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Authorization;
+
+namespace RazorPagesWebSite
+{
+ [AllowAnonymous]
+ public class AnonymousModel
+ {
+ public void OnGet()
+ {
+ }
+ }
+}
diff --git a/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousViaModel.cshtml b/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousViaModel.cshtml
new file mode 100644
index 0000000000..a5f469d689
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousViaModel.cshtml
@@ -0,0 +1,3 @@
+@page
+@model AnonymousModel
+Hello from Anonymous
\ No newline at end of file