Add IPageFactory and IPageActivator
This commit is contained in:
parent
9146fce4ec
commit
2b8233932a
|
|
@ -0,0 +1,186 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
{
|
||||||
|
public class RazorPagePropertyActivator
|
||||||
|
{
|
||||||
|
private delegate ViewDataDictionary CreateViewDataNestedDelegate(ViewDataDictionary source);
|
||||||
|
private delegate ViewDataDictionary CreateViewDataRootDelegate(ModelStateDictionary modelState);
|
||||||
|
|
||||||
|
public RazorPagePropertyActivator(
|
||||||
|
Type pageType,
|
||||||
|
Type modelType,
|
||||||
|
IModelMetadataProvider metadataProvider,
|
||||||
|
PropertyValueAccessors propertyValueAccessors)
|
||||||
|
{
|
||||||
|
var viewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
|
||||||
|
ViewDataDictionaryType = viewDataType;
|
||||||
|
CreateViewDataNested = GetCreateViewDataNested(viewDataType);
|
||||||
|
CreateViewDataRoot = GetCreateViewDataRoot(viewDataType, metadataProvider);
|
||||||
|
|
||||||
|
PropertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(
|
||||||
|
pageType,
|
||||||
|
typeof(RazorInjectAttribute),
|
||||||
|
propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors),
|
||||||
|
includeNonPublic: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PropertyActivator<ViewContext>[] PropertyActivators { get; }
|
||||||
|
|
||||||
|
private Type ViewDataDictionaryType { get; }
|
||||||
|
|
||||||
|
private CreateViewDataNestedDelegate CreateViewDataNested { get; }
|
||||||
|
|
||||||
|
private CreateViewDataRootDelegate CreateViewDataRoot { get; }
|
||||||
|
|
||||||
|
public void Activate(object page, ViewContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ViewData = CreateViewDataDictionary(context);
|
||||||
|
|
||||||
|
for (var i = 0; i < PropertyActivators.Length; i++)
|
||||||
|
{
|
||||||
|
var activateInfo = PropertyActivators[i];
|
||||||
|
activateInfo.Activate(page, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ViewDataDictionary CreateViewDataDictionary(ViewContext context)
|
||||||
|
{
|
||||||
|
// Create a ViewDataDictionary<TModel> if the ViewContext.ViewData is not set or the type of
|
||||||
|
// ViewContext.ViewData is an incompatible type.
|
||||||
|
if (context.ViewData == null)
|
||||||
|
{
|
||||||
|
// Create ViewDataDictionary<TModel>(IModelMetadataProvider, ModelStateDictionary).
|
||||||
|
return CreateViewDataRoot(context.ModelState);
|
||||||
|
}
|
||||||
|
else if (context.ViewData.GetType() != ViewDataDictionaryType)
|
||||||
|
{
|
||||||
|
// Create ViewDataDictionary<TModel>(ViewDataDictionary).
|
||||||
|
return CreateViewDataNested(context.ViewData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.ViewData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CreateViewDataNestedDelegate GetCreateViewDataNested(Type viewDataDictionaryType)
|
||||||
|
{
|
||||||
|
var parameterTypes = new Type[] { typeof(ViewDataDictionary) };
|
||||||
|
var matchingConstructor = viewDataDictionaryType.GetConstructor(parameterTypes);
|
||||||
|
Debug.Assert(matchingConstructor != null);
|
||||||
|
|
||||||
|
var parameters = new ParameterExpression[] { Expression.Parameter(parameterTypes[0]) };
|
||||||
|
var newExpression = Expression.New(matchingConstructor, parameters);
|
||||||
|
var castNewCall = Expression.Convert(
|
||||||
|
newExpression,
|
||||||
|
typeof(ViewDataDictionary));
|
||||||
|
var lambda = Expression.Lambda<CreateViewDataNestedDelegate>(castNewCall, parameters);
|
||||||
|
return lambda.Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CreateViewDataRootDelegate GetCreateViewDataRoot(
|
||||||
|
Type viewDataDictionaryType,
|
||||||
|
IModelMetadataProvider provider)
|
||||||
|
{
|
||||||
|
var parameterTypes = new[]
|
||||||
|
{
|
||||||
|
typeof(IModelMetadataProvider),
|
||||||
|
typeof(ModelStateDictionary)
|
||||||
|
};
|
||||||
|
var matchingConstructor = viewDataDictionaryType.GetConstructor(parameterTypes);
|
||||||
|
Debug.Assert(matchingConstructor != null);
|
||||||
|
|
||||||
|
var parameterExpression = Expression.Parameter(parameterTypes[1]);
|
||||||
|
var parameters = new Expression[]
|
||||||
|
{
|
||||||
|
Expression.Constant(provider),
|
||||||
|
parameterExpression
|
||||||
|
};
|
||||||
|
var newExpression = Expression.New(matchingConstructor, parameters);
|
||||||
|
var castNewCall = Expression.Convert(
|
||||||
|
newExpression,
|
||||||
|
typeof(ViewDataDictionary));
|
||||||
|
var lambda = Expression.Lambda<CreateViewDataRootDelegate>(castNewCall, parameterExpression);
|
||||||
|
return lambda.Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PropertyActivator<ViewContext> CreateActivateInfo(
|
||||||
|
PropertyInfo property,
|
||||||
|
PropertyValueAccessors valueAccessors)
|
||||||
|
{
|
||||||
|
Func<ViewContext, object> valueAccessor;
|
||||||
|
if (typeof(ViewDataDictionary).IsAssignableFrom(property.PropertyType))
|
||||||
|
{
|
||||||
|
// Logic looks reversed in condition above but is OK. Support only properties of base
|
||||||
|
// ViewDataDictionary type and activationInfo.ViewDataDictionaryType. VDD<AnotherType> will fail when
|
||||||
|
// assigning to the property (InvalidCastException) and that's fine.
|
||||||
|
valueAccessor = context => context.ViewData;
|
||||||
|
}
|
||||||
|
else if (property.PropertyType == typeof(IUrlHelper))
|
||||||
|
{
|
||||||
|
// W.r.t. specificity of above condition: Users are much more likely to inject their own
|
||||||
|
// IUrlHelperFactory than to create a class implementing IUrlHelper (or a sub-interface) and inject
|
||||||
|
// that. But the second scenario is supported. (Note the class must implement ICanHasViewContext.)
|
||||||
|
valueAccessor = valueAccessors.UrlHelperAccessor;
|
||||||
|
}
|
||||||
|
else if (property.PropertyType == typeof(IJsonHelper))
|
||||||
|
{
|
||||||
|
valueAccessor = valueAccessors.JsonHelperAccessor;
|
||||||
|
}
|
||||||
|
else if (property.PropertyType == typeof(DiagnosticSource))
|
||||||
|
{
|
||||||
|
valueAccessor = valueAccessors.DiagnosticSourceAccessor;
|
||||||
|
}
|
||||||
|
else if (property.PropertyType == typeof(HtmlEncoder))
|
||||||
|
{
|
||||||
|
valueAccessor = valueAccessors.HtmlEncoderAccessor;
|
||||||
|
}
|
||||||
|
else if (property.PropertyType == typeof(IModelExpressionProvider))
|
||||||
|
{
|
||||||
|
valueAccessor = valueAccessors.ModelExpressionProviderAccessor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valueAccessor = context =>
|
||||||
|
{
|
||||||
|
var serviceProvider = context.HttpContext.RequestServices;
|
||||||
|
var value = serviceProvider.GetRequiredService(property.PropertyType);
|
||||||
|
(value as IViewContextAware)?.Contextualize(context);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PropertyActivator<ViewContext>(property, valueAccessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropertyValueAccessors
|
||||||
|
{
|
||||||
|
public Func<ViewContext, object> UrlHelperAccessor { get; set; }
|
||||||
|
|
||||||
|
public Func<ViewContext, object> JsonHelperAccessor { get; set; }
|
||||||
|
|
||||||
|
public Func<ViewContext, object> DiagnosticSourceAccessor { get; set; }
|
||||||
|
|
||||||
|
public Func<ViewContext, object> HtmlEncoderAccessor { get; set; }
|
||||||
|
|
||||||
|
public Func<ViewContext, object> ModelExpressionProviderAccessor { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
@ -12,30 +11,19 @@ using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class RazorPageActivator : IRazorPageActivator
|
public class RazorPageActivator : IRazorPageActivator
|
||||||
{
|
{
|
||||||
private delegate ViewDataDictionary CreateViewDataNested(ViewDataDictionary source);
|
|
||||||
private delegate ViewDataDictionary CreateViewDataRoot(
|
|
||||||
IModelMetadataProvider metadataProvider,
|
|
||||||
ModelStateDictionary modelState);
|
|
||||||
|
|
||||||
// Name of the "public TModel Model" property on RazorPage<TModel>
|
// Name of the "public TModel Model" property on RazorPage<TModel>
|
||||||
private const string ModelPropertyName = "Model";
|
private const string ModelPropertyName = "Model";
|
||||||
private readonly ConcurrentDictionary<Type, PageActivationInfo> _activationInfo;
|
private readonly ConcurrentDictionary<Type, RazorPagePropertyActivator> _activationInfo;
|
||||||
private readonly IModelMetadataProvider _metadataProvider;
|
private readonly IModelMetadataProvider _metadataProvider;
|
||||||
|
|
||||||
// Value accessors for common singleton properties activated in a RazorPage.
|
// Value accessors for common singleton properties activated in a RazorPage.
|
||||||
private Func<ViewContext, object> _urlHelperAccessor;
|
private readonly RazorPagePropertyActivator.PropertyValueAccessors _propertyAccessors;
|
||||||
private Func<ViewContext, object> _jsonHelperAccessor;
|
|
||||||
private Func<ViewContext, object> _diagnosticSourceAccessor;
|
|
||||||
private Func<ViewContext, object> _htmlEncoderAccessor;
|
|
||||||
private Func<ViewContext, object> _modelExpressionProviderAccessor;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RazorPageActivator"/> class.
|
/// Initializes a new instance of the <see cref="RazorPageActivator"/> class.
|
||||||
|
|
@ -48,13 +36,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
HtmlEncoder htmlEncoder,
|
HtmlEncoder htmlEncoder,
|
||||||
IModelExpressionProvider modelExpressionProvider)
|
IModelExpressionProvider modelExpressionProvider)
|
||||||
{
|
{
|
||||||
_activationInfo = new ConcurrentDictionary<Type, PageActivationInfo>();
|
_activationInfo = new ConcurrentDictionary<Type, RazorPagePropertyActivator>();
|
||||||
_metadataProvider = metadataProvider;
|
_metadataProvider = metadataProvider;
|
||||||
_urlHelperAccessor = context => urlHelperFactory.GetUrlHelper(context);
|
|
||||||
_jsonHelperAccessor = context => jsonHelper;
|
_propertyAccessors = new RazorPagePropertyActivator.PropertyValueAccessors
|
||||||
_diagnosticSourceAccessor = context => diagnosticSource;
|
{
|
||||||
_htmlEncoderAccessor = context => htmlEncoder;
|
UrlHelperAccessor = context => urlHelperFactory.GetUrlHelper(context),
|
||||||
_modelExpressionProviderAccessor = context => modelExpressionProvider;
|
JsonHelperAccessor = context => jsonHelper,
|
||||||
|
DiagnosticSourceAccessor = context => diagnosticSource,
|
||||||
|
HtmlEncoderAccessor = context => htmlEncoder,
|
||||||
|
ModelExpressionProviderAccessor = context => modelExpressionProvider,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -70,159 +62,30 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
var activationInfo = _activationInfo.GetOrAdd(page.GetType(),
|
var pageType = page.GetType();
|
||||||
CreateViewActivationInfo);
|
RazorPagePropertyActivator propertyActivator;
|
||||||
|
if (!_activationInfo.TryGetValue(pageType, out propertyActivator))
|
||||||
context.ViewData = CreateViewDataDictionary(context, activationInfo);
|
|
||||||
|
|
||||||
for (var i = 0; i < activationInfo.PropertyActivators.Length; i++)
|
|
||||||
{
|
{
|
||||||
var activateInfo = activationInfo.PropertyActivators[i];
|
// Look for a property named "Model". If it is non-null, we'll assume this is
|
||||||
activateInfo.Activate(page, context);
|
// the equivalent of TModel Model property on RazorPage<TModel>
|
||||||
}
|
var modelProperty = pageType.GetRuntimeProperty(ModelPropertyName);
|
||||||
}
|
if (modelProperty == null)
|
||||||
|
|
||||||
private ViewDataDictionary CreateViewDataDictionary(ViewContext context, PageActivationInfo activationInfo)
|
|
||||||
{
|
|
||||||
// Create a ViewDataDictionary<TModel> if the ViewContext.ViewData is not set or the type of
|
|
||||||
// ViewContext.ViewData is an incompatible type.
|
|
||||||
if (context.ViewData == null)
|
|
||||||
{
|
|
||||||
// Create ViewDataDictionary<TModel>(IModelMetadataProvider, ModelStateDictionary).
|
|
||||||
return activationInfo.CreateViewDataRoot(
|
|
||||||
_metadataProvider,
|
|
||||||
context.ModelState);
|
|
||||||
}
|
|
||||||
else if (context.ViewData.GetType() != activationInfo.ViewDataDictionaryType)
|
|
||||||
{
|
|
||||||
// Create ViewDataDictionary<TModel>(ViewDataDictionary).
|
|
||||||
return activationInfo.CreateViewDataNested(context.ViewData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.ViewData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PageActivationInfo CreateViewActivationInfo(Type type)
|
|
||||||
{
|
|
||||||
// Look for a property named "Model". If it is non-null, we'll assume this is
|
|
||||||
// the equivalent of TModel Model property on RazorPage<TModel>
|
|
||||||
var modelProperty = type.GetRuntimeProperty(ModelPropertyName);
|
|
||||||
if (modelProperty == null)
|
|
||||||
{
|
|
||||||
var message = Resources.FormatViewCannotBeActivated(type.FullName, GetType().FullName);
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
var modelType = modelProperty.PropertyType;
|
|
||||||
var viewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
|
|
||||||
|
|
||||||
return new PageActivationInfo
|
|
||||||
{
|
|
||||||
ViewDataDictionaryType = viewDataType,
|
|
||||||
CreateViewDataNested = GetCreateViewDataNested(viewDataType),
|
|
||||||
CreateViewDataRoot = GetCreateViewDataRoot(viewDataType),
|
|
||||||
PropertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(
|
|
||||||
type,
|
|
||||||
typeof(RazorInjectAttribute),
|
|
||||||
CreateActivateInfo,
|
|
||||||
includeNonPublic: true)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private CreateViewDataNested GetCreateViewDataNested(Type viewDataDictionaryType)
|
|
||||||
{
|
|
||||||
var parameterTypes = new Type[] { typeof(ViewDataDictionary) };
|
|
||||||
var matchingConstructor = viewDataDictionaryType.GetConstructor(parameterTypes);
|
|
||||||
Debug.Assert(matchingConstructor != null);
|
|
||||||
|
|
||||||
var parameters = new ParameterExpression[] { Expression.Parameter(parameterTypes[0]) };
|
|
||||||
var newExpression = Expression.New(matchingConstructor, parameters);
|
|
||||||
var castNewCall = Expression.Convert(
|
|
||||||
newExpression,
|
|
||||||
typeof(ViewDataDictionary));
|
|
||||||
var lambda = Expression.Lambda<CreateViewDataNested>(castNewCall, parameters);
|
|
||||||
return lambda.Compile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CreateViewDataRoot GetCreateViewDataRoot(Type viewDataDictionaryType)
|
|
||||||
{
|
|
||||||
var parameterTypes = new Type[] {
|
|
||||||
typeof(IModelMetadataProvider),
|
|
||||||
typeof(ModelStateDictionary) };
|
|
||||||
var matchingConstructor = viewDataDictionaryType.GetConstructor(parameterTypes);
|
|
||||||
Debug.Assert(matchingConstructor != null);
|
|
||||||
|
|
||||||
var parameters = new ParameterExpression[] {
|
|
||||||
Expression.Parameter(parameterTypes[0]),
|
|
||||||
Expression.Parameter(parameterTypes[1]) };
|
|
||||||
var newExpression = Expression.New(matchingConstructor, parameters);
|
|
||||||
var castNewCall = Expression.Convert(
|
|
||||||
newExpression,
|
|
||||||
typeof(ViewDataDictionary));
|
|
||||||
var lambda = Expression.Lambda<CreateViewDataRoot>(castNewCall, parameters);
|
|
||||||
return lambda.Compile();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PropertyActivator<ViewContext> CreateActivateInfo(PropertyInfo property)
|
|
||||||
{
|
|
||||||
Func<ViewContext, object> valueAccessor;
|
|
||||||
if (typeof(ViewDataDictionary).IsAssignableFrom(property.PropertyType))
|
|
||||||
{
|
|
||||||
// Logic looks reversed in condition above but is OK. Support only properties of base
|
|
||||||
// ViewDataDictionary type and activationInfo.ViewDataDictionaryType. VDD<AnotherType> will fail when
|
|
||||||
// assigning to the property (InvalidCastException) and that's fine.
|
|
||||||
valueAccessor = context => context.ViewData;
|
|
||||||
}
|
|
||||||
else if (property.PropertyType == typeof(IUrlHelper))
|
|
||||||
{
|
|
||||||
// W.r.t. specificity of above condition: Users are much more likely to inject their own
|
|
||||||
// IUrlHelperFactory than to create a class implementing IUrlHelper (or a sub-interface) and inject
|
|
||||||
// that. But the second scenario is supported. (Note the class must implement ICanHasViewContext.)
|
|
||||||
valueAccessor = _urlHelperAccessor;
|
|
||||||
}
|
|
||||||
else if (property.PropertyType == typeof(IJsonHelper))
|
|
||||||
{
|
|
||||||
valueAccessor = _jsonHelperAccessor;
|
|
||||||
}
|
|
||||||
else if (property.PropertyType == typeof(DiagnosticSource))
|
|
||||||
{
|
|
||||||
valueAccessor = _diagnosticSourceAccessor;
|
|
||||||
}
|
|
||||||
else if (property.PropertyType == typeof(HtmlEncoder))
|
|
||||||
{
|
|
||||||
valueAccessor = _htmlEncoderAccessor;
|
|
||||||
}
|
|
||||||
else if (property.PropertyType == typeof(IModelExpressionProvider))
|
|
||||||
{
|
|
||||||
valueAccessor = _modelExpressionProviderAccessor;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
valueAccessor = context =>
|
|
||||||
{
|
{
|
||||||
var serviceProvider = context.HttpContext.RequestServices;
|
var message = Resources.FormatViewCannotBeActivated(pageType.FullName, GetType().FullName);
|
||||||
var value = serviceProvider.GetRequiredService(property.PropertyType);
|
throw new InvalidOperationException(message);
|
||||||
(value as IViewContextAware)?.Contextualize(context);
|
}
|
||||||
|
|
||||||
return value;
|
var modelType = modelProperty.PropertyType;
|
||||||
};
|
propertyActivator = new RazorPagePropertyActivator(
|
||||||
|
pageType,
|
||||||
|
modelType,
|
||||||
|
_metadataProvider,
|
||||||
|
_propertyAccessors);
|
||||||
|
|
||||||
|
propertyActivator = _activationInfo.GetOrAdd(pageType, propertyActivator);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PropertyActivator<ViewContext>(property, valueAccessor);
|
propertyActivator.Activate(page, context);
|
||||||
}
|
|
||||||
|
|
||||||
private class PageActivationInfo
|
|
||||||
{
|
|
||||||
public PropertyActivator<ViewContext>[] PropertyActivators { get; set; }
|
|
||||||
|
|
||||||
public Type ViewDataDictionaryType { get; set; }
|
|
||||||
|
|
||||||
public CreateViewDataNested CreateViewDataNested { get; set; }
|
|
||||||
|
|
||||||
public CreateViewDataRoot CreateViewDataRoot { get; set; }
|
|
||||||
|
|
||||||
public Action<object, object> ViewDataDictionarySetter { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="PageActionDescriptor"/> for a compiled Razor page.
|
||||||
|
/// </summary>
|
||||||
|
public class CompiledPageActionDescriptor : PageActionDescriptor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an empty <see cref="CompiledPageActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CompiledPageActionDescriptor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="CompiledPageActionDescriptor"/>
|
||||||
|
/// from the specified <paramref name="actionDescriptor"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionDescriptor">The <see cref="PageActionDescriptor"/>.</param>
|
||||||
|
public CompiledPageActionDescriptor(PageActionDescriptor actionDescriptor)
|
||||||
|
: base(actionDescriptor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="TypeInfo"/> of the page.
|
||||||
|
/// </summary>
|
||||||
|
public TypeInfo PageTypeInfo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="TypeInfo"/> of the model.
|
||||||
|
/// </summary>
|
||||||
|
public TypeInfo ModelTypeInfo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods to create a Razor page.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPageActivatorProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a Razor page activator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
|
||||||
|
/// <returns>The delegate used to activate the page.</returns>
|
||||||
|
Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor descriptor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases a Razor page.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
|
||||||
|
/// <returns>The delegate used to dispose the activated page.</returns>
|
||||||
|
Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods for creation and disposal of Razor pages.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPageFactoryProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a factory for producing Razor pages for the specified <see cref="PageContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
|
||||||
|
/// <returns>The Razor page factory.</returns>
|
||||||
|
Func<PageContext, object> CreatePageFactory(CompiledPageActionDescriptor descriptor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases a Razor page.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
|
||||||
|
/// <returns>The delegate used to release the created page.</returns>
|
||||||
|
Action<PageContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IPageLoader
|
||||||
|
{
|
||||||
|
Type Load(PageActionDescriptor actionDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
// 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.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="IPageActivatorProvider"/> that uses type activation to create Pages.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultPageActivator : IPageActivatorProvider
|
||||||
|
{
|
||||||
|
private readonly Action<PageContext, object> _disposer = Dispose;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
if (actionDescriptor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(actionDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageTypeInfo = actionDescriptor.PageTypeInfo?.AsType();
|
||||||
|
if (pageTypeInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
|
||||||
|
nameof(actionDescriptor.PageTypeInfo),
|
||||||
|
nameof(actionDescriptor)),
|
||||||
|
nameof(actionDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreatePageFactory(pageTypeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
if (actionDescriptor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(actionDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo))
|
||||||
|
{
|
||||||
|
return _disposer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Func<PageContext, object> CreatePageFactory(Type pageTypeInfo)
|
||||||
|
{
|
||||||
|
var parameter = Expression.Parameter(typeof(PageContext), "pageContext");
|
||||||
|
|
||||||
|
// new Page();
|
||||||
|
var newExpression = Expression.New(pageTypeInfo);
|
||||||
|
|
||||||
|
// () => new Page();
|
||||||
|
var pageFactory = Expression
|
||||||
|
.Lambda<Func<PageContext, object>>(newExpression, parameter)
|
||||||
|
.Compile();
|
||||||
|
return pageFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Dispose(PageContext context, object page)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
((IDisposable)page).Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void NullDisposer(PageContext context, object page)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class DefaultPageFactory : IPageFactoryProvider
|
||||||
|
{
|
||||||
|
private readonly IPageActivatorProvider _pageActivator;
|
||||||
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
|
private readonly RazorPagePropertyActivator.PropertyValueAccessors _propertyAccessors;
|
||||||
|
|
||||||
|
public DefaultPageFactory(
|
||||||
|
IPageActivatorProvider pageActivator,
|
||||||
|
IModelMetadataProvider metadataProvider,
|
||||||
|
IUrlHelperFactory urlHelperFactory,
|
||||||
|
IJsonHelper jsonHelper,
|
||||||
|
DiagnosticSource diagnosticSource,
|
||||||
|
HtmlEncoder htmlEncoder,
|
||||||
|
IModelExpressionProvider modelExpressionProvider)
|
||||||
|
{
|
||||||
|
_pageActivator = pageActivator;
|
||||||
|
_modelMetadataProvider = metadataProvider;
|
||||||
|
_propertyAccessors = new RazorPagePropertyActivator.PropertyValueAccessors
|
||||||
|
{
|
||||||
|
UrlHelperAccessor = context => urlHelperFactory.GetUrlHelper(context),
|
||||||
|
JsonHelperAccessor = context => jsonHelper,
|
||||||
|
DiagnosticSourceAccessor = context => diagnosticSource,
|
||||||
|
HtmlEncoderAccessor = context => htmlEncoder,
|
||||||
|
ModelExpressionProviderAccessor = context => modelExpressionProvider,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Func<PageContext, object> CreatePageFactory(CompiledPageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
if (!typeof(Page).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatActivatedInstance_MustBeAnInstanceOf(
|
||||||
|
_pageActivator.GetType().FullName,
|
||||||
|
typeof(Page).FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var activatorFactory = _pageActivator.CreateActivator(actionDescriptor);
|
||||||
|
var modelType = actionDescriptor.ModelTypeInfo?.AsType() ?? actionDescriptor.PageTypeInfo.AsType();
|
||||||
|
var propertyActivator = new RazorPagePropertyActivator(
|
||||||
|
actionDescriptor.PageTypeInfo.AsType(),
|
||||||
|
modelType,
|
||||||
|
_modelMetadataProvider,
|
||||||
|
_propertyAccessors);
|
||||||
|
|
||||||
|
return (context) =>
|
||||||
|
{
|
||||||
|
var page = (Page)activatorFactory(context);
|
||||||
|
page.PageContext = context;
|
||||||
|
propertyActivator.Activate(page, context);
|
||||||
|
return page;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Action<PageContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor)
|
||||||
|
{
|
||||||
|
if (descriptor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(descriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _pageActivator.CreateReleaser(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class PageActionInvoker : IActionInvoker
|
||||||
|
{
|
||||||
|
private readonly PageActionInvokerCacheEntry _cacheEntry;
|
||||||
|
private readonly ActionContext _actionContext;
|
||||||
|
|
||||||
|
public PageActionInvoker(
|
||||||
|
PageActionInvokerCacheEntry cacheEntry,
|
||||||
|
ActionContext actionContext)
|
||||||
|
{
|
||||||
|
_cacheEntry = cacheEntry;
|
||||||
|
_actionContext = actionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InvokeAsync()
|
||||||
|
{
|
||||||
|
return TaskCache.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// 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.Filters;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class PageActionInvokerCacheEntry
|
||||||
|
{
|
||||||
|
public PageActionInvokerCacheEntry(
|
||||||
|
CompiledPageActionDescriptor actionDescriptor,
|
||||||
|
Func<PageContext, object> pageFactory,
|
||||||
|
Action<PageContext, object> releasePage,
|
||||||
|
Func<PageContext, IFilterMetadata[]> filterProvider)
|
||||||
|
{
|
||||||
|
ActionDescriptor = actionDescriptor;
|
||||||
|
PageFactory = pageFactory;
|
||||||
|
ReleasePage = releasePage;
|
||||||
|
FilterProvider = filterProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompiledPageActionDescriptor ActionDescriptor { get; }
|
||||||
|
|
||||||
|
public Func<PageContext, object> PageFactory { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The action invoked to release a page. This may be <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Action<PageContext, object> ReleasePage { get; }
|
||||||
|
|
||||||
|
Func<PageContext, IFilterMetadata[]> FilterProvider { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
// 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.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class PageActionInvokerProvider : IActionInvokerProvider
|
||||||
|
{
|
||||||
|
private const string ModelPropertyName = "Model";
|
||||||
|
private readonly IPageLoader _loader;
|
||||||
|
private readonly IPageFactoryProvider _pageFactoryProvider;
|
||||||
|
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
||||||
|
private readonly IFilterProvider[] _filterProviders;
|
||||||
|
private volatile InnerCache _currentCache;
|
||||||
|
|
||||||
|
public PageActionInvokerProvider(
|
||||||
|
IPageLoader loader,
|
||||||
|
IPageFactoryProvider pageFactoryProvider,
|
||||||
|
IActionDescriptorCollectionProvider collectionProvider,
|
||||||
|
IEnumerable<IFilterProvider> filterProviders)
|
||||||
|
{
|
||||||
|
_loader = loader;
|
||||||
|
_collectionProvider = collectionProvider;
|
||||||
|
_pageFactoryProvider = pageFactoryProvider;
|
||||||
|
_filterProviders = filterProviders.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Order { get; } = -1000;
|
||||||
|
|
||||||
|
public void OnProvidersExecuting(ActionInvokerProviderContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionDescriptor = context.ActionContext.ActionDescriptor as PageActionDescriptor;
|
||||||
|
if (actionDescriptor == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheEntry = GetOrAddCacheEntry(context, actionDescriptor);
|
||||||
|
context.Result = new PageActionInvoker(cacheEntry, context.ActionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnProvidersExecuted(ActionInvokerProviderContext context)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private InnerCache CurrentCache
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var current = _currentCache;
|
||||||
|
var actionDescriptors = _collectionProvider.ActionDescriptors;
|
||||||
|
|
||||||
|
if (current == null || current.Version != actionDescriptors.Version)
|
||||||
|
{
|
||||||
|
current = new InnerCache(actionDescriptors.Version);
|
||||||
|
_currentCache = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for unit testing
|
||||||
|
internal PageActionInvokerCacheEntry GetOrAddCacheEntry(
|
||||||
|
ActionInvokerProviderContext context,
|
||||||
|
PageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
var cache = CurrentCache;
|
||||||
|
|
||||||
|
PageActionInvokerCacheEntry cacheEntry;
|
||||||
|
if (!cache.Entries.TryGetValue(actionDescriptor, out cacheEntry))
|
||||||
|
{
|
||||||
|
cacheEntry = CreateCacheEntry(context);
|
||||||
|
cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PageActionInvokerCacheEntry CreateCacheEntry(ActionInvokerProviderContext context)
|
||||||
|
{
|
||||||
|
var actionDescriptor = (PageActionDescriptor)context.ActionContext.ActionDescriptor;
|
||||||
|
var compiledType = _loader.Load(actionDescriptor).GetTypeInfo();
|
||||||
|
var modelType = compiledType.GetProperty(ModelPropertyName)?.PropertyType.GetTypeInfo();
|
||||||
|
|
||||||
|
var compiledActionDescriptor = new CompiledPageActionDescriptor(actionDescriptor)
|
||||||
|
{
|
||||||
|
ModelTypeInfo = modelType,
|
||||||
|
PageTypeInfo = compiledType,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new PageActionInvokerCacheEntry(
|
||||||
|
compiledActionDescriptor,
|
||||||
|
_pageFactoryProvider.CreatePageFactory(compiledActionDescriptor),
|
||||||
|
_pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor),
|
||||||
|
PageFilterFactoryProvider.GetFilterFactory(_filterProviders, context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InnerCache
|
||||||
|
{
|
||||||
|
public InnerCache(int version)
|
||||||
|
{
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConcurrentDictionary<ActionDescriptor, PageActionInvokerCacheEntry> Entries { get; } =
|
||||||
|
new ConcurrentDictionary<ActionDescriptor, PageActionInvokerCacheEntry>();
|
||||||
|
|
||||||
|
public int Version { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
// 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.Threading;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public static class PageFilterFactoryProvider
|
||||||
|
{
|
||||||
|
public static Func<ActionContext, IFilterMetadata[]> GetFilterFactory(
|
||||||
|
IFilterProvider[] filterProviders,
|
||||||
|
ActionInvokerProviderContext actionInvokerProviderContext)
|
||||||
|
{
|
||||||
|
if (filterProviders == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(filterProviders));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionInvokerProviderContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(actionInvokerProviderContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionDescriptor = actionInvokerProviderContext.ActionContext.ActionDescriptor;
|
||||||
|
|
||||||
|
// staticFilterItems is captured as part of the closure.We evaluate it once to determine
|
||||||
|
// which of the staticFilters are reusable.
|
||||||
|
var staticFilterItems = new FilterItem[actionDescriptor.FilterDescriptors.Count];
|
||||||
|
for (var i = 0; i < actionDescriptor.FilterDescriptors.Count; i++)
|
||||||
|
{
|
||||||
|
staticFilterItems[i] = new FilterItem(actionDescriptor.FilterDescriptors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var internalFilterFactory = GetFilterFactory(filterProviders);
|
||||||
|
var allFilterItems = new List<FilterItem>(staticFilterItems);
|
||||||
|
|
||||||
|
// Execute the filter factory to determine which static filters can be cached.
|
||||||
|
var filters = internalFilterFactory(allFilterItems, actionInvokerProviderContext.ActionContext);
|
||||||
|
|
||||||
|
// Cache the filter items based on the following criteria
|
||||||
|
// 1. Are created statically (ex: via filter attributes, added to global filter list etc.)
|
||||||
|
// 2. Are re-usable
|
||||||
|
for (var i = 0; i < staticFilterItems.Length; i++)
|
||||||
|
{
|
||||||
|
var item = staticFilterItems[i];
|
||||||
|
if (!item.IsReusable)
|
||||||
|
{
|
||||||
|
item.Filter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (actionContext) =>
|
||||||
|
{
|
||||||
|
// Reuse the filters cached outside the closure for the very first run. This avoids re-running
|
||||||
|
// filters twice the first time we cache for a page.
|
||||||
|
var cachedFilters = Interlocked.Exchange(ref filters, null);
|
||||||
|
if (cachedFilters != null)
|
||||||
|
{
|
||||||
|
return cachedFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a separate collection as we want to hold onto the statically defined filter items
|
||||||
|
// in order to cache them
|
||||||
|
var filterItems = new List<FilterItem>(staticFilterItems.Length);
|
||||||
|
for (var i = 0; i < staticFilterItems.Length; i++)
|
||||||
|
{
|
||||||
|
// Deep copy the cached filter items as filter providers could modify them
|
||||||
|
var filterItem = staticFilterItems[i];
|
||||||
|
filterItems.Add(new FilterItem(filterItem.Descriptor)
|
||||||
|
{
|
||||||
|
Filter = filterItem.Filter,
|
||||||
|
IsReusable = filterItem.IsReusable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return internalFilterFactory(filterItems, actionContext);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Func<IList<FilterItem>, ActionContext, IFilterMetadata[]> GetFilterFactory(
|
||||||
|
IFilterProvider[] filterProviders)
|
||||||
|
{
|
||||||
|
return (filterItems, actionContext) =>
|
||||||
|
{
|
||||||
|
// Execute providers
|
||||||
|
var filterContext = new FilterProviderContext(actionContext, filterItems);
|
||||||
|
|
||||||
|
for (var i = 0; i < filterProviders.Length; i++)
|
||||||
|
{
|
||||||
|
filterProviders[i].OnProvidersExecuting(filterContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = filterProviders.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
filterProviders[i].OnProvidersExecuted(filterContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract filter instances from statically defined filters and filter providers
|
||||||
|
var count = 0;
|
||||||
|
for (var i = 0; i < filterItems.Count; i++)
|
||||||
|
{
|
||||||
|
if (filterItems[i].Filter != null)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return EmptyArray<IFilterMetadata>.Instance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var filters = new IFilterMetadata[count];
|
||||||
|
var filterIndex = 0;
|
||||||
|
for (int i = 0; i < filterItems.Count; i++)
|
||||||
|
{
|
||||||
|
var filter = filterItems[i].Filter;
|
||||||
|
if (filter != null)
|
||||||
|
{
|
||||||
|
filters[filterIndex++] = filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||||
|
<DisabledCustomTools>.resx</DisabledCustomTools>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A base class for a Razor page.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Page : IRazorPage
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IHtmlContent BodyContent { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsLayoutBeingRendered { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Layout { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, RenderAsyncDelegate> PreviousSectionWriters { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, RenderAsyncDelegate> SectionWriters { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="PageContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
public PageContext PageContext { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ViewContext ViewContext { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void EnsureRenderedBodyOrSections()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract Task ExecuteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,13 +4,36 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
{
|
{
|
||||||
[DebuggerDisplay("{" + nameof(ViewEnginePath) + "}")]
|
[DebuggerDisplay("{" + nameof(ViewEnginePath) + "}")]
|
||||||
public class PageActionDescriptor : ActionDescriptor
|
public class PageActionDescriptor : ActionDescriptor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="PageActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public PageActionDescriptor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A copy constructor for <see cref="PageActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The <see cref="PageActionDescriptor"/> to copy from.</param>
|
||||||
|
public PageActionDescriptor(PageActionDescriptor other)
|
||||||
|
{
|
||||||
|
RelativePath = other.RelativePath;
|
||||||
|
ViewEnginePath = other.ViewEnginePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the application root relative path for the page.
|
||||||
|
/// </summary>
|
||||||
public string RelativePath { get; set; }
|
public string RelativePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path relative to the base path for page discovery.
|
||||||
|
/// </summary>
|
||||||
public string ViewEnginePath { get; set; }
|
public string ViewEnginePath { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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.IO;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context associated with the current request for a Razor page.
|
||||||
|
/// </summary>
|
||||||
|
public class PageContext : ViewContext
|
||||||
|
{
|
||||||
|
private CompiledPageActionDescriptor _actionDescriptor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an empty <see cref="ViewContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The default constructor is provided for unit test purposes only.
|
||||||
|
/// </remarks>
|
||||||
|
public PageContext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageContext(
|
||||||
|
ActionContext actionContext,
|
||||||
|
ViewDataDictionary viewData,
|
||||||
|
ITempDataDictionary tempDataDictionary,
|
||||||
|
HtmlHelperOptions htmlHelperOptions)
|
||||||
|
: base(actionContext, NullView.Instance, viewData, tempDataDictionary, TextWriter.Null, htmlHelperOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="PageActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public new CompiledPageActionDescriptor ActionDescriptor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _actionDescriptor;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_actionDescriptor = value;
|
||||||
|
base.ActionDescriptor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Reflection;
|
||||||
using System.Resources;
|
using System.Resources;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||||
[assembly: NeutralResourcesLanguage("en-us")]
|
[assembly: NeutralResourcesLanguage("en-us")]
|
||||||
[assembly: AssemblyCompany("Microsoft Corporation.")]
|
[assembly: AssemblyCompany("Microsoft Corporation.")]
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The route for the page at '{0}' cannot start with / or ~/. Pages do not support overriding the file path of the page.
|
/// The @page directive for the Razor page at {0} cannot override the relative path prefix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatPageActionDescriptorProvider_RouteTemplateCannotBeOverrideable(object p0)
|
internal static string FormatPageActionDescriptorProvider_RouteTemplateCannotBeOverrideable(object p0)
|
||||||
{
|
{
|
||||||
|
|
@ -42,6 +42,54 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
return GetString("RazorProject_PathMustStartWithForwardSlash");
|
return GetString("RazorProject_PathMustStartWithForwardSlash");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The '{0}' property of '{1}' must not be null.
|
||||||
|
/// </summary>
|
||||||
|
internal static string PropertyOfTypeCannotBeNull
|
||||||
|
{
|
||||||
|
get { return GetString("PropertyOfTypeCannotBeNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The '{0}' property of '{1}' must not be null.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatPropertyOfTypeCannotBeNull(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyOfTypeCannotBeNull"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Page created by '{0}' must be an instance of '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ActivatedInstance_MustBeAnInstanceOf
|
||||||
|
{
|
||||||
|
get { return GetString("ActivatedInstance_MustBeAnInstanceOf"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Page created by '{0}' must be an instance of '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatActivatedInstance_MustBeAnInstanceOf(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ActivatedInstance_MustBeAnInstanceOf"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Razor page type '{0}' does not have a parameterless constructor.
|
||||||
|
/// </summary>
|
||||||
|
internal static string PageActivator_TypeDoesNotHaveParameterlessConstructor
|
||||||
|
{
|
||||||
|
get { return GetString("PageActivator_TypeDoesNotHaveParameterlessConstructor"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Razor page type '{0}' does not have a parameterless constructor.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatPageActivator_TypeDoesNotHaveParameterlessConstructor(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("PageActivator_TypeDoesNotHaveParameterlessConstructor"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -123,4 +123,10 @@
|
||||||
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
||||||
<value>Path must begin with a forward slash '/'.</value>
|
<value>Path must begin with a forward slash '/'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
|
||||||
|
<value>The '{0}' property of '{1}' must not be null.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActivatedInstance_MustBeAnInstanceOf" xml:space="preserve">
|
||||||
|
<value>Page created by '{0}' must be an instance of '{1}'.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"description": "ASP.NET Core MVC Razor Pages.",
|
"description": "ASP.NET Core MVC Razor Pages.",
|
||||||
"version": "1.0.0-*",
|
"version": "1.0.0-*",
|
||||||
"packOptions": {
|
"packOptions": {
|
||||||
|
|
@ -22,10 +22,18 @@
|
||||||
"xmlDoc": true
|
"xmlDoc": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNetCore.Mvc.ViewFeatures": {
|
"Microsoft.AspNetCore.Mvc.Razor": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Razor.Evolution": "1.0.0-*",
|
"Microsoft.AspNetCore.Razor.Evolution": "1.0.0-*",
|
||||||
|
"Microsoft.Extensions.PropertyActivator.Sources": {
|
||||||
|
"version": "1.2.0-*",
|
||||||
|
"type": "build"
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.PropertyHelper.Sources": {
|
||||||
|
"version": "1.2.0-*",
|
||||||
|
"type": "build"
|
||||||
|
},
|
||||||
"NETStandard.Library": "1.6.2-*"
|
"NETStandard.Library": "1.6.2-*"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class DefaultPageActivatorTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateActivator_ThrowsIfPageTypeInfoIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new CompiledPageActionDescriptor();
|
||||||
|
var activator = new DefaultPageActivator();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
ExceptionAssert.ThrowsArgument(
|
||||||
|
() => activator.CreateActivator(descriptor),
|
||||||
|
"actionDescriptor",
|
||||||
|
"The 'PageTypeInfo' property of 'actionDescriptor' must not be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(TestPage))]
|
||||||
|
[InlineData(typeof(PageWithMultipleConstructors))]
|
||||||
|
public void CreateActivator_ReturnsFactoryForPage(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pageContext = new PageContext();
|
||||||
|
var descriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = type.GetTypeInfo(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var activator = new DefaultPageActivator();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var factory = activator.CreateActivator(descriptor);
|
||||||
|
var instance = factory(pageContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(instance);
|
||||||
|
Assert.IsType(type, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateActivator_ThrowsIfTypeDoesNotHaveParameterlessConstructor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(PageWithoutParameterlessConstructor).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var pageContext = new PageContext();
|
||||||
|
var activator = new DefaultPageActivator();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentException>(() => activator.CreateActivator(descriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(TestPage))]
|
||||||
|
[InlineData(typeof(object))]
|
||||||
|
public void CreateReleaser_ReturnsNullForPagesThatDoNotImplementDisposable(Type pageType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new PageContext();
|
||||||
|
var activator = new DefaultPageActivator();
|
||||||
|
var page = new TestPage();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var releaser = activator.CreateReleaser(new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = pageType.GetTypeInfo()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(releaser);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateReleaser_CreatesDelegateThatDisposesDisposableTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new PageContext();
|
||||||
|
var activator = new DefaultPageActivator();
|
||||||
|
var page = new DisposablePage();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var disposer = activator.CreateReleaser(new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = page.GetType().GetTypeInfo()
|
||||||
|
});
|
||||||
|
Assert.NotNull(disposer);
|
||||||
|
disposer(context, page);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(page.Disposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPage : Page
|
||||||
|
{
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PageWithMultipleConstructors : Page
|
||||||
|
{
|
||||||
|
public PageWithMultipleConstructors(int x)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageWithMultipleConstructors()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PageWithoutParameterlessConstructor : Page
|
||||||
|
{
|
||||||
|
public PageWithoutParameterlessConstructor(ILogger logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DisposablePage : TestPage, IDisposable
|
||||||
|
{
|
||||||
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class DefaultPageFactoryProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreatePage_ThrowsIfActivatedInstanceIsNotAnInstanceOfRazorPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(object).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var pageActivator = CreateActivator();
|
||||||
|
var factoryProvider = CreatePageFactory();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => factoryProvider.CreatePageFactory(descriptor));
|
||||||
|
Assert.Equal(
|
||||||
|
$"Page created by '{pageActivator.GetType()}' must be an instance of '{typeof(Page)}'.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PageFactorySetsPageContext()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var pageContext = new PageContext();
|
||||||
|
var factoryProvider = CreatePageFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var factory = factoryProvider.CreatePageFactory(descriptor);
|
||||||
|
var instance = factory(pageContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var testPage = Assert.IsType<TestPage>(instance);
|
||||||
|
Assert.Same(pageContext, testPage.PageContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PageFactorySetsPropertiesWithRazorInject()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pageContext = new PageContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
||||||
|
var urlHelper = Mock.Of<IUrlHelper>();
|
||||||
|
urlHelperFactory.Setup(f => f.GetUrlHelper(pageContext))
|
||||||
|
.Returns(urlHelper)
|
||||||
|
.Verifiable();
|
||||||
|
var htmlEncoder = HtmlEncoder.Create();
|
||||||
|
|
||||||
|
var factoryProvider = CreatePageFactory(
|
||||||
|
urlHelperFactory: urlHelperFactory.Object,
|
||||||
|
htmlEncoder: htmlEncoder);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
|
||||||
|
var instance = factory(pageContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var testPage = Assert.IsType<TestPage>(instance);
|
||||||
|
Assert.Same(urlHelper, testPage.UrlHelper);
|
||||||
|
Assert.Same(htmlEncoder, testPage.HtmlEncoder);
|
||||||
|
Assert.NotNull(testPage.ViewData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PageFactorySetViewDataWithModelTypeWhenNotNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pageContext = new PageContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(ViewDataTestPage).GetTypeInfo(),
|
||||||
|
ModelTypeInfo = typeof(ViewDataTestPageModel).GetTypeInfo(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var factoryProvider = CreatePageFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
|
||||||
|
var instance = factory(pageContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var testPage = Assert.IsType<ViewDataTestPage>(instance);
|
||||||
|
Assert.NotNull(testPage.ViewData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PageFactorySetsNonGenericViewDataDictionary()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pageContext = new PageContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(NonGenericViewDataTestPage).GetTypeInfo()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var factoryProvider = CreatePageFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
|
||||||
|
var instance = factory(pageContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var testPage = Assert.IsType<NonGenericViewDataTestPage>(instance);
|
||||||
|
Assert.NotNull(testPage.ViewData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PageFactorySetsNestedVidewDataDictionaryWhenContextHasANonNullDictionary()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelMetadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
var pageContext = new PageContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo()
|
||||||
|
},
|
||||||
|
ViewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary())
|
||||||
|
{
|
||||||
|
{ "test-key", "test-value" },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var factoryProvider = CreatePageFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
|
||||||
|
var instance = factory(pageContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var testPage = Assert.IsType<TestPage>(instance);
|
||||||
|
Assert.NotNull(testPage.ViewData);
|
||||||
|
Assert.Equal("test-value", testPage.ViewData["test-key"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PageFactoryDoesNotBindPropertiesWithNoRazorInjectAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var serviceProvider = new ServiceCollection()
|
||||||
|
.AddSingleton<ILogger>(NullLogger.Instance)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
var pageContext = new PageContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(PropertiesWithoutRazorInject).GetTypeInfo()
|
||||||
|
},
|
||||||
|
HttpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
RequestServices = serviceProvider,
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var factoryProvider = CreatePageFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
|
||||||
|
var instance = factory(pageContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var testPage = Assert.IsType<PropertiesWithoutRazorInject>(instance);
|
||||||
|
Assert.Null(testPage.DiagnosticSourceWithoutInject);
|
||||||
|
Assert.NotNull(testPage.DiagnosticSourceWithInject);
|
||||||
|
|
||||||
|
Assert.Null(testPage.LoggerWithoutInject);
|
||||||
|
Assert.NotNull(testPage.LoggerWithInject);
|
||||||
|
|
||||||
|
Assert.Null(testPage.ModelExpressionProviderWithoutInject);
|
||||||
|
Assert.NotNull(testPage.ModelExpressionProviderWithInject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DefaultPageFactory CreatePageFactory(
|
||||||
|
IPageActivatorProvider pageActivator = null,
|
||||||
|
IModelMetadataProvider provider = null,
|
||||||
|
IUrlHelperFactory urlHelperFactory = null,
|
||||||
|
IJsonHelper jsonHelper = null,
|
||||||
|
DiagnosticSource diagnosticSource = null,
|
||||||
|
HtmlEncoder htmlEncoder = null,
|
||||||
|
IModelExpressionProvider modelExpressionProvider = null)
|
||||||
|
{
|
||||||
|
return new DefaultPageFactory(
|
||||||
|
pageActivator ?? CreateActivator(),
|
||||||
|
provider ?? Mock.Of<IModelMetadataProvider>(),
|
||||||
|
urlHelperFactory ?? Mock.Of<IUrlHelperFactory>(),
|
||||||
|
jsonHelper ?? Mock.Of<IJsonHelper>(),
|
||||||
|
diagnosticSource ?? new DiagnosticListener("Microsoft.AspNetCore.Mvc.RazorPages"),
|
||||||
|
htmlEncoder ?? HtmlEncoder.Default,
|
||||||
|
modelExpressionProvider ?? Mock.Of<IModelExpressionProvider>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IPageActivatorProvider CreateActivator()
|
||||||
|
{
|
||||||
|
var activator = new Mock<IPageActivatorProvider>();
|
||||||
|
activator.Setup(a => a.CreateActivator(It.IsAny<CompiledPageActionDescriptor>()))
|
||||||
|
.Returns((CompiledPageActionDescriptor descriptor) =>
|
||||||
|
{
|
||||||
|
return (context) => Activator.CreateInstance(descriptor.PageTypeInfo.AsType());
|
||||||
|
});
|
||||||
|
return activator.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPage : Page
|
||||||
|
{
|
||||||
|
[RazorInject]
|
||||||
|
public IUrlHelper UrlHelper { get; set; }
|
||||||
|
|
||||||
|
[RazorInject]
|
||||||
|
public HtmlEncoder HtmlEncoder { get; set; }
|
||||||
|
|
||||||
|
[RazorInject]
|
||||||
|
public ViewDataDictionary<TestPage> ViewData { get; set; }
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonGenericViewDataTestPage : Page
|
||||||
|
{
|
||||||
|
[RazorInject]
|
||||||
|
public ViewDataDictionary ViewData { get; set; }
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewDataTestPage : Page
|
||||||
|
{
|
||||||
|
[RazorInject]
|
||||||
|
public ViewDataDictionary<ViewDataTestPageModel> ViewData { get; set; }
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewDataTestPageModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PropertiesWithoutRazorInject : Page
|
||||||
|
{
|
||||||
|
public IModelExpressionProvider ModelExpressionProviderWithoutInject { get; set; }
|
||||||
|
|
||||||
|
[RazorInject]
|
||||||
|
public IModelExpressionProvider ModelExpressionProviderWithInject { get; set; }
|
||||||
|
|
||||||
|
public DiagnosticSource DiagnosticSourceWithoutInject { get; set; }
|
||||||
|
|
||||||
|
[RazorInject]
|
||||||
|
public DiagnosticSource DiagnosticSourceWithInject { get; set; }
|
||||||
|
|
||||||
|
public ILogger LoggerWithoutInject { get; set; }
|
||||||
|
|
||||||
|
[RazorInject]
|
||||||
|
public ILogger LoggerWithInject { get; set; }
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
// 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.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class PageInvokerProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetOrAddCacheEntry_PopulatesCacheEntry()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new PageActionDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = "Path1",
|
||||||
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
|
};
|
||||||
|
Func<PageContext, object> factory = _ => null;
|
||||||
|
Action<PageContext, object> releaser = (_, __) => { };
|
||||||
|
var loader = new Mock<IPageLoader>();
|
||||||
|
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||||
|
.Returns(typeof(object));
|
||||||
|
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
|
||||||
|
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||||
|
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
||||||
|
var factoryProvider = new Mock<IPageFactoryProvider>();
|
||||||
|
factoryProvider.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
|
||||||
|
.Returns(factory);
|
||||||
|
factoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny<CompiledPageActionDescriptor>()))
|
||||||
|
.Returns(releaser);
|
||||||
|
|
||||||
|
var invokerProvider = new PageActionInvokerProvider(
|
||||||
|
loader.Object,
|
||||||
|
factoryProvider.Object,
|
||||||
|
actionDescriptorProvider.Object,
|
||||||
|
new IFilterProvider[0]);
|
||||||
|
var context = new ActionInvokerProviderContext(new ActionContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = descriptor,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var entry = invokerProvider.GetOrAddCacheEntry(context, descriptor);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
var compiledPageActionDescriptor = Assert.IsType<CompiledPageActionDescriptor>(entry.ActionDescriptor);
|
||||||
|
Assert.Equal(descriptor.RelativePath, compiledPageActionDescriptor.RelativePath);
|
||||||
|
Assert.Same(factory, entry.PageFactory);
|
||||||
|
Assert.Same(releaser, entry.ReleasePage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetOrAddCacheEntry_CachesEntries()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new PageActionDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = "Path1",
|
||||||
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
|
};
|
||||||
|
var loader = new Mock<IPageLoader>();
|
||||||
|
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||||
|
.Returns(typeof(object));
|
||||||
|
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
|
||||||
|
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||||
|
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
||||||
|
|
||||||
|
var invokerProvider = new PageActionInvokerProvider(
|
||||||
|
loader.Object,
|
||||||
|
Mock.Of<IPageFactoryProvider>(),
|
||||||
|
actionDescriptorProvider.Object,
|
||||||
|
new IFilterProvider[0]);
|
||||||
|
var context = new ActionInvokerProviderContext(new ActionContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = descriptor,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var entry1 = invokerProvider.GetOrAddCacheEntry(context, descriptor);
|
||||||
|
var entry2 = invokerProvider.GetOrAddCacheEntry(context, descriptor);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(entry1, entry2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetOrAddCacheEntry_UpdatesEntriesWhenActionDescriptorProviderCollectionIsUpdated()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new PageActionDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = "Path1",
|
||||||
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
|
};
|
||||||
|
var descriptorCollection1 = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
|
||||||
|
var descriptorCollection2 = new ActionDescriptorCollection(new[] { descriptor }, version: 2);
|
||||||
|
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||||
|
actionDescriptorProvider.SetupSequence(p => p.ActionDescriptors)
|
||||||
|
.Returns(descriptorCollection1)
|
||||||
|
.Returns(descriptorCollection2);
|
||||||
|
|
||||||
|
var loader = new Mock<IPageLoader>();
|
||||||
|
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||||
|
.Returns(typeof(object));
|
||||||
|
var invokerProvider = new PageActionInvokerProvider(
|
||||||
|
loader.Object,
|
||||||
|
Mock.Of<IPageFactoryProvider>(),
|
||||||
|
actionDescriptorProvider.Object,
|
||||||
|
new IFilterProvider[0]);
|
||||||
|
var context = new ActionInvokerProviderContext(new ActionContext
|
||||||
|
{
|
||||||
|
ActionDescriptor = descriptor,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var entry1 = invokerProvider.GetOrAddCacheEntry(context, descriptor);
|
||||||
|
var entry2 = invokerProvider.GetOrAddCacheEntry(context, descriptor);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(entry1, entry2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class PageFilterFactoryProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void FilterFactory_ReturnsNoFilters_IfNoFiltersAreSpecified()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filterProviders = new IFilterProvider[0];
|
||||||
|
var actionInvokerProviderContext = GetInvokerContext();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var filterFactory = PageFilterFactoryProvider.GetFilterFactory(
|
||||||
|
filterProviders,
|
||||||
|
actionInvokerProviderContext);
|
||||||
|
var filters1 = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
var filters2 = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(filters1);
|
||||||
|
Assert.Empty(filters2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterFactory_ReturnsNoFilters_IfAllFiltersAreRemoved()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filterProvider = new TestFilterProvider(
|
||||||
|
context => context.Results.Clear());
|
||||||
|
var filter = new FilterDescriptor(new TypeFilterAttribute(typeof(object)), FilterScope.Global);
|
||||||
|
var actionInvokerProviderContext = GetInvokerContext(filter);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var filterFactory = PageFilterFactoryProvider.GetFilterFactory(
|
||||||
|
new[] { filterProvider },
|
||||||
|
actionInvokerProviderContext);
|
||||||
|
var filters1 = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
var filters2 = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(filters1);
|
||||||
|
Assert.Empty(filters2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterFactory_CachesAllFilters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var staticFilter1 = new TestFilter();
|
||||||
|
var staticFilter2 = new TestFilter();
|
||||||
|
var actionInvokerProviderContext = GetInvokerContext(new[]
|
||||||
|
{
|
||||||
|
new FilterDescriptor(staticFilter1, FilterScope.Action),
|
||||||
|
new FilterDescriptor(staticFilter2, FilterScope.Action),
|
||||||
|
});
|
||||||
|
var filterProviders = new[] { new DefaultFilterProvider() };
|
||||||
|
|
||||||
|
// Act - 1
|
||||||
|
var filterFactory = PageFilterFactoryProvider.GetFilterFactory(
|
||||||
|
filterProviders,
|
||||||
|
actionInvokerProviderContext);
|
||||||
|
|
||||||
|
var request1Filters = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.Collection(
|
||||||
|
request1Filters,
|
||||||
|
f => Assert.Same(staticFilter1, f),
|
||||||
|
f => Assert.Same(staticFilter2, f));
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
var request2Filters = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.Collection(
|
||||||
|
request2Filters,
|
||||||
|
f => Assert.Same(staticFilter1, f),
|
||||||
|
f => Assert.Same(staticFilter2, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterFactory_CachesFilterFromFactory()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var staticFilter = new TestFilter();
|
||||||
|
var actionInvokerProviderContext = GetInvokerContext(new[]
|
||||||
|
{
|
||||||
|
new FilterDescriptor(new TestFilterFactory() { IsReusable = true }, FilterScope.Action),
|
||||||
|
new FilterDescriptor(staticFilter, FilterScope.Action),
|
||||||
|
});
|
||||||
|
var filterProviders = new[] { new DefaultFilterProvider() };
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var filterFactory = PageFilterFactoryProvider.GetFilterFactory(
|
||||||
|
filterProviders,
|
||||||
|
actionInvokerProviderContext);
|
||||||
|
|
||||||
|
var filters = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
Assert.Equal(2, filters.Length);
|
||||||
|
var cachedFactoryCreatedFilter = Assert.IsType<TestFilter>(filters[0]); // Created by factory
|
||||||
|
Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
|
||||||
|
|
||||||
|
for (var i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
filters = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
|
||||||
|
var currentFactoryCreatedFilter = filters[0];
|
||||||
|
Assert.Same(currentFactoryCreatedFilter, cachedFactoryCreatedFilter); // Cached
|
||||||
|
Assert.Same(staticFilter, filters[1]); // Cached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterFactory_DoesNotCacheFiltersWithIsReusableFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var staticFilter = new TestFilter();
|
||||||
|
var actionInvokerProviderContext = GetInvokerContext(new[]
|
||||||
|
{
|
||||||
|
new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action),
|
||||||
|
new FilterDescriptor(staticFilter, FilterScope.Action),
|
||||||
|
});
|
||||||
|
var filterProviders = new[] { new DefaultFilterProvider() };
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var filterFactory = PageFilterFactoryProvider.GetFilterFactory(
|
||||||
|
filterProviders,
|
||||||
|
actionInvokerProviderContext);
|
||||||
|
IFilterMetadata previousFactoryCreatedFilter = null;
|
||||||
|
for (var i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
var filters = filterFactory(actionInvokerProviderContext.ActionContext);
|
||||||
|
|
||||||
|
var currentFactoryCreatedFilter = filters[0];
|
||||||
|
Assert.NotSame(currentFactoryCreatedFilter, previousFactoryCreatedFilter); // Never Cached
|
||||||
|
Assert.Same(staticFilter, filters[1]); // Cached
|
||||||
|
|
||||||
|
previousFactoryCreatedFilter = currentFactoryCreatedFilter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public void FilterFactory_FiltersAddedByFilterProviders_AreNeverCached(bool reusable)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var customFilterProvider = new TestFilterProvider(
|
||||||
|
providerExecuting: (providerContext) =>
|
||||||
|
{
|
||||||
|
var filter = new TestFilter(providerContext.ActionContext.HttpContext.Items["name"] as string);
|
||||||
|
providerContext.Results.Add(
|
||||||
|
new FilterItem(new FilterDescriptor(filter, FilterScope.Global), filter)
|
||||||
|
{
|
||||||
|
IsReusable = reusable
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var staticFilter = new TestFilter();
|
||||||
|
var actionInvokerProviderContext = GetInvokerContext(new[]
|
||||||
|
{
|
||||||
|
new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action),
|
||||||
|
new FilterDescriptor(staticFilter, FilterScope.Action),
|
||||||
|
});
|
||||||
|
var actionContext = actionInvokerProviderContext.ActionContext;
|
||||||
|
var filterProviders = new IFilterProvider[] { new DefaultFilterProvider(), customFilterProvider };
|
||||||
|
|
||||||
|
|
||||||
|
// Act - 1
|
||||||
|
actionContext.HttpContext.Items["name"] = "foo";
|
||||||
|
var filterFactory = PageFilterFactoryProvider.GetFilterFactory(
|
||||||
|
filterProviders,
|
||||||
|
actionInvokerProviderContext);
|
||||||
|
var filters = filterFactory(actionContext);
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.Equal(3, filters.Length);
|
||||||
|
var request1Filter1 = Assert.IsType<TestFilter>(filters[0]); // Created by factory
|
||||||
|
Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
|
||||||
|
var request1Filter3 = Assert.IsType<TestFilter>(filters[2]); // Created by custom filter provider
|
||||||
|
Assert.Equal("foo", request1Filter3.Data);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
actionContext.HttpContext.Items["name"] = "bar";
|
||||||
|
filters = filterFactory(actionContext);
|
||||||
|
|
||||||
|
// Assert -2
|
||||||
|
Assert.Equal(3, filters.Length);
|
||||||
|
var request2Filter1 = Assert.IsType<TestFilter>(filters[0]);
|
||||||
|
Assert.NotSame(request1Filter1, request2Filter1); // Created by factory
|
||||||
|
Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
|
||||||
|
var request2Filter3 = Assert.IsType<TestFilter>(filters[2]);
|
||||||
|
Assert.NotSame(request1Filter3, request2Filter3); // Created by custom filter provider again
|
||||||
|
Assert.Equal("bar", request2Filter3.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestFilter : IFilterMetadata
|
||||||
|
{
|
||||||
|
public TestFilter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestFilter(string data)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Data { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestFilterFactory : IFilterFactory
|
||||||
|
{
|
||||||
|
private TestFilter _testFilter;
|
||||||
|
|
||||||
|
public bool IsReusable { get; set; }
|
||||||
|
|
||||||
|
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
if (IsReusable)
|
||||||
|
{
|
||||||
|
if (_testFilter == null)
|
||||||
|
{
|
||||||
|
_testFilter = new TestFilter();
|
||||||
|
}
|
||||||
|
return _testFilter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new TestFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestFilterProvider : IFilterProvider
|
||||||
|
{
|
||||||
|
private readonly Action<FilterProviderContext> _providerExecuting;
|
||||||
|
private readonly Action<FilterProviderContext> _providerExecuted;
|
||||||
|
|
||||||
|
public TestFilterProvider(
|
||||||
|
Action<FilterProviderContext> providerExecuting,
|
||||||
|
Action<FilterProviderContext> providerExecuted = null,
|
||||||
|
int order = 0)
|
||||||
|
{
|
||||||
|
_providerExecuting = providerExecuting;
|
||||||
|
_providerExecuted = providerExecuted;
|
||||||
|
Order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Order { get; }
|
||||||
|
|
||||||
|
public void OnProvidersExecuting(FilterProviderContext context)
|
||||||
|
{
|
||||||
|
_providerExecuting?.Invoke(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnProvidersExecuted(FilterProviderContext context)
|
||||||
|
{
|
||||||
|
_providerExecuted?.Invoke(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionInvokerProviderContext GetInvokerContext(params FilterDescriptor[] filters)
|
||||||
|
{
|
||||||
|
var actionDescriptor = new PageActionDescriptor
|
||||||
|
{
|
||||||
|
FilterDescriptors = new List<FilterDescriptor>(filters ?? Enumerable.Empty<FilterDescriptor>())
|
||||||
|
};
|
||||||
|
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), actionDescriptor);
|
||||||
|
return new ActionInvokerProviderContext(actionContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"warningsAsErrors": true
|
"warningsAsErrors": true,
|
||||||
|
"keyFile": "../../tools/Key.snk"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotnet-test-xunit": "2.2.0-*",
|
"dotnet-test-xunit": "2.2.0-*",
|
||||||
|
|
@ -9,6 +10,7 @@
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
||||||
|
"Microsoft.Extensions.DependencyInjection": "1.2.0-*",
|
||||||
"Moq": "4.6.36-*",
|
"Moq": "4.6.36-*",
|
||||||
"xunit": "2.2.0-*"
|
"xunit": "2.2.0-*"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue