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

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public class ControllerContext : ActionContext public class ControllerContext : ActionContext
{ {
private IList<IValueProvider> _valueProviders; private IList<IValueProviderFactory> _valueProviderFactories;
/// <summary> /// <summary>
/// Creates a new <see cref="ControllerContext"/>. /// Creates a new <see cref="ControllerContext"/>.
@ -52,18 +52,18 @@ namespace Microsoft.AspNetCore.Mvc
} }
/// <summary> /// <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> /// </summary>
public virtual IList<IValueProvider> ValueProviders public virtual IList<IValueProviderFactory> ValueProviderFactories
{ {
get get
{ {
if (_valueProviders == null) if (_valueProviderFactories == null)
{ {
_valueProviders = new List<IValueProvider>(); _valueProviderFactories = new List<IValueProviderFactory>();
} }
return _valueProviders; return _valueProviderFactories;
} }
set set
{ {
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentNullException(nameof(value)); 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.Controllers;
using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

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

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public abstract class FilterActionInvoker : IActionInvoker public abstract class FilterActionInvoker : IActionInvoker
{ {
private readonly ControllerActionInvokerCache _controllerActionInvokerCache; private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
private readonly DiagnosticSource _diagnosticSource; private readonly DiagnosticSource _diagnosticSource;
private readonly int _maxModelValidationErrors; private readonly int _maxModelValidationErrors;
@ -71,13 +71,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentNullException(nameof(diagnosticSource)); throw new ArgumentNullException(nameof(diagnosticSource));
} }
Context = new ControllerContext(actionContext);
_controllerActionInvokerCache = controllerActionInvokerCache; _controllerActionInvokerCache = controllerActionInvokerCache;
_valueProviderFactories = valueProviderFactories;
Logger = logger; Logger = logger;
_diagnosticSource = diagnosticSource; _diagnosticSource = diagnosticSource;
_maxModelValidationErrors = maxModelValidationErrors; _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; } protected ControllerContext Context { get; }
@ -110,8 +114,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_controllerActionMethodExecutor = controllerActionInvokerState.ActionMethodExecutor; _controllerActionMethodExecutor = controllerActionInvokerState.ActionMethodExecutor;
_cursor = new FilterCursor(_filters); _cursor = new FilterCursor(_filters);
Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
await InvokeAllAuthorizationFiltersAsync(); await InvokeAllAuthorizationFiltersAsync();
// If Authorization Filters return a result, it's a short circuit because // If Authorization Filters return a result, it's a short circuit because
@ -301,18 +303,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
else 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 // >> ExceptionFilters >> Model Binding >> ActionFilters >> Action
await InvokeAllExceptionFiltersAsync(); await InvokeAllExceptionFiltersAsync();

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc.ModelBinding 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 /> /// <inheritdoc />
public virtual bool ContainsPrefix(string prefix) 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), stringLocalizerFactory: null),
}; };
valueProvider = valueProvider ?? new SimpleValueProvider();
var controllerContext = new ControllerContext() var controllerContext = new ControllerContext()
{ {
HttpContext = httpContext, HttpContext = httpContext,
ValueProviders = new[] { valueProvider, }, ValueProviderFactories = new[] { new SimpleValueProviderFactory(valueProvider), },
}; };
var binderFactory = new Mock<IModelBinderFactory>(); var binderFactory = new Mock<IModelBinderFactory>();

View File

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

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
{ {
var httpContext = GetHttpContext(updateRequest, updateOptions); var httpContext = GetHttpContext(updateRequest, updateOptions);
var services = httpContext.RequestServices; var services = httpContext.RequestServices;
var options = services.GetRequiredService<IOptions<MvcOptions>>();
var context = new ModelBindingTestContext() var context = new ModelBindingTestContext()
{ {
@ -33,17 +34,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
HttpContext = httpContext, HttpContext = httpContext,
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
RouteData = new RouteData(), 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; return context;
} }
@ -129,19 +122,5 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return serviceCollection.BuildServiceProvider(); 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; var oldModel = model;
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Address(); var model = new Address();
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}; };
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var oldModel = model; var oldModel = model;
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person2(); var model = new Person2();
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -265,7 +265,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address; var collection = model.Address;
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}; };
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person6(); var model = new Person6();
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -406,7 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person4(); var model = new Person4();
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -453,7 +453,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address; var collection = model.Address;
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -495,7 +495,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person5(); var model = new Person5();
// Act // Act
var result = await TryUpdateModel(model, string.Empty, testContext); var result = await TryUpdateModelAsync(model, string.Empty, testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -530,7 +530,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var oldModel = model; var oldModel = model;
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -565,7 +565,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Address(); var model = new Address();
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -616,7 +616,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}; };
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -666,7 +666,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var oldModel = model; var oldModel = model;
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -702,7 +702,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person2(); var model = new Person2();
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -742,7 +742,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address; var collection = model.Address;
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -794,7 +794,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}; };
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -829,7 +829,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person6(); var model = new Person6();
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -863,7 +863,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person4(); var model = new Person4();
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -910,7 +910,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var collection = model.Address; var collection = model.Address;
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -947,7 +947,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = new Person5(); var model = new Person5();
// Act // Act
var result = await TryUpdateModel(model, "prefix", testContext); var result = await TryUpdateModelAsync(model, "prefix", testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -979,7 +979,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}; };
// Act // Act
var result = await TryUpdateModel(model, prefix: "files", testContext: testContext); var result = await TryUpdateModelAsync(model, prefix: "files", testContext: testContext);
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -1085,19 +1085,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
} }
} }
private Task<bool> TryUpdateModel( private async Task<bool> TryUpdateModelAsync(
object model, object model,
string prefix, string prefix,
ModelBindingTestContext testContext) ModelBindingTestContext testContext)
{ {
return ModelBindingHelper.TryUpdateModelAsync( var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
return await ModelBindingHelper.TryUpdateModelAsync(
model, model,
model.GetType(), model.GetType(),
prefix, prefix,
testContext, testContext,
testContext.MetadataProvider, testContext.MetadataProvider,
TestModelBinderFactory.CreateDefault(), TestModelBinderFactory.CreateDefault(),
new CompositeValueProvider(testContext.ValueProviders), valueProvider,
ModelBindingTestHelper.GetObjectValidator(testContext.MetadataProvider)); 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), stringLocalizerFactory: null),
}; };
valueProvider = valueProvider ?? new SimpleValueProvider();
var controllerContext = new ControllerContext() var controllerContext = new ControllerContext()
{ {
HttpContext = httpContext, HttpContext = httpContext,
ValueProviders = new[] { valueProvider, }, ValueProviderFactories = new[] { new SimpleValueProviderFactory(valueProvider), },
}; };
var controller = new TestableController() var controller = new TestableController()