[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.Linq.Expressions;
using System.Reflection;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -29,13 +30,28 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private readonly ConcurrentDictionary<Type, PageActivationInfo> _activationInfo;
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>
/// Initializes a new instance of the <see cref="RazorPageActivator"/> class.
/// </summary>
public RazorPageActivator(IModelMetadataProvider metadataProvider)
public RazorPageActivator(
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
IJsonHelper jsonHelper,
DiagnosticSource diagnosticSource,
HtmlEncoder htmlEncoder)
{
_activationInfo = new ConcurrentDictionary<Type, PageActivationInfo>();
_metadataProvider = metadataProvider;
_urlHelperAccessor = context => urlHelperFactory.GetUrlHelper(context);
_jsonHelperAccessor = context => jsonHelper;
_diagnosticSourceAccessor = context => diagnosticSource;
_htmlEncoderAccessor = context => htmlEncoder;
}
/// <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
// 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 = context =>
{
var serviceProvider = context.HttpContext.RequestServices;
var factory = serviceProvider.GetRequiredService<IUrlHelperFactory>();
return factory.GetUrlHelper(context);
};
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
{

View File

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

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@ -14,10 +15,12 @@ using Microsoft.AspNetCore.Mvc.Internal;
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.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq;
using Xunit;
@ -66,7 +69,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
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 typeActivator = new TypeActivatorCache();
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.
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
@ -240,6 +241,7 @@ namespace Microsoft.AspNetCore.Mvc
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new ApplicationPartManager());
serviceCollection.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore.Mvc"));
serviceCollection.AddMvc();
serviceCollection
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()