Skip to content

Commit 89a8d0e

Browse files
committed
Remove IScopedInstance - use AsyncLocal for ActionContext and
ActionBindingContext This change replaces IScopedInstance<T> in favor or IActionContextAccessor and IActionBindingContextAccessor. In the spirit of IHttpContextAccessor, these are both singletons which use AsyncLocal for storage. This change allows the invoker factory to be cached which results in some significant perf gains.
1 parent ecfbdf2 commit 89a8d0e

40 files changed

+311
-371
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#if DNX451
5+
using System.Runtime.Remoting;
6+
using System.Runtime.Remoting.Messaging;
7+
#else
8+
using System.Threading;
9+
#endif
10+
11+
namespace Microsoft.AspNet.Mvc
12+
{
13+
public class ActionBindingContextAccessor : IActionBindingContextAccessor
14+
{
15+
#if DNX451
16+
private static string Key = typeof(ActionBindingContext).FullName;
17+
18+
public ActionBindingContext ActionBindingContext
19+
{
20+
get
21+
{
22+
var handle = CallContext.LogicalGetData(Key) as ObjectHandle;
23+
return handle != null ? (ActionBindingContext)handle.Unwrap() : null;
24+
}
25+
set
26+
{
27+
CallContext.LogicalSetData(Key, new ObjectHandle(value));
28+
}
29+
}
30+
#else
31+
private readonly AsyncLocal<ActionBindingContext> _storage = new AsyncLocal<ActionBindingContext>();
32+
33+
public ActionBindingContext ActionBindingContext
34+
{
35+
get { return _storage.Value; }
36+
set { _storage.Value = value; }
37+
}
38+
#endif
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#if DNX451
5+
using System.Runtime.Remoting;
6+
using System.Runtime.Remoting.Messaging;
7+
#else
8+
using System.Threading;
9+
#endif
10+
11+
namespace Microsoft.AspNet.Mvc
12+
{
13+
public class ActionContextAccessor : IActionContextAccessor
14+
{
15+
#if DNX451
16+
private static string Key = typeof(ActionContext).FullName;
17+
18+
public ActionContext ActionContext
19+
{
20+
get
21+
{
22+
var handle = CallContext.LogicalGetData(Key) as ObjectHandle;
23+
return handle != null ? (ActionContext)handle.Unwrap() : null;
24+
}
25+
set
26+
{
27+
CallContext.LogicalSetData(Key, new ObjectHandle(value));
28+
}
29+
}
30+
#else
31+
private readonly AsyncLocal<ActionContext> _storage = new AsyncLocal<ActionContext>();
32+
33+
public ActionContext ActionContext
34+
{
35+
get { return _storage.Value; }
36+
set { _storage.Value = value; }
37+
}
38+
#endif
39+
}
40+
}

src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public ControllerActionInvoker(
3030
[NotNull] IReadOnlyList<IModelBinder> modelBinders,
3131
[NotNull] IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
3232
[NotNull] IReadOnlyList<IValueProviderFactory> valueProviderFactories,
33-
[NotNull] IScopedInstance<ActionBindingContext> actionBindingContextAccessor,
34-
[NotNull] ILoggerFactory loggerFactory,
33+
[NotNull] IActionBindingContextAccessor actionBindingContextAccessor,
34+
[NotNull] ILogger logger,
3535
int maxModelValidationErrors)
3636
: base(
3737
actionContext,
@@ -42,7 +42,7 @@ public ControllerActionInvoker(
4242
modelValidatorProviders,
4343
valueProviderFactories,
4444
actionBindingContextAccessor,
45-
loggerFactory,
45+
logger,
4646
maxModelValidationErrors)
4747
{
4848
_descriptor = descriptor;

src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ public class ControllerActionInvokerProvider : IActionInvokerProvider
2121
private readonly IReadOnlyList<IOutputFormatter> _outputFormatters;
2222
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
2323
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
24-
private readonly IScopedInstance<ActionBindingContext> _actionBindingContextAccessor;
24+
private readonly IActionBindingContextAccessor _actionBindingContextAccessor;
2525
private readonly int _maxModelValidationErrors;
26-
private readonly ILoggerFactory _loggerFactory;
26+
private readonly ILogger _logger;
2727

2828
public ControllerActionInvokerProvider(
2929
IControllerFactory controllerFactory,
3030
IEnumerable<IFilterProvider> filterProviders,
3131
IControllerActionArgumentBinder argumentBinder,
3232
IOptions<MvcOptions> optionsAccessor,
33-
IScopedInstance<ActionBindingContext> actionBindingContextAccessor,
33+
IActionBindingContextAccessor actionBindingContextAccessor,
3434
ILoggerFactory loggerFactory)
3535
{
3636
_controllerFactory = controllerFactory;
@@ -43,7 +43,8 @@ public ControllerActionInvokerProvider(
4343
_valueProviderFactories = optionsAccessor.Options.ValueProviderFactories.ToArray();
4444
_actionBindingContextAccessor = actionBindingContextAccessor;
4545
_maxModelValidationErrors = optionsAccessor.Options.MaxModelValidationErrors;
46-
_loggerFactory = loggerFactory;
46+
47+
_logger = loggerFactory.CreateLogger<ControllerActionInvoker>();
4748
}
4849

4950
public int Order
@@ -70,7 +71,7 @@ public void OnProvidersExecuting([NotNull] ActionInvokerProviderContext context)
7071
_modelValidatorProviders,
7172
_valueProviderFactories,
7273
_actionBindingContextAccessor,
73-
_loggerFactory,
74+
_logger,
7475
_maxModelValidationErrors);
7576
}
7677
}

src/Microsoft.AspNet.Mvc.Core/DefaultControllerPropertyActivator.cs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ namespace Microsoft.AspNet.Mvc
1212
{
1313
public class DefaultControllerPropertyActivator : IControllerPropertyActivator
1414
{
15-
private readonly ConcurrentDictionary<Type, PropertyActivator<ActionContext>[]> _activateActions;
16-
private readonly Func<Type, PropertyActivator<ActionContext>[]> _getPropertiesToActivate;
15+
private readonly IActionBindingContextAccessor _actionBindingContextAccessor;
16+
private readonly ConcurrentDictionary<Type, PropertyActivator<Contexts>[]> _activateActions;
17+
private readonly Func<Type, PropertyActivator<Contexts>[]> _getPropertiesToActivate;
1718

18-
public DefaultControllerPropertyActivator()
19+
public DefaultControllerPropertyActivator(IActionBindingContextAccessor actionBindingContextAccessor)
1920
{
20-
_activateActions = new ConcurrentDictionary<Type, PropertyActivator<ActionContext>[]>();
21+
_actionBindingContextAccessor = actionBindingContextAccessor;
22+
23+
_activateActions = new ConcurrentDictionary<Type, PropertyActivator<Contexts>[]>();
2124
_getPropertiesToActivate = GetPropertiesToActivate;
2225
}
2326

@@ -28,34 +31,39 @@ public void Activate(ActionContext actionContext, object controller)
2831
controllerType,
2932
_getPropertiesToActivate);
3033

34+
var contexts = new Contexts()
35+
{
36+
ActionBindingContext = _actionBindingContextAccessor.ActionBindingContext,
37+
ActionContext = actionContext,
38+
};
39+
3140
for (var i = 0; i < propertiesToActivate.Length; i++)
3241
{
3342
var activateInfo = propertiesToActivate[i];
34-
activateInfo.Activate(controller, actionContext);
43+
activateInfo.Activate(controller, contexts);
3544
}
3645
}
3746

38-
private PropertyActivator<ActionContext>[] GetPropertiesToActivate(Type type)
47+
private PropertyActivator<Contexts>[] GetPropertiesToActivate(Type type)
3948
{
40-
IEnumerable<PropertyActivator<ActionContext>> activators;
41-
activators = PropertyActivator<ActionContext>.GetPropertiesToActivate(
49+
IEnumerable<PropertyActivator<Contexts>> activators;
50+
activators = PropertyActivator<Contexts>.GetPropertiesToActivate(
4251
type,
4352
typeof(ActionContextAttribute),
44-
p => new PropertyActivator<ActionContext>(p, c => c));
53+
p => new PropertyActivator<Contexts>(p, c => c.ActionContext));
4554

46-
activators = activators.Concat(PropertyActivator<ActionContext>.GetPropertiesToActivate(
55+
activators = activators.Concat(PropertyActivator<Contexts>.GetPropertiesToActivate(
4756
type,
4857
typeof(ActionBindingContextAttribute),
49-
p => new PropertyActivator<ActionContext>(p, GetActionBindingContext)));
58+
p => new PropertyActivator<Contexts>(p, c => c.ActionBindingContext)));
5059

5160
return activators.ToArray();
5261
}
5362

54-
private static ActionBindingContext GetActionBindingContext(ActionContext context)
63+
private struct Contexts
5564
{
56-
var serviceProvider = context.HttpContext.RequestServices;
57-
var accessor = serviceProvider.GetRequiredService<IScopedInstance<ActionBindingContext>>();
58-
return accessor.Value;
65+
public ActionBindingContext ActionBindingContext;
66+
public ActionContext ActionContext;
5967
}
6068
}
6169
}

