Make ValueProvider creation lazy

We want this change to avoid MVC eagerly reading the form. This is good
for general perf and also for scenarios where you want read the body
yourself (large file uploads).

We DO have scenarios where you want to configure the value providers
per-request or also to change the limits on the value providers (form) so
it's worth keeping these around on the context.
This commit is contained in:
Ryan Nowak 2016-04-26 09:13:35 -07:00
parent e483478415
commit 4d63ffa879
14 changed files with 177 additions and 107 deletions

View File

@ -1064,7 +1064,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
[NonAction]
public virtual Task<bool> TryUpdateModelAsync<TModel>(
public virtual async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix)
where TModel : class
@ -1079,7 +1079,8 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(prefix));
}
return TryUpdateModelAsync(model, prefix, new CompositeValueProvider(ControllerContext.ValueProviders));
var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext);
return await TryUpdateModelAsync(model, prefix, valueProvider);
}
/// <summary>
@ -1136,7 +1137,7 @@ namespace Microsoft.AspNetCore.Mvc
/// which need to be included for the current model.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
[NonAction]
public Task<bool> TryUpdateModelAsync<TModel>(
public async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
params Expression<Func<TModel, object>>[] includeExpressions)
@ -1152,13 +1153,14 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(includeExpressions));
}
return ModelBindingHelper.TryUpdateModelAsync(
var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
ControllerContext,
MetadataProvider,
ModelBinderFactory,
new CompositeValueProvider(ControllerContext.ValueProviders),
valueProvider,
ObjectValidator,
includeExpressions);
}
@ -1174,7 +1176,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <param name="propertyFilter">A predicate which can be used to filter properties at runtime.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
[NonAction]
public Task<bool> TryUpdateModelAsync<TModel>(
public async Task<bool> TryUpdateModelAsync<TModel>(
TModel model,
string prefix,
Func<ModelMetadata, bool> propertyFilter)
@ -1190,13 +1192,14 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(propertyFilter));
}
return ModelBindingHelper.TryUpdateModelAsync(
var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
ControllerContext,
MetadataProvider,
ModelBinderFactory,
new CompositeValueProvider(ControllerContext.ValueProviders),
valueProvider,
ObjectValidator,
propertyFilter);
}
@ -1302,7 +1305,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
[NonAction]
public virtual Task<bool> TryUpdateModelAsync(
public virtual async Task<bool> TryUpdateModelAsync(
object model,
Type modelType,
string prefix)
@ -1317,14 +1320,15 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(modelType));
}
return ModelBindingHelper.TryUpdateModelAsync(
var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
modelType,
prefix,
ControllerContext,
MetadataProvider,
ModelBinderFactory,
new CompositeValueProvider(ControllerContext.ValueProviders),
valueProvider,
ObjectValidator);
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class ControllerContext : ActionContext
{
private IList<IValueProvider> _valueProviders;
private IList<IValueProviderFactory> _valueProviderFactories;
/// <summary>
/// Creates a new <see cref="ControllerContext"/>.
@ -52,18 +52,18 @@ namespace Microsoft.AspNetCore.Mvc
}
/// <summary>
/// Gets or sets the list of <see cref="IValueProvider"/> instances for the current request.
/// Gets or sets the list of <see cref="IValueProviderFactory"/> instances for the current request.
/// </summary>
public virtual IList<IValueProvider> ValueProviders
public virtual IList<IValueProviderFactory> ValueProviderFactories
{
get
{
if (_valueProviders == null)
if (_valueProviderFactories == null)
{
_valueProviders = new List<IValueProvider>();
_valueProviderFactories = new List<IValueProviderFactory>();
}
return _valueProviders;
return _valueProviderFactories;
}
set
{
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(value));
}
_valueProviders = value;
_valueProviderFactories = value;
}
}
}

View File

@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;

View File

@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
else if (actionDescriptor.BoundProperties.Count == 0)
{
return PopulateArgumentsAsync(controllerContext, actionDescriptor.Parameters);
return BindActionArgumentsCoreAsync(controllerContext, actionDescriptor);
}
else
{
@ -81,19 +81,36 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
private async Task<IDictionary<string, object>> BindActionArgumentsCoreAsync(
ControllerContext controllerContext,
ControllerActionDescriptor actionDescriptor)
{
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
var actionArguments = await PopulateArgumentsAsync(
controllerContext,
actionDescriptor.Parameters,
valueProvider);
return actionArguments;
}
private async Task<IDictionary<string, object>> BindActionArgumentsAndPropertiesCoreAsync(
ControllerContext controllerContext,
object controller,
ControllerActionDescriptor actionDescriptor)
{
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
var controllerProperties = await PopulateArgumentsAsync(
controllerContext,
actionDescriptor.BoundProperties);
actionDescriptor.BoundProperties,
valueProvider);
ActivateProperties(actionDescriptor, controller, controllerProperties);
var actionArguments = await PopulateArgumentsAsync(
controllerContext,
actionDescriptor.Parameters);
actionDescriptor.Parameters,
valueProvider);
return actionArguments;
}
@ -111,10 +128,35 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentNullException(nameof(controllerContext));
}
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
return await BindModelAsync(parameter, controllerContext, valueProvider);
}
public async Task<ModelBindingResult?> BindModelAsync(
ParameterDescriptor parameter,
ControllerContext controllerContext,
IValueProvider valueProvider)
{
if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter));
}
if (controllerContext == null)
{
throw new ArgumentNullException(nameof(controllerContext));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
controllerContext,
new CompositeValueProvider(controllerContext.ValueProviders),
valueProvider,
metadata,
parameter.BindingInfo,
parameter.Name);
@ -241,7 +283,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private async Task<IDictionary<string, object>> PopulateArgumentsAsync(
ControllerContext controllerContext,
IList<ParameterDescriptor> parameters)
IList<ParameterDescriptor> parameters,
IValueProvider valueProvider)
{
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
@ -249,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
var modelBindingResult = await BindModelAsync(parameter, controllerContext);
var modelBindingResult = await BindModelAsync(parameter, controllerContext, valueProvider);
if (modelBindingResult != null && modelBindingResult.Value.IsModelSet)
{
arguments[parameter.Name] = modelBindingResult.Value.Model;

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public abstract class FilterActionInvoker : IActionInvoker
{
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
private readonly DiagnosticSource _diagnosticSource;
private readonly int _maxModelValidationErrors;
@ -71,13 +71,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentNullException(nameof(diagnosticSource));
}
Context = new ControllerContext(actionContext);
_controllerActionInvokerCache = controllerActionInvokerCache;
_valueProviderFactories = valueProviderFactories;
Logger = logger;
_diagnosticSource = diagnosticSource;
_maxModelValidationErrors = maxModelValidationErrors;
Context = new ControllerContext(actionContext);
Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
// PERF: These are rarely going to be changed, so let's go copy-on-write.
Context.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(valueProviderFactories);
}
protected ControllerContext Context { get; }
@ -110,8 +114,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_controllerActionMethodExecutor = controllerActionInvokerState.ActionMethodExecutor;
_cursor = new FilterCursor(_filters);
Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
await InvokeAllAuthorizationFiltersAsync();
// If Authorization Filters return a result, it's a short circuit because
@ -301,18 +303,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
else
{
// We've reached the end of resource filters, so move to setting up state to invoke model
// binding.
var valueProviders = new List<IValueProvider>();
var factoryContext = new ValueProviderFactoryContext(Context);
for (var i = 0; i < _valueProviderFactories.Count; i++)
{
var factory = _valueProviderFactories[i];
await factory.CreateValueProviderAsync(factoryContext);
}
Context.ValueProviders = factoryContext.ValueProviders;
// >> ExceptionFilters >> Model Binding >> ActionFilters >> Action
await InvokeAllExceptionFiltersAsync();

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
@ -34,6 +35,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
}
/// <summary>
/// Asynchronously creates a <see cref="CompositeValueProvider"/> using the provided
/// <paramref name="controllerContext"/>.
/// </summary>
/// <param name="controllerContext">The <see cref="ControllerContext"/> associated with the current request.</param>
/// <returns>
/// A <see cref="Task{TResult}"/> which, when completed, asynchronously returns a
/// <see cref="CompositeValueProvider"/>.
/// </returns>
public static async Task<CompositeValueProvider> CreateAsync(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException(nameof(controllerContext));
}
var factories = controllerContext.ValueProviderFactories;
var valueProviderFactoryContext = new ValueProviderFactoryContext(controllerContext);
for (var i = 0; i < factories.Count; i++)
{
var factory = factories[i];
await factory.CreateValueProviderAsync(valueProviderFactoryContext);
}
return new CompositeValueProvider(valueProviderFactoryContext.ValueProviders);
}
/// <inheritdoc />
public virtual bool ContainsPrefix(string prefix)
{

View File

@ -1,12 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
/// <summary>
/// Represents an aggregate of <see cref="IValueProviderFactory"/>.
/// </summary>
public interface ICompositeValueProviderFactory : IValueProviderFactory
{
}
}

View File

@ -1488,10 +1488,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
stringLocalizerFactory: null),
};
valueProvider = valueProvider ?? new SimpleValueProvider();
var controllerContext = new ControllerContext()
{
HttpContext = httpContext,
ValueProviders = new[] { valueProvider, },
ValueProviderFactories = new[] { new SimpleValueProviderFactory(valueProvider), },
};
var binderFactory = new Mock<IModelBinderFactory>();

View File

@ -537,7 +537,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
});
var factory = GetModelBinderFactory(binder);
controllerContext.ValueProviders.Add(new SimpleValueProvider());
controllerContext.ValueProviderFactories.Add(new SimpleValueProviderFactory());
var argumentBinder = GetArgumentBinder(factory);
var controller = new TestController();
@ -563,7 +563,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
RouteData = new RouteData(),
};
context.ValueProviders.Add(new SimpleValueProvider());
context.ValueProviderFactories.Add(new SimpleValueProviderFactory());
return context;
}

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
{
var httpContext = GetHttpContext(updateRequest, updateOptions);
var services = httpContext.RequestServices;
var options = services.GetRequiredService<IOptions<MvcOptions>>();
var context = new ModelBindingTestContext()
{
@ -33,17 +34,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
HttpContext = httpContext,
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
RouteData = new RouteData(),
ValueProviderFactories = new List<IValueProviderFactory>(options.Value.ValueProviderFactories),
};
var options = services.GetRequiredService<IOptions<MvcOptions>>();
var valueProviderFactoryContext = new ValueProviderFactoryContext(context);
foreach (var factory in options.Value.ValueProviderFactories)
{
factory.CreateValueProviderAsync(valueProviderFactoryContext).GetAwaiter().GetResult();
}
context.ValueProviders = valueProviderFactoryContext.ValueProviders;
return context;
}
@ -129,19 +122,5 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return serviceCollection.BuildServiceProvider();
}
private static ControllerContext GetControllerContext(MvcOptions options, ActionContext context)
{
var valueProviderFactoryContext = new ValueProviderFactoryContext(context);
foreach (var factory in options.ValueProviderFactories)
{
factory.CreateValueProviderAsync(valueProviderFactoryContext).GetAwaiter().GetResult();
}
return new ControllerContext(context)
{
ValueProviders = valueProviderFactoryContext.ValueProviders
};
}
}
}

View File

@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var oldModel = model;
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Address();
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
};
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var oldModel = model;
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person2();
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -265,7 +265,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
};
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person6();
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -406,7 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person4();
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -453,7 +453,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -495,7 +495,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person5();
// Act
var result = await TryUpdateModel(model, string.Empty, testContext);
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert
Assert.True(result);
@ -530,7 +530,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var oldModel = model;
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -565,7 +565,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Address();
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -616,7 +616,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
};
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -666,7 +666,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var oldModel = model;
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -702,7 +702,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person2();
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -742,7 +742,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -794,7 +794,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
};
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -829,7 +829,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person6();
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -863,7 +863,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person4();
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -910,7 +910,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -947,7 +947,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person5();
// Act
var result = await TryUpdateModel(model, "prefix", testContext);
var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert
Assert.True(result);
@ -979,7 +979,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
};
// Act
var result = await TryUpdateModel(model, prefix: "files", testContext: testContext);
var result = await TryUpdateModelAsync(model, prefix: "files", testContext: testContext);
// Assert
Assert.True(result);
@ -1085,19 +1085,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
}
private Task<bool> TryUpdateModel(
private async Task<bool> TryUpdateModelAsync(
object model,
string prefix,
ModelBindingTestContext testContext)
{
return ModelBindingHelper.TryUpdateModelAsync(
var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
model.GetType(),
prefix,
testContext,
testContext.MetadataProvider,
TestModelBinderFactory.CreateDefault(),
new CompositeValueProvider(testContext.ValueProviders),
valueProvider,
ModelBindingTestHelper.GetObjectValidator(testContext.MetadataProvider));
}
}

View File

@ -0,0 +1,36 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Internal;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
public class SimpleValueProviderFactory : IValueProviderFactory
{
private readonly IValueProvider _valueProvider;
public SimpleValueProviderFactory()
{
_valueProvider = new SimpleValueProvider();
}
public SimpleValueProviderFactory(IValueProvider valueProvider)
{
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
_valueProvider = valueProvider;
}
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
context.ValueProviders.Add(_valueProvider);
return TaskCache.CompletedTask;
}
}
}

View File

@ -287,10 +287,11 @@ namespace Microsoft.AspNetCore.Mvc.Test
stringLocalizerFactory: null),
};
valueProvider = valueProvider ?? new SimpleValueProvider();
var controllerContext = new ControllerContext()
{
HttpContext = httpContext,
ValueProviders = new[] { valueProvider, },
ValueProviderFactories = new[] { new SimpleValueProviderFactory(valueProvider), },
};
var controller = new TestableController()