Optimize async code in model binders
Optimize the 'no-op' path for our model binders to return a cached task where possible without going async.
This commit is contained in:
parent
ab08e27a4b
commit
a6aaef0d63
|
|
@ -2,7 +2,6 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,15 +21,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache =
|
||||
new ConcurrentDictionary<Type, ObjectFactory>();
|
||||
|
||||
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
if (bindingContext.BinderType == null)
|
||||
{
|
||||
// Return NoResult so that we are able to continue with the default set of model binders,
|
||||
// if there is no specific model binder provided.
|
||||
return ModelBindingResult.NoResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext);
|
||||
}
|
||||
|
||||
private async Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||
var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.BinderType, _createFactory);
|
||||
var instance = createFactory(requestServices, arguments: null);
|
||||
|
|
|
|||
|
|
@ -1,91 +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.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> which provides data from a specific <see cref="ModelBinding.BindingSource"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A <see cref="BindingSourceModelBinder"/> is an <see cref="IModelBinder"/> base-implementation which
|
||||
/// can provide data for all parameters and model properties which specify the corresponding
|
||||
/// <see cref="ModelBinding.BindingSource"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="BindingSourceModelBinder"/> is greedy, meaning that a given instance expects to handle all
|
||||
/// parameters and properties annotated with the corresponding <see cref="ModelBinding.BindingSource"/> and
|
||||
/// will short-circuit the model binding process to prevent other binders from running.
|
||||
/// <see cref="ModelBinding.BindingSource.IsGreedy"/> of <see cref="BindingSource"/> must be set to <c>true.</c>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class BindingSourceModelBinder : IModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BindingSourceModelBinder"/>.
|
||||
/// </summary>
|
||||
/// <param name="bindingSource">
|
||||
/// The <see cref="ModelBinding.BindingSource"/>. Must be a single-source (non-composite) with
|
||||
/// <see cref="ModelBinding.BindingSource.IsGreedy"/> equal to <c>true</c>.
|
||||
/// </param>
|
||||
protected BindingSourceModelBinder([NotNull] BindingSource bindingSource)
|
||||
{
|
||||
// This class implements a pattern that's only useful for greedy model binders. If you need
|
||||
// to implement something non-greedy then don't use the base class.
|
||||
if (!bindingSource.IsGreedy)
|
||||
{
|
||||
var message = Resources.FormatBindingSource_MustBeGreedy(
|
||||
bindingSource.DisplayName,
|
||||
nameof(BindingSourceModelBinder));
|
||||
throw new ArgumentException(message, nameof(bindingSource));
|
||||
}
|
||||
|
||||
BindingSource = bindingSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding <see cref="ModelBinding.BindingSource"/>.
|
||||
/// </summary>
|
||||
protected BindingSource BindingSource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Binds the model. Called when the model's supported binding-source matches <see cref="BindingSource"/>.
|
||||
/// </summary>
|
||||
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task"/> which will complete when model binding has completed.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Other model binders will never run if this method is called. Return <c>null</c> to skip other model binders
|
||||
/// but allow higher-level handling e.g. falling back to empty prefix.
|
||||
/// </remarks>
|
||||
protected abstract Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext context)
|
||||
{
|
||||
var allowedBindingSource = context.BindingSource;
|
||||
if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource))
|
||||
{
|
||||
// Binding Sources are opt-in. This model either didn't specify one or specified something
|
||||
// incompatible so let other binders run.
|
||||
return ModelBindingResult.NoResult;
|
||||
}
|
||||
|
||||
var result = await BindModelCoreAsync(context);
|
||||
|
||||
var modelBindingResult = result != ModelBindingResult.NoResult ?
|
||||
result :
|
||||
ModelBindingResult.Failed(context.ModelName);
|
||||
|
||||
// This model binder is the only handler for its binding source.
|
||||
// Always tell the model binding system to skip other model binders i.e. return non-null.
|
||||
return modelBindingResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,19 +14,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// An <see cref="IModelBinder"/> which binds models from the request body using an <see cref="IInputFormatter"/>
|
||||
/// when a model has the binding source <see cref="BindingSource.Body"/>/
|
||||
/// </summary>
|
||||
public class BodyModelBinder : BindingSourceModelBinder
|
||||
public class BodyModelBinder : IModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BodyModelBinder"/>.
|
||||
/// </summary>
|
||||
public BodyModelBinder()
|
||||
: base(BindingSource.Body)
|
||||
/// <inheritdoc />
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(BindingSource.Body))
|
||||
{
|
||||
// Formatters are opt-in. This model either didn't specify [FromBody] or specified something
|
||||
// incompatible so let other binders run.
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected async override Task<ModelBindingResult> BindModelCoreAsync(
|
||||
[NotNull] ModelBindingContext bindingContext)
|
||||
/// <summary>
|
||||
/// Attempts to bind the model using formatters.
|
||||
/// </summary>
|
||||
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{ModelBindingResult}"/> which when completed returns a <see cref="ModelBindingResult"/>.
|
||||
/// </returns>
|
||||
private async Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
// For compatibility with MVC 5.0 for top level object we want to consider an empty key instead of
|
||||
// the parameter name/a custom name. In all other cases (like when binding body to a property) we
|
||||
|
|
|
|||
|
|
@ -15,22 +15,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// <inheritdoc />
|
||||
public Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
return Task.FromResult(BindModel(bindingContext));
|
||||
}
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
private ModelBindingResult BindModel(ModelBindingContext bindingContext)
|
||||
{
|
||||
// Check if this binder applies.
|
||||
if (bindingContext.ModelType != typeof(byte[]))
|
||||
{
|
||||
return ModelBindingResult.NoResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
// Check for missing data case 1: There was no <input ... /> element containing this data.
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueProviderResult == ValueProviderResult.None)
|
||||
{
|
||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||
}
|
||||
|
||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||
|
|
@ -39,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var value = valueProviderResult.FirstValue;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -50,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
bindingContext.ModelMetadata,
|
||||
model);
|
||||
|
||||
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
|
||||
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -59,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
// Matched the type (byte[]) only this binder supports. As in missing data cases, always tell the model
|
||||
// binding system to skip other model binders i.e. return non-null.
|
||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,13 +18,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public class FormCollectionModelBinder : IModelBinder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
public Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
if (bindingContext.ModelType != typeof(IFormCollection))
|
||||
{
|
||||
return ModelBindingResult.NoResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext);
|
||||
}
|
||||
|
||||
private async Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
object model;
|
||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||
if (request.HasFormContentType)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
#if DNXCORE50
|
||||
using System.Reflection;
|
||||
|
|
@ -20,8 +21,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public class FormFileModelBinder : IModelBinder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
public Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
if (bindingContext.ModelType != typeof(IFormFile) &&
|
||||
!typeof(IEnumerable<IFormFile>).IsAssignableFrom(bindingContext.ModelType))
|
||||
{
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext);
|
||||
}
|
||||
|
||||
private async Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
object value;
|
||||
if (bindingContext.ModelType == typeof(IFormFile))
|
||||
{
|
||||
|
|
@ -36,6 +52,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
else
|
||||
{
|
||||
// This binder does not support the requested type.
|
||||
Debug.Fail("We shouldn't be called without a matching type.");
|
||||
return ModelBindingResult.NoResult;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,33 +14,44 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
public class GenericModelBinder : IModelBinder
|
||||
{
|
||||
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
var binderType = ResolveBinderType(bindingContext);
|
||||
if (binderType != null)
|
||||
if (binderType == null)
|
||||
{
|
||||
var binder = (IModelBinder)Activator.CreateInstance(binderType);
|
||||
|
||||
var collectionBinder = binder as ICollectionModelBinder;
|
||||
if (collectionBinder != null &&
|
||||
bindingContext.Model == null &&
|
||||
!collectionBinder.CanCreateInstance(bindingContext.ModelType))
|
||||
{
|
||||
// Able to resolve a binder type but need a new model instance and that binder cannot create it.
|
||||
return ModelBindingResult.NoResult;
|
||||
}
|
||||
|
||||
var result = await binder.BindModelAsync(bindingContext);
|
||||
var modelBindingResult = result != ModelBindingResult.NoResult ?
|
||||
result :
|
||||
ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
|
||||
// Were able to resolve a binder type.
|
||||
// Always tell the model binding system to skip other model binders.
|
||||
return modelBindingResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
return ModelBindingResult.NoResult;
|
||||
var binder = (IModelBinder)Activator.CreateInstance(binderType);
|
||||
|
||||
var collectionBinder = binder as ICollectionModelBinder;
|
||||
if (collectionBinder != null &&
|
||||
bindingContext.Model == null &&
|
||||
!collectionBinder.CanCreateInstance(bindingContext.ModelType))
|
||||
{
|
||||
// Able to resolve a binder type but need a new model instance and that binder cannot create it.
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext, binder);
|
||||
}
|
||||
|
||||
private async Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext, IModelBinder binder)
|
||||
{
|
||||
Debug.Assert(binder != null);
|
||||
|
||||
var result = await binder.BindModelAsync(bindingContext);
|
||||
var modelBindingResult = result != ModelBindingResult.NoResult ?
|
||||
result :
|
||||
ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
|
||||
// Were able to resolve a binder type.
|
||||
// Always tell the model binding system to skip other model binders.
|
||||
return modelBindingResult;
|
||||
}
|
||||
|
||||
private static Type ResolveBinderType(ModelBindingContext context)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Reflection;
|
|||
#endif
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
|
|
@ -15,19 +14,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// An <see cref="IModelBinder"/> which binds models from the request headers when a model
|
||||
/// has the binding source <see cref="BindingSource.Header"/>/
|
||||
/// </summary>
|
||||
public class HeaderModelBinder : BindingSourceModelBinder
|
||||
public class HeaderModelBinder : IModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HeaderModelBinder"/>.
|
||||
/// </summary>
|
||||
public HeaderModelBinder()
|
||||
: base(BindingSource.Header)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(BindingSource.Header))
|
||||
{
|
||||
// Headers are opt-in. This model either didn't specify [FromHeader] or specified something
|
||||
// incompatible so let other binders run.
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||
var modelMetadata = bindingContext.ModelMetadata;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
typeof(MutableObjectModelBinder).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyAddRange));
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
public Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||
if (!CanBindType(bindingContext.ModelMetadata))
|
||||
{
|
||||
return ModelBindingResult.NoResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
var mutableObjectBinderContext = new MutableObjectBinderContext()
|
||||
|
|
@ -37,9 +37,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
if (!(CanCreateModel(mutableObjectBinderContext)))
|
||||
{
|
||||
return ModelBindingResult.NoResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext, mutableObjectBinderContext);
|
||||
}
|
||||
|
||||
private async Task<ModelBindingResult> BindModelCoreAsync(
|
||||
ModelBindingContext bindingContext,
|
||||
MutableObjectBinderContext mutableObjectBinderContext)
|
||||
{
|
||||
// Create model first (if necessary) to avoid reporting errors about properties when activation fails.
|
||||
var model = GetModel(bindingContext);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
|
|
@ -11,19 +10,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// An <see cref="IModelBinder"/> which binds models from the request services when a model
|
||||
/// has the binding source <see cref="BindingSource.Services"/>/
|
||||
/// </summary>
|
||||
public class ServicesModelBinder : BindingSourceModelBinder
|
||||
public class ServicesModelBinder : IModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ServicesModelBinder"/>.
|
||||
/// </summary>
|
||||
public ServicesModelBinder()
|
||||
: base(BindingSource.Services)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(BindingSource.Services))
|
||||
{
|
||||
// Services are opt-in. This model either didn't specify [FromService] or specified something
|
||||
// incompatible so let other binders run.
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||
var model = requestServices.GetRequiredService(bindingContext.ModelType);
|
||||
var validationNode =
|
||||
|
|
|
|||
|
|
@ -11,22 +11,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
return Task.FromResult(BindModel(bindingContext));
|
||||
}
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
public ModelBindingResult BindModel(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext.ModelMetadata.IsComplexType)
|
||||
{
|
||||
// this type cannot be converted
|
||||
return ModelBindingResult.NoResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueProviderResult == ValueProviderResult.None)
|
||||
{
|
||||
// no entry
|
||||
return ModelBindingResult.NoResult;
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||
|
|
@ -54,16 +53,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
bindingContext.ModelName,
|
||||
Resources.FormatCommon_ValueNotValidForProperty(model));
|
||||
|
||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var validationNode = new ModelValidationNode(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata,
|
||||
bindingContext.ModelMetadata,
|
||||
model);
|
||||
|
||||
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
|
||||
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -72,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
// Were able to find a converter for the type but conversion failed.
|
||||
// Tell the model binding system to skip other model binders.
|
||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -954,22 +954,6 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeFromRequest"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.
|
||||
/// </summary>
|
||||
internal static string BindingSource_MustBeGreedy
|
||||
{
|
||||
get { return GetString("BindingSource_MustBeGreedy"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.
|
||||
/// </summary>
|
||||
internal static string FormatBindingSource_MustBeGreedy(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeGreedy"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property {0}.{1} could not be found.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -303,9 +303,6 @@
|
|||
<data name="BindingSource_MustBeFromRequest" xml:space="preserve">
|
||||
<value>The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request.</value>
|
||||
</data>
|
||||
<data name="BindingSource_MustBeGreedy" xml:space="preserve">
|
||||
<value>The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.</value>
|
||||
</data>
|
||||
<data name="Common_PropertyNotFound" xml:space="preserve">
|
||||
<value>The property {0}.{1} could not be found.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -1,136 +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.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
public class BindingSourceModelBinderTest
|
||||
{
|
||||
[Fact]
|
||||
public void BindingSourceModelBinder_ThrowsOnNonGreedySource()
|
||||
{
|
||||
// Arrange
|
||||
var expected =
|
||||
"The provided binding source 'Test Source' is not a greedy data source. " +
|
||||
"'BindingSourceModelBinder' only supports greedy data sources." + Environment.NewLine +
|
||||
"Parameter name: bindingSource";
|
||||
|
||||
var bindingSource = new BindingSource(
|
||||
"Test",
|
||||
displayName: "Test Source",
|
||||
isGreedy: false,
|
||||
isFromRequest: true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentException>(
|
||||
() => new TestableBindingSourceModelBinder(bindingSource, isModelSet: false));
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindingSourceModelBinder_ReturnsNull_WithNoSource()
|
||||
{
|
||||
// Arrange
|
||||
var context = new ModelBindingContext()
|
||||
{
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(string)),
|
||||
ModelName = "model",
|
||||
};
|
||||
|
||||
var binder = new TestableBindingSourceModelBinder(BindingSource.Body, isModelSet: false);
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelBindingResult.NoResult, result);
|
||||
Assert.False(binder.WasBindModelCoreCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindingSourceModelBinder_ReturnsNull_NonMatchingSource()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestModelMetadataProvider();
|
||||
provider.ForType<string>().BindingDetails(d => d.BindingSource = BindingSource.Query);
|
||||
|
||||
var context = new ModelBindingContext()
|
||||
{
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(string)),
|
||||
ModelName = "model",
|
||||
};
|
||||
|
||||
var binder = new TestableBindingSourceModelBinder(BindingSource.Body, isModelSet: false);
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelBindingResult.NoResult, result);
|
||||
Assert.False(binder.WasBindModelCoreCalled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task BindingSourceModelBinder_ReturnsNonEmptyResult_MatchingSource(bool isModelSet)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestModelMetadataProvider();
|
||||
provider.ForType<string>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
var modelMetadata = provider.GetMetadataForType(typeof(string));
|
||||
var context = new ModelBindingContext()
|
||||
{
|
||||
BinderModelName = modelMetadata.BinderModelName,
|
||||
BindingSource = modelMetadata.BindingSource,
|
||||
ModelMetadata = modelMetadata,
|
||||
ModelName = "model",
|
||||
};
|
||||
|
||||
var binder = new TestableBindingSourceModelBinder(BindingSource.Body, isModelSet);
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(ModelBindingResult.NoResult, result);
|
||||
Assert.Equal(isModelSet, result.IsModelSet);
|
||||
Assert.Null(result.Model);
|
||||
Assert.True(binder.WasBindModelCoreCalled);
|
||||
}
|
||||
|
||||
private class TestableBindingSourceModelBinder : BindingSourceModelBinder
|
||||
{
|
||||
bool _isModelSet;
|
||||
|
||||
public TestableBindingSourceModelBinder(BindingSource source, bool isModelSet)
|
||||
: base(source)
|
||||
{
|
||||
_isModelSet = isModelSet;
|
||||
}
|
||||
|
||||
public bool WasBindModelCoreCalled { get; private set; }
|
||||
|
||||
protected override Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
WasBindModelCoreCalled = true;
|
||||
|
||||
if (_isModelSet)
|
||||
{
|
||||
return ModelBindingResult.SuccessAsync(
|
||||
bindingContext.ModelName,
|
||||
model: null,
|
||||
validationNode: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,50 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.Equal(headerValue, result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_ReturnsNoResult_ForNullBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(string);
|
||||
var header = "User-Agent";
|
||||
var headerValue = "UnitTest";
|
||||
|
||||
var binder = new HeaderModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = null;
|
||||
|
||||
modelBindingContext.FieldName = header;
|
||||
modelBindingContext.OperationBindingContext.HttpContext.Request.Headers.Add(header, new[] { headerValue });
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelBindingResult.NoResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_ReturnsNoResult_ForNonHeaderBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(string);
|
||||
var header = "User-Agent";
|
||||
var headerValue = "UnitTest";
|
||||
|
||||
var binder = new HeaderModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = BindingSource.Body;
|
||||
|
||||
modelBindingContext.FieldName = header;
|
||||
modelBindingContext.OperationBindingContext.HttpContext.Request.Headers.Add(header, new[] { headerValue });
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelBindingResult.NoResult, result);
|
||||
}
|
||||
|
||||
private static ModelBindingContext GetBindingContext(Type modelType)
|
||||
{
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
// 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.AspNet.Http.Internal;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
public class ServiceModelBinderTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task ServiceModelBinder_BindsService()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(IService);
|
||||
|
||||
var binder = new ServicesModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(ModelBindingResult.NoResult, result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.NotNull(result.Model);
|
||||
Assert.Equal("modelName", result.Key);
|
||||
|
||||
Assert.NotNull(result.ValidationNode);
|
||||
Assert.Equal("modelName", result.ValidationNode.Key);
|
||||
Assert.True(result.ValidationNode.SuppressValidation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceModelBinder_ReturnsNoResult_ForNullBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(IService);
|
||||
|
||||
var binder = new ServicesModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = null;
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelBindingResult.NoResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceModelBinder_ReturnsNoResult_ForNonServiceBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(IService);
|
||||
|
||||
var binder = new ServicesModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = BindingSource.Body;
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelBindingResult.NoResult, result);
|
||||
}
|
||||
|
||||
private static ModelBindingContext GetBindingContext(Type modelType)
|
||||
{
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType(modelType).BindingDetails(d => d.BindingSource = BindingSource.Services);
|
||||
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
||||
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddInstance<IService>(new Service());
|
||||
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
ModelMetadata = modelMetadata,
|
||||
ModelName = "modelName",
|
||||
FieldName = "modelName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = new HeaderModelBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
HttpContext = new DefaultHttpContext()
|
||||
{
|
||||
RequestServices = services.BuildServiceProvider(),
|
||||
},
|
||||
},
|
||||
BinderModelName = modelMetadata.BinderModelName,
|
||||
BindingSource = modelMetadata.BindingSource,
|
||||
};
|
||||
|
||||
return bindingContext;
|
||||
}
|
||||
|
||||
private interface IService
|
||||
{
|
||||
}
|
||||
|
||||
private class Service : IService
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,15 +148,19 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
}
|
||||
}
|
||||
|
||||
private class AddressBinder : BindingSourceModelBinder
|
||||
private class AddressBinder : IModelBinder
|
||||
{
|
||||
public AddressBinder()
|
||||
: base(BindAddressAttribute.Source)
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
}
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(BindAddressAttribute.Source))
|
||||
{
|
||||
// Binding Sources are opt-in. This model either didn't specify one or specified something
|
||||
// incompatible so let other binders run.
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
protected override Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, new Address(), validationNode: null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,17 @@ using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
|
|||
|
||||
namespace ModelBindingWebSite
|
||||
{
|
||||
public class TestBindingSourceModelBinder : BindingSourceModelBinder
|
||||
public class TestBindingSourceModelBinder : IModelBinder
|
||||
{
|
||||
public TestBindingSourceModelBinder()
|
||||
: base(FromTestAttribute.TestBindingSource)
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
}
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(FromTestAttribute.TestBindingSource))
|
||||
{
|
||||
return ModelBindingResult.NoResultAsync;
|
||||
}
|
||||
|
||||
protected override Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
var attributes = ((DefaultModelMetadata)bindingContext.ModelMetadata).Attributes;
|
||||
var metadata = attributes.Attributes.OfType<FromTestAttribute>().First();
|
||||
var model = metadata.Value;
|
||||
|
|
|
|||
Loading…
Reference in New Issue