src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ internal static void AddMvcCoreServices(IServiceCollection services)
104104
//
105105
// Action Invoker
106106
//
107-
// These two access per-request services
108-
services.TryAddTransient<IActionInvokerFactory, ActionInvokerFactory>();
107+
// The IActionInvokerFactory is cachable
108+
services.TryAddSingleton<IActionInvokerFactory, ActionInvokerFactory>();
109109
services.TryAddEnumerable(
110110
ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());
111111

@@ -136,8 +136,9 @@ internal static void AddMvcCoreServices(IServiceCollection services)
136136
//
137137
services.TryAddSingleton<MvcMarkerService, MvcMarkerService>();
138138
services.TryAddSingleton<ITypeActivatorCache, DefaultTypeActivatorCache>();
139-
services.TryAddScoped(typeof(IScopedInstance<>), typeof(ScopedInstance<>));
140-
services.TryAddScoped<IUrlHelper, UrlHelper>();
139+
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
140+
services.TryAddSingleton<IActionBindingContextAccessor, ActionBindingContextAccessor>();
141+
services.TryAddSingleton<IUrlHelper, UrlHelper>();
141142
}
142143

143144
private static void ConfigureDefaultServices(IServiceCollection services)

src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public abstract class FilterActionInvoker : IActionInvoker
2222
private readonly IReadOnlyList<IOutputFormatter> _outputFormatters;
2323
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
2424
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
25-
private readonly IScopedInstance<ActionBindingContext> _actionBindingContextAccessor;
25+
private readonly IActionBindingContextAccessor _actionBindingContextAccessor;
2626
private readonly ILogger _logger;
2727
private readonly int _maxModelValidationErrors;
2828

@@ -61,8 +61,8 @@ public FilterActionInvoker(
6161
[NotNull] IReadOnlyList<IModelBinder> modelBinders,
6262
[NotNull] IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
6363
[NotNull] IReadOnlyList<IValueProviderFactory> valueProviderFactories,
64-
[NotNull] IScopedInstance<ActionBindingContext> actionBindingContextAccessor,
65-
[NotNull] ILoggerFactory loggerFactory,
64+
[NotNull] IActionBindingContextAccessor actionBindingContextAccessor,
65+
[NotNull] ILogger logger,
6666
int maxModelValidationErrors)
6767
{
6868
ActionContext = actionContext;
@@ -74,7 +74,7 @@ public FilterActionInvoker(
7474
_modelValidatorProviders = modelValidatorProviders;
7575
_valueProviderFactories = valueProviderFactories;
7676
_actionBindingContextAccessor = actionBindingContextAccessor;
77-
_logger = loggerFactory.CreateLogger<FilterActionInvoker>();
77+
_logger = logger;
7878
_maxModelValidationErrors = maxModelValidationErrors;
7979
}
8080

@@ -84,11 +84,11 @@ protected ActionBindingContext ActionBindingContext
8484
{
8585
get
8686
{
87-
return _actionBindingContextAccessor.Value;
87+
return _actionBindingContextAccessor.ActionBindingContext;
8888
}
8989
private set
9090
{
91-
_actionBindingContextAccessor.Value = value;
91+
_actionBindingContextAccessor.ActionBindingContext = value;
9292
}
9393
}
9494

@@ -316,7 +316,22 @@ await item.FilterAsync.OnResourceExecutionAsync(
316316
}
317317
else
318318
{
319-
// We've reached the end of resource filters, so move on to exception filters.
319+
// We've reached the end of resource filters, so move to setting up state to invoke model
320+
// binding.
321+
ActionBindingContext = new ActionBindingContext();
322+
ActionBindingContext.InputFormatters = _resourceExecutingContext.InputFormatters;
323+
ActionBindingContext.OutputFormatters = _resourceExecutingContext.OutputFormatters;
324+
ActionBindingContext.ModelBinder = new CompositeModelBinder(_resourceExecutingContext.ModelBinders);
325+
ActionBindingContext.ValidatorProvider = new CompositeModelValidatorProvider(
326+
_resourceExecutingContext.ValidatorProviders);
327+
328+
var valueProviderFactoryContext = new ValueProviderFactoryContext(
329+
ActionContext.HttpContext,
330+
ActionContext.RouteData.Values);
331+
332+
ActionBindingContext.ValueProvider = CompositeValueProvider.Create(
333+
_resourceExecutingContext.ValueProviderFactories,
334+
valueProviderFactoryContext);
320335

321336
// >> ExceptionFilters >> Model Binding >> ActionFilters >> Action
322337
await InvokeAllExceptionFiltersAsync();
@@ -465,23 +480,6 @@ private async Task InvokeAllActionFiltersAsync()
465480
{
466481
_cursor.SetStage(FilterStage.ActionFilters);
467482

468-
Debug.Assert(_resourceExecutingContext != null);
469-
470-
ActionBindingContext = new ActionBindingContext();
471-
ActionBindingContext.InputFormatters = _resourceExecutingContext.InputFormatters;
472-
ActionBindingContext.OutputFormatters = _resourceExecutingContext.OutputFormatters;
473-
ActionBindingContext.ModelBinder = new CompositeModelBinder(_resourceExecutingContext.ModelBinders);
474-
ActionBindingContext.ValidatorProvider = new CompositeModelValidatorProvider(
475-
_resourceExecutingContext.ValidatorProviders);
476-
477-
var valueProviderFactoryContext = new ValueProviderFactoryContext(
478-
ActionContext.HttpContext,
479-
ActionContext.RouteData.Values);
480-
481-
ActionBindingContext.ValueProvider = CompositeValueProvider.Create(
482-
_resourceExecutingContext.ValueProviderFactories,
483-
valueProviderFactoryContext);
484-
485483
Instance = CreateInstance();
486484

487485
var arguments = await BindActionArgumentsAsync(ActionContext, ActionBindingContext);

src/Microsoft.AspNet.Mvc.Core/FormatFilter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ public class FormatFilter : IFormatFilter, IResourceFilter, IResultFilter
2121
/// Initializes an instance of <see cref="FormatFilter"/>.
2222
/// </summary>
2323
/// <param name="options">The <see cref="IOptions{MvcOptions}"/></param>
24-
/// <param name="actionContext">The <see cref="IScopedInstance{ActionContext}"/></param>
25-
public FormatFilter(IOptions<MvcOptions> options, IScopedInstance<ActionContext> actionContext)
24+
/// <param name="actionContextAccessor">The <see cref="IActionContextAccessor"/></param>
25+
public FormatFilter(IOptions<MvcOptions> options, IActionContextAccessor actionContextAccessor)
2626
{
2727
IsActive = true;
28-
Format = GetFormat(actionContext.Value);
28+
Format = GetFormat(actionContextAccessor.ActionContext);
2929

3030
if (string.IsNullOrEmpty(Format))
3131
{
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNet.Mvc
5+
{
6+
public interface IActionBindingContextAccessor
7+
{
8+
ActionBindingContext ActionBindingContext { get; set; }
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNet.Mvc
5+
{
6+
public interface IActionContextAccessor
7+
{
8+
ActionContext ActionContext { get; set; }
9+
}
10+
}

0 commit comments

Comments
 (0)