334 lines
13 KiB
C#
334 lines
13 KiB
C#
// 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.Diagnostics;
|
|
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.Internal;
|
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
using Microsoft.AspNetCore.Mvc.Razor;
|
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
|
using Microsoft.AspNetCore.Razor.Language;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Extensions.Primitives;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|
{
|
|
public class PageActionInvokerProvider : IActionInvokerProvider
|
|
{
|
|
private const string ViewStartFileName = "_ViewStart.cshtml";
|
|
|
|
private readonly IPageLoader _loader;
|
|
private readonly IPageFactoryProvider _pageFactoryProvider;
|
|
private readonly IPageModelFactoryProvider _modelFactoryProvider;
|
|
private readonly IRazorPageFactoryProvider _razorPageFactoryProvider;
|
|
private readonly IActionDescriptorCollectionProvider _collectionProvider;
|
|
private readonly IFilterProvider[] _filterProviders;
|
|
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
|
private readonly ParameterBinder _parameterBinder;
|
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
|
private readonly ITempDataDictionaryFactory _tempDataFactory;
|
|
private readonly HtmlHelperOptions _htmlHelperOptions;
|
|
private readonly RazorPagesOptions _razorPagesOptions;
|
|
private readonly IPageHandlerMethodSelector _selector;
|
|
private readonly RazorProject _razorProject;
|
|
private readonly DiagnosticSource _diagnosticSource;
|
|
private readonly ILogger<PageActionInvoker> _logger;
|
|
private volatile InnerCache _currentCache;
|
|
|
|
public PageActionInvokerProvider(
|
|
IPageLoader loader,
|
|
IPageFactoryProvider pageFactoryProvider,
|
|
IPageModelFactoryProvider modelFactoryProvider,
|
|
IRazorPageFactoryProvider razorPageFactoryProvider,
|
|
IActionDescriptorCollectionProvider collectionProvider,
|
|
IEnumerable<IFilterProvider> filterProviders,
|
|
ParameterBinder parameterBinder,
|
|
IModelMetadataProvider modelMetadataProvider,
|
|
ITempDataDictionaryFactory tempDataFactory,
|
|
IOptions<MvcOptions> mvcOptions,
|
|
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
|
IOptions<RazorPagesOptions> razorPagesOptions,
|
|
IPageHandlerMethodSelector selector,
|
|
RazorProject razorProject,
|
|
DiagnosticSource diagnosticSource,
|
|
ILoggerFactory loggerFactory)
|
|
{
|
|
_loader = loader;
|
|
_pageFactoryProvider = pageFactoryProvider;
|
|
_modelFactoryProvider = modelFactoryProvider;
|
|
_razorPageFactoryProvider = razorPageFactoryProvider;
|
|
_collectionProvider = collectionProvider;
|
|
_filterProviders = filterProviders.ToArray();
|
|
_valueProviderFactories = mvcOptions.Value.ValueProviderFactories.ToArray();
|
|
_parameterBinder = parameterBinder;
|
|
_modelMetadataProvider = modelMetadataProvider;
|
|
_tempDataFactory = tempDataFactory;
|
|
_htmlHelperOptions = htmlHelperOptions.Value;
|
|
_razorPagesOptions = razorPagesOptions.Value;
|
|
_selector = selector;
|
|
_razorProject = razorProject;
|
|
_diagnosticSource = diagnosticSource;
|
|
_logger = loggerFactory.CreateLogger<PageActionInvoker>();
|
|
}
|
|
|
|
public int Order { get; } = -1000;
|
|
|
|
public void OnProvidersExecuting(ActionInvokerProviderContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
var actionContext = context.ActionContext;
|
|
var actionDescriptor = actionContext.ActionDescriptor as PageActionDescriptor;
|
|
if (actionDescriptor == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var cache = CurrentCache;
|
|
PageActionInvokerCacheEntry cacheEntry;
|
|
|
|
IFilterMetadata[] filters;
|
|
if (!cache.Entries.TryGetValue(actionDescriptor, out cacheEntry))
|
|
{
|
|
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, actionContext);
|
|
filters = filterFactoryResult.Filters;
|
|
cacheEntry = CreateCacheEntry(context, filterFactoryResult.CacheableFilters);
|
|
cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
|
|
}
|
|
else
|
|
{
|
|
filters = FilterFactory.CreateUncachedFilters(
|
|
_filterProviders,
|
|
actionContext,
|
|
cacheEntry.CacheableFilters);
|
|
}
|
|
|
|
context.Result = CreateActionInvoker(actionContext, cacheEntry, filters);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
private PageActionInvoker CreateActionInvoker(
|
|
ActionContext actionContext,
|
|
PageActionInvokerCacheEntry cacheEntry,
|
|
IFilterMetadata[] filters)
|
|
{
|
|
var tempData = _tempDataFactory.GetTempData(actionContext.HttpContext);
|
|
var pageContext = new PageContext(
|
|
actionContext,
|
|
new ViewDataDictionary(_modelMetadataProvider, actionContext.ModelState),
|
|
tempData,
|
|
_htmlHelperOptions);
|
|
|
|
pageContext.ActionDescriptor = cacheEntry.ActionDescriptor;
|
|
|
|
return new PageActionInvoker(
|
|
_selector,
|
|
_diagnosticSource,
|
|
_logger,
|
|
pageContext,
|
|
filters,
|
|
new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories),
|
|
cacheEntry);
|
|
}
|
|
|
|
private PageActionInvokerCacheEntry CreateCacheEntry(
|
|
ActionInvokerProviderContext context,
|
|
FilterItem[] cachedFilters)
|
|
{
|
|
var actionDescriptor = (PageActionDescriptor)context.ActionContext.ActionDescriptor;
|
|
var compiledActionDescriptor = _loader.Load(actionDescriptor);
|
|
|
|
var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor);
|
|
var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor);
|
|
var propertyBinder = PagePropertyBinderFactory.CreateBinder(
|
|
_parameterBinder,
|
|
_modelMetadataProvider,
|
|
compiledActionDescriptor);
|
|
|
|
Func<PageContext, object> modelFactory = null;
|
|
Action<PageContext, object> modelReleaser = null;
|
|
if (compiledActionDescriptor.ModelTypeInfo == null)
|
|
{
|
|
PopulateHandlerMethodDescriptors(compiledActionDescriptor.PageTypeInfo, compiledActionDescriptor);
|
|
}
|
|
else
|
|
{
|
|
PopulateHandlerMethodDescriptors(compiledActionDescriptor.ModelTypeInfo, compiledActionDescriptor);
|
|
|
|
modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor);
|
|
modelReleaser = _modelFactoryProvider.CreateModelDisposer(compiledActionDescriptor);
|
|
}
|
|
|
|
var viewStartFactories = GetViewStartFactories(compiledActionDescriptor);
|
|
|
|
return new PageActionInvokerCacheEntry(
|
|
compiledActionDescriptor,
|
|
pageFactory,
|
|
pageDisposer,
|
|
modelFactory,
|
|
modelReleaser,
|
|
propertyBinder,
|
|
viewStartFactories,
|
|
cachedFilters);
|
|
}
|
|
|
|
// Internal for testing.
|
|
internal List<Func<IRazorPage>> GetViewStartFactories(CompiledPageActionDescriptor descriptor)
|
|
{
|
|
var viewStartFactories = new List<Func<IRazorPage>>();
|
|
var viewStartItems = _razorProject.FindHierarchicalItems(
|
|
_razorPagesOptions.RootDirectory,
|
|
descriptor.RelativePath,
|
|
ViewStartFileName);
|
|
foreach (var item in viewStartItems)
|
|
{
|
|
if (item.Exists)
|
|
{
|
|
var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path);
|
|
if (factoryResult.Success)
|
|
{
|
|
viewStartFactories.Insert(0, factoryResult.RazorPageFactory);
|
|
}
|
|
}
|
|
}
|
|
|
|
return viewStartFactories;
|
|
}
|
|
|
|
// Internal for testing.
|
|
internal static void PopulateHandlerMethodDescriptors(TypeInfo type, CompiledPageActionDescriptor actionDescriptor)
|
|
{
|
|
var methods = type.GetMethods();
|
|
for (var i = 0; i < methods.Length; i++)
|
|
{
|
|
var method = methods[i];
|
|
if (!IsValidHandler(method))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string httpMethod;
|
|
int formActionStart;
|
|
|
|
if (method.Name.StartsWith("OnGet", StringComparison.Ordinal))
|
|
{
|
|
httpMethod = "GET";
|
|
formActionStart = "OnGet".Length;
|
|
}
|
|
else if (method.Name.StartsWith("OnPost", StringComparison.Ordinal))
|
|
{
|
|
httpMethod = "POST";
|
|
formActionStart = "OnPost".Length;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var formActionLength = method.Name.Length - formActionStart;
|
|
if (method.Name.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
formActionLength -= "Async".Length;
|
|
}
|
|
|
|
var formAction = new StringSegment(method.Name, formActionStart, formActionLength);
|
|
|
|
var handlerMethodDescriptor = new HandlerMethodDescriptor
|
|
{
|
|
Method = method,
|
|
Executor = ExecutorFactory.CreateExecutor(actionDescriptor, method),
|
|
FormAction = formAction,
|
|
HttpMethod = httpMethod,
|
|
};
|
|
|
|
actionDescriptor.HandlerMethods.Add(handlerMethodDescriptor);
|
|
}
|
|
}
|
|
|
|
private static bool IsValidHandler(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 class InnerCache
|
|
{
|
|
public InnerCache(int version)
|
|
{
|
|
Version = version;
|
|
}
|
|
|
|
public ConcurrentDictionary<ActionDescriptor, PageActionInvokerCacheEntry> Entries { get; } =
|
|
new ConcurrentDictionary<ActionDescriptor, PageActionInvokerCacheEntry>();
|
|
|
|
public int Version { get; }
|
|
}
|
|
}
|
|
}
|