From d2908e7b7b06433544ede8ce902ea7126d3bc9dc Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 24 Jun 2015 19:30:49 -0700 Subject: [PATCH] Tweak lifetimes for a few commonly resolved services This is some low hanging fruit for reducing the number of resolves we have per request. DefaultHtmlGenerator: Lots of these are created by RazorPage. It needs IUrlHelper, so scoped is the best we can do for now. For an example, on the front page of our sample, 48 of these are created for each request. 48! This takes it down to 1-per-request. JsonResult: Again, multiple created per request (12 for the sample). This class is totally stateless, so we can get down to 0-per-request. DefaultViewComponentInvokerFactory: Same story as JsonResult. DefaultObjectValidator/MvcMarkerService/DefaultFilterProvider: these are stateless and pretty much guaranteed to be used by every request. Getting them off the table. --- .../DefaultControllerFactory.cs | 3 --- .../DefaultFilterProvider.cs | 10 ++-------- .../MvcCoreServiceCollectionExtensions.cs | 14 ++++++++------ .../ViewComponents/DefaultViewComponentHelper.cs | 10 ++++++---- .../ViewComponents/DefaultViewComponentInvoker.cs | 6 ++---- .../DefaultViewComponentInvokerFactory.cs | 9 +-------- .../ViewComponents/IViewComponentInvokerFactory.cs | 4 +--- .../MvcServiceCollectionExtensions.cs | 12 ++++++------ .../DefaultFilterProviderTest.cs | 4 +--- 9 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs index 25f7eafea8..1172508a18 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs @@ -2,13 +2,10 @@ // 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.AspNet.Mvc.Core; -using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultFilterProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultFilterProvider.cs index ee3363507c..6d2c0ae413 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultFilterProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultFilterProvider.cs @@ -10,18 +10,11 @@ namespace Microsoft.AspNet.Mvc.Filters { public class DefaultFilterProvider : IFilterProvider { - public DefaultFilterProvider(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - } - public int Order { get { return DefaultOrder.DefaultFrameworkSortOrder; } } - protected IServiceProvider ServiceProvider { get; private set; } - /// public void OnProvidersExecuting([NotNull] FilterProviderContext context) { @@ -55,7 +48,8 @@ namespace Microsoft.AspNet.Mvc.Filters } else { - filterItem.Filter = filterFactory.CreateInstance(ServiceProvider); + var services = context.ActionContext.HttpContext.RequestServices; + filterItem.Filter = filterFactory.CreateInstance(services); if (filterItem.Filter == null) { diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcCoreServiceCollectionExtensions.cs index d32fac2abc..2db49214df 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcCoreServiceCollectionExtensions.cs @@ -88,13 +88,15 @@ namespace Microsoft.Framework.DependencyInjection // // Action Invoker // - // This accesses per-request services + // These two access per-request services services.TryAddTransient(); - services.TryAddTransient(); services.TryAddEnumerable( ServiceDescriptor.Transient()); + + // These are stateless + services.TryAddSingleton(); services.TryAddEnumerable( - ServiceDescriptor.Transient()); + ServiceDescriptor.Singleton()); // // ModelBinding, Validation and Formatting @@ -106,7 +108,7 @@ namespace Microsoft.Framework.DependencyInjection var options = serviceProvider.GetRequiredService>().Options; return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders); })); - services.TryAdd(ServiceDescriptor.Transient(serviceProvider => + services.TryAdd(ServiceDescriptor.Singleton(serviceProvider => { var options = serviceProvider.GetRequiredService>().Options; var modelMetadataProvider = serviceProvider.GetRequiredService(); @@ -125,7 +127,7 @@ namespace Microsoft.Framework.DependencyInjection // // Random Infrastructure // - services.TryAddTransient(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddScoped(typeof(IScopedInstance<>), typeof(ScopedInstance<>)); services.TryAddScoped(); @@ -141,4 +143,4 @@ namespace Microsoft.Framework.DependencyInjection ServiceDescriptor.Transient, MvcRouteOptionsSetup>()); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentHelper.cs b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentHelper.cs index 7ef13e5b0d..79986ecbac 100644 --- a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentHelper.cs @@ -132,14 +132,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents [NotNull] ViewComponentDescriptor descriptor, object[] arguments) { - var invoker = _invokerFactory.CreateInstance(descriptor, arguments); + var context = new ViewComponentContext(descriptor, arguments, _viewContext, writer); + + var invoker = _invokerFactory.CreateInstance(context); if (invoker == null) { throw new InvalidOperationException( Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName)); } - var context = new ViewComponentContext(descriptor, arguments, _viewContext, writer); await invoker.InvokeAsync(context); } @@ -148,14 +149,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents [NotNull] ViewComponentDescriptor descriptor, object[] arguments) { - var invoker = _invokerFactory.CreateInstance(descriptor, arguments); + var context = new ViewComponentContext(descriptor, arguments, _viewContext, writer); + + var invoker = _invokerFactory.CreateInstance(context); if (invoker == null) { throw new InvalidOperationException( Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName)); } - var context = new ViewComponentContext(descriptor, arguments, _viewContext, writer); invoker.Invoke(context); } } diff --git a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvoker.cs b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvoker.cs index 865c459769..afb1a59fd1 100644 --- a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvoker.cs @@ -13,16 +13,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents { public class DefaultViewComponentInvoker : IViewComponentInvoker { - private readonly IServiceProvider _serviceProvider; private readonly ITypeActivatorCache _typeActivatorCache; private readonly IViewComponentActivator _viewComponentActivator; public DefaultViewComponentInvoker( - [NotNull] IServiceProvider serviceProvider, [NotNull] ITypeActivatorCache typeActivatorCache, [NotNull] IViewComponentActivator viewComponentActivator) { - _serviceProvider = serviceProvider; _typeActivatorCache = typeActivatorCache; _viewComponentActivator = viewComponentActivator; } @@ -77,8 +74,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents private object CreateComponent([NotNull] ViewComponentContext context) { + var services = context.ViewContext.HttpContext.RequestServices; var component = _typeActivatorCache.CreateInstance( - _serviceProvider, + services, context.ViewComponentDescriptor.Type); _viewComponentActivator.Activate(component, context); return component; diff --git a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvokerFactory.cs b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvokerFactory.cs index 880acbb650..41bbd98a87 100644 --- a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvokerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/DefaultViewComponentInvokerFactory.cs @@ -1,23 +1,19 @@ // 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.Framework.Internal; namespace Microsoft.AspNet.Mvc.ViewComponents { public class DefaultViewComponentInvokerFactory : IViewComponentInvokerFactory { - private readonly IServiceProvider _serviceProvider; private readonly ITypeActivatorCache _typeActivatorCache; private readonly IViewComponentActivator _viewComponentActivator; public DefaultViewComponentInvokerFactory( - IServiceProvider serviceProvider, ITypeActivatorCache typeActivatorCache, IViewComponentActivator viewComponentActivator) { - _serviceProvider = serviceProvider; _typeActivatorCache = typeActivatorCache; _viewComponentActivator = viewComponentActivator; } @@ -26,12 +22,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents // We don't currently make use of the descriptor or the arguments here (they are available on the context). // We might do this some day to cache which method we select, so resist the urge to 'clean' this without // considering that possibility. - public IViewComponentInvoker CreateInstance( - [NotNull] ViewComponentDescriptor viewComponentDescriptor, - object[] args) + public IViewComponentInvoker CreateInstance([NotNull] ViewComponentContext context) { return new DefaultViewComponentInvoker( - _serviceProvider, _typeActivatorCache, _viewComponentActivator); } diff --git a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/IViewComponentInvokerFactory.cs b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/IViewComponentInvokerFactory.cs index 5477d431aa..726da7c7bd 100644 --- a/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/IViewComponentInvokerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Extensions/ViewComponents/IViewComponentInvokerFactory.cs @@ -7,8 +7,6 @@ namespace Microsoft.AspNet.Mvc.ViewComponents { public interface IViewComponentInvokerFactory { - IViewComponentInvoker CreateInstance( - [NotNull] ViewComponentDescriptor viewComponentDescriptor, - object[] args); + IViewComponentInvoker CreateInstance([NotNull] ViewComponentContext context); } } diff --git a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs index 806decbd4f..b5deb66160 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs @@ -222,7 +222,7 @@ namespace Microsoft.Framework.DependencyInjection // View and rendering helpers services.TryAddTransient(); services.TryAddTransient(typeof(IHtmlHelper<>), typeof(HtmlHelper<>)); - services.TryAddTransient(); + services.TryAddSingleton(); // Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict. services.TryAddSingleton(); @@ -230,9 +230,9 @@ namespace Microsoft.Framework.DependencyInjection // Consumed by the Cache tag helper to cache results across the lifetime of the application. services.TryAddSingleton(); - // DefaultHtmlGenerator is pretty much stateless but depends on Scoped services such as IUrlHelper and - // IActionBindingContextProvider. Therefore it too is scoped. - services.TryAddTransient(); + // DefaultHtmlGenerator is pretty much stateless but depends on IUrlHelper, which is scoped. + // Therefore it too is scoped. + services.TryAddScoped(); // These do caching so they should stay singleton services.TryAddSingleton(); @@ -242,7 +242,7 @@ namespace Microsoft.Framework.DependencyInjection DefaultViewComponentDescriptorCollectionProvider>(); services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddSingleton(); services.TryAddTransient(); // Security and Authorization @@ -292,4 +292,4 @@ namespace Microsoft.Framework.DependencyInjection services.AddWebEncoders(); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultFilterProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultFilterProviderTest.cs index da5215f970..66fff9b790 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultFilterProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultFilterProviderTest.cs @@ -137,9 +137,7 @@ namespace Microsoft.AspNet.Mvc.Filters private DefaultFilterProvider CreateProvider() { - var services = new ServiceContainer(); - - return new DefaultFilterProvider(services); + return new DefaultFilterProvider(); } private FilterProviderContext CreateFilterContext(List items)