[Perf] Avoid Service Provider lookups when activating common Singleton properties of a Razor Page

Fixes #4244
This commit is contained in:
mnltejaswini 2016-04-14 10:59:31 -07:00
parent 588abbc588
commit fa34f61d46
4 changed files with 104 additions and 36 deletions

View File

@ -6,6 +6,7 @@ using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
@ -29,13 +30,28 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private readonly ConcurrentDictionary<Type, PageActivationInfo> _activationInfo; private readonly ConcurrentDictionary<Type, PageActivationInfo> _activationInfo;
private readonly IModelMetadataProvider _metadataProvider; private readonly IModelMetadataProvider _metadataProvider;
// Value accessors for common singleton properties activated in a RazorPage.
private Func<ViewContext, object> _urlHelperAccessor;
private Func<ViewContext, object> _jsonHelperAccessor;
private Func<ViewContext, object> _diagnosticSourceAccessor;
private Func<ViewContext, object> _htmlEncoderAccessor;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RazorPageActivator"/> class. /// Initializes a new instance of the <see cref="RazorPageActivator"/> class.
/// </summary> /// </summary>
public RazorPageActivator(IModelMetadataProvider metadataProvider) public RazorPageActivator(
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
IJsonHelper jsonHelper,
DiagnosticSource diagnosticSource,
HtmlEncoder htmlEncoder)
{ {
_activationInfo = new ConcurrentDictionary<Type, PageActivationInfo>(); _activationInfo = new ConcurrentDictionary<Type, PageActivationInfo>();
_metadataProvider = metadataProvider; _metadataProvider = metadataProvider;
_urlHelperAccessor = context => urlHelperFactory.GetUrlHelper(context);
_jsonHelperAccessor = context => jsonHelper;
_diagnosticSourceAccessor = context => diagnosticSource;
_htmlEncoderAccessor = context => htmlEncoder;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -160,12 +176,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor
// W.r.t. specificity of above condition: Users are much more likely to inject their own // 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 // 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.) // that. But the second scenario is supported. (Note the class must implement ICanHasViewContext.)
valueAccessor = context => valueAccessor = _urlHelperAccessor;
{ }
var serviceProvider = context.HttpContext.RequestServices; else if (property.PropertyType == typeof(IJsonHelper))
var factory = serviceProvider.GetRequiredService<IUrlHelperFactory>(); {
return factory.GetUrlHelper(context); valueAccessor = _jsonHelperAccessor;
}; }
else if (property.PropertyType == typeof(DiagnosticSource))
{
valueAccessor = _diagnosticSourceAccessor;
}
else if (property.PropertyType == typeof(HtmlEncoder))
{
valueAccessor = _htmlEncoderAccessor;
} }
else else
{ {

View File

@ -4,10 +4,10 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
@ -29,19 +29,26 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public void Activate_ActivatesAndContextualizesPropertiesOnViews() public void Activate_ActivatesAndContextualizesPropertiesOnViews()
{ {
// Arrange // Arrange
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var urlHelperFactory = new UrlHelperFactory();
var jsonHelper = new JsonHelper(new JsonOutputFormatter());
var htmlEncoder = new HtmlTestEncoder();
var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
urlHelperFactory,
jsonHelper,
diagnosticSource,
htmlEncoder);
var instance = new TestRazorPage(); var instance = new TestRazorPage();
var myService = new MyService(); var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>(); var helper = Mock.Of<IHtmlHelper<object>>();
var htmlEncoder = new HtmlTestEncoder();
var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
var serviceProvider = new ServiceCollection() var serviceProvider = new ServiceCollection()
.AddSingleton(myService) .AddSingleton(myService)
.AddSingleton(helper) .AddSingleton(helper)
.AddSingleton<HtmlEncoder>(htmlEncoder)
.AddSingleton(new ExpressionTextCache()) .AddSingleton(new ExpressionTextCache())
.AddSingleton<DiagnosticSource>(diagnosticSource)
.BuildServiceProvider(); .BuildServiceProvider();
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {
@ -57,6 +64,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
TextWriter.Null, TextWriter.Null,
new HtmlHelperOptions()); new HtmlHelperOptions());
var urlHelper = urlHelperFactory.GetUrlHelper(viewContext);
// Act // Act
activator.Activate(instance, viewContext); activator.Activate(instance, viewContext);
@ -65,6 +74,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
Assert.Same(myService, instance.MyService); Assert.Same(myService, instance.MyService);
Assert.Same(viewContext, myService.ViewContext); Assert.Same(viewContext, myService.ViewContext);
Assert.Same(diagnosticSource, instance.DiagnosticSource); Assert.Same(diagnosticSource, instance.DiagnosticSource);
Assert.Same(htmlEncoder, instance.HtmlEncoder);
Assert.Same(jsonHelper, instance.Json);
Assert.Same(urlHelper, instance.Url);
Assert.Null(instance.MyService2); Assert.Null(instance.MyService2);
} }
@ -72,7 +84,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public void Activate_ThrowsIfTheViewDoesNotDeriveFromRazorViewOfT() public void Activate_ThrowsIfTheViewDoesNotDeriveFromRazorViewOfT()
{ {
// Arrange // Arrange
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
new UrlHelperFactory(),
new JsonHelper(new JsonOutputFormatter()),
new DiagnosticListener("Microsoft.AspNetCore"),
new HtmlTestEncoder());
var instance = new DoesNotDeriveFromRazorPageOfT(); var instance = new DoesNotDeriveFromRazorPageOfT();
var myService = new MyService(); var myService = new MyService();
@ -102,18 +120,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public void Activate_InstantiatesNewViewDataDictionaryType_IfTheTypeDoesNotMatch() public void Activate_InstantiatesNewViewDataDictionaryType_IfTheTypeDoesNotMatch()
{ {
// Arrange // Arrange
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
new UrlHelperFactory(),
new JsonHelper(new JsonOutputFormatter()),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder());
var instance = new TestRazorPage(); var instance = new TestRazorPage();
var myService = new MyService(); var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>(); var helper = Mock.Of<IHtmlHelper<object>>();
var htmlEncoder = new HtmlTestEncoder();
var serviceProvider = new ServiceCollection() var serviceProvider = new ServiceCollection()
.AddSingleton(myService) .AddSingleton(myService)
.AddSingleton(helper) .AddSingleton(helper)
.AddSingleton<HtmlEncoder>(htmlEncoder)
.AddSingleton(new ExpressionTextCache()) .AddSingleton(new ExpressionTextCache())
.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore.Mvc"))
.BuildServiceProvider(); .BuildServiceProvider();
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {
@ -144,17 +164,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public void Activate_UsesPassedInViewDataDictionaryInstance_IfPassedInTypeMatches() public void Activate_UsesPassedInViewDataDictionaryInstance_IfPassedInTypeMatches()
{ {
// Arrange // Arrange
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
new UrlHelperFactory(),
new JsonHelper(new JsonOutputFormatter()),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder());
var instance = new TestRazorPage(); var instance = new TestRazorPage();
var myService = new MyService(); var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>(); var helper = Mock.Of<IHtmlHelper<object>>();
var htmlEncoder = new HtmlTestEncoder();
var serviceProvider = new ServiceCollection() var serviceProvider = new ServiceCollection()
.AddSingleton(myService) .AddSingleton(myService)
.AddSingleton(helper) .AddSingleton(helper)
.AddSingleton<HtmlEncoder>(htmlEncoder)
.AddSingleton(new ExpressionTextCache()) .AddSingleton(new ExpressionTextCache())
.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore.Mvc"))
.BuildServiceProvider(); .BuildServiceProvider();
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {
@ -185,17 +207,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public void Activate_DeterminesModelTypeFromProperty() public void Activate_DeterminesModelTypeFromProperty()
{ {
// Arrange // Arrange
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
new UrlHelperFactory(),
new JsonHelper(new JsonOutputFormatter()),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder());
var instance = new DoesNotDeriveFromRazorPageOfTButHasModelProperty(); var instance = new DoesNotDeriveFromRazorPageOfTButHasModelProperty();
var myService = new MyService(); var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>(); var helper = Mock.Of<IHtmlHelper<object>>();
var htmlEncoder = new HtmlTestEncoder();
var serviceProvider = new ServiceCollection() var serviceProvider = new ServiceCollection()
.AddSingleton(myService) .AddSingleton(myService)
.AddSingleton(helper) .AddSingleton(helper)
.AddSingleton<HtmlEncoder>(htmlEncoder)
.AddSingleton(new ExpressionTextCache()) .AddSingleton(new ExpressionTextCache())
.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore.Mvc"))
.BuildServiceProvider(); .BuildServiceProvider();
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {
@ -223,14 +247,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public void Activate_Throws_WhenViewDataPropertyHasIncorrectType() public void Activate_Throws_WhenViewDataPropertyHasIncorrectType()
{ {
// Arrange // Arrange
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
new UrlHelperFactory(),
new JsonHelper(new JsonOutputFormatter()),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder());
var instance = new HasIncorrectViewDataPropertyType(); var instance = new HasIncorrectViewDataPropertyType();
var collection = new ServiceCollection(); var collection = new ServiceCollection();
collection collection.AddSingleton(new ExpressionTextCache());
.AddSingleton<HtmlEncoder>(new HtmlTestEncoder())
.AddSingleton(new ExpressionTextCache())
.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore.Mvc"));
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
{ {
RequestServices = collection.BuildServiceProvider(), RequestServices = collection.BuildServiceProvider(),
@ -253,15 +279,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public void Activate_CanGetUrlHelperFromDependencyInjection() public void Activate_CanGetUrlHelperFromDependencyInjection()
{ {
// Arrange // Arrange
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
new UrlHelperFactory(),
new JsonHelper(new JsonOutputFormatter()),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder());
var instance = new HasUnusualIUrlHelperProperty(); var instance = new HasUnusualIUrlHelperProperty();
// IUrlHelperFactory should not be used. But set it up to match a real configuration. // IUrlHelperFactory should not be used. But set it up to match a real configuration.
var collection = new ServiceCollection(); var collection = new ServiceCollection();
collection collection
.AddSingleton<IUrlHelperFactory, UrlHelperFactory>()
.AddSingleton<HtmlEncoder>(new HtmlTestEncoder())
.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore.Mvc"))
.AddSingleton(new ExpressionTextCache()) .AddSingleton(new ExpressionTextCache())
.AddSingleton<IUrlHelperWrapper, UrlHelperWrapper>(); .AddSingleton<IUrlHelperWrapper, UrlHelperWrapper>();
var httpContext = new DefaultHttpContext var httpContext = new DefaultHttpContext
@ -291,6 +319,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public MyService MyService { get; set; } public MyService MyService { get; set; }
public MyService MyService2 { get; set; } public MyService MyService2 { get; set; }
[RazorInject]
public IJsonHelper Json { get; set; }
[RazorInject]
public IUrlHelper Url { get; set; }
} }
private class TestRazorPage : TestPageBase<MyModel> private class TestRazorPage : TestPageBase<MyModel>

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -14,10 +15,12 @@ using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor.Internal; 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.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -66,7 +69,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private static TestRazorPage CreateTestRazorPage() private static TestRazorPage CreateTestRazorPage()
{ {
var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
new UrlHelperFactory(),
new JsonHelper(new Formatters.JsonOutputFormatter()),
new DiagnosticListener("Microsoft.AspNetCore"),
new HtmlTestEncoder());
var serviceProvider = new Mock<IServiceProvider>(); var serviceProvider = new Mock<IServiceProvider>();
var typeActivator = new TypeActivatorCache(); var typeActivator = new TypeActivatorCache();
var tagHelperActivator = new DefaultTagHelperActivator(typeActivator); var tagHelperActivator = new DefaultTagHelperActivator(typeActivator);

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
@ -240,6 +241,7 @@ namespace Microsoft.AspNetCore.Mvc
{ {
var serviceCollection = new ServiceCollection(); var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new ApplicationPartManager()); serviceCollection.AddSingleton(new ApplicationPartManager());
serviceCollection.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore.Mvc"));
serviceCollection.AddMvc(); serviceCollection.AddMvc();
serviceCollection serviceCollection
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>() .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()