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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache =
|
private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache =
|
||||||
new ConcurrentDictionary<Type, ObjectFactory>();
|
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)
|
if (bindingContext.BinderType == null)
|
||||||
{
|
{
|
||||||
// Return NoResult so that we are able to continue with the default set of model binders,
|
// Return NoResult so that we are able to continue with the default set of model binders,
|
||||||
// if there is no specific model binder provided.
|
// 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 requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||||
var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.BinderType, _createFactory);
|
var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.BinderType, _createFactory);
|
||||||
var instance = createFactory(requestServices, arguments: null);
|
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"/>
|
/// 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"/>/
|
/// when a model has the binding source <see cref="BindingSource.Body"/>/
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BodyModelBinder : BindingSourceModelBinder
|
public class BodyModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Creates a new <see cref="BodyModelBinder"/>.
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
/// </summary>
|
|
||||||
public BodyModelBinder()
|
|
||||||
: base(BindingSource.Body)
|
|
||||||
{
|
{
|
||||||
|
// 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 />
|
/// <summary>
|
||||||
protected async override Task<ModelBindingResult> BindModelCoreAsync(
|
/// Attempts to bind the model using formatters.
|
||||||
[NotNull] ModelBindingContext bindingContext)
|
/// </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
|
// 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
|
// 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 />
|
/// <inheritdoc />
|
||||||
public Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
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.
|
// Check if this binder applies.
|
||||||
if (bindingContext.ModelType != typeof(byte[]))
|
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.
|
// Check for missing data case 1: There was no <input ... /> element containing this data.
|
||||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||||
if (valueProviderResult == ValueProviderResult.None)
|
if (valueProviderResult == ValueProviderResult.None)
|
||||||
{
|
{
|
||||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||||
|
|
@ -39,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var value = valueProviderResult.FirstValue;
|
var value = valueProviderResult.FirstValue;
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -50,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
bindingContext.ModelMetadata,
|
bindingContext.ModelMetadata,
|
||||||
model);
|
model);
|
||||||
|
|
||||||
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
|
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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
|
// 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.
|
// 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
|
public class FormCollectionModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <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))
|
if (bindingContext.ModelType != typeof(IFormCollection))
|
||||||
{
|
{
|
||||||
return ModelBindingResult.NoResult;
|
return ModelBindingResult.NoResultAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return BindModelCoreAsync(bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
object model;
|
object model;
|
||||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
if (request.HasFormContentType)
|
if (request.HasFormContentType)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if DNXCORE50
|
#if DNXCORE50
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -20,7 +21,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public class FormFileModelBinder : IModelBinder
|
public class FormFileModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <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;
|
object value;
|
||||||
if (bindingContext.ModelType == typeof(IFormFile))
|
if (bindingContext.ModelType == typeof(IFormFile))
|
||||||
|
|
@ -36,6 +52,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This binder does not support the requested type.
|
// This binder does not support the requested type.
|
||||||
|
Debug.Fail("We shouldn't be called without a matching type.");
|
||||||
return ModelBindingResult.NoResult;
|
return ModelBindingResult.NoResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,33 +14,44 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class GenericModelBinder : IModelBinder
|
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);
|
var binderType = ResolveBinderType(bindingContext);
|
||||||
if (binderType != null)
|
if (binderType == null)
|
||||||
{
|
{
|
||||||
var binder = (IModelBinder)Activator.CreateInstance(binderType);
|
return ModelBindingResult.NoResultAsync;
|
||||||
|
|
||||||
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.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)
|
private static Type ResolveBinderType(ModelBindingContext context)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Reflection;
|
||||||
#endif
|
#endif
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.Framework.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
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
|
/// An <see cref="IModelBinder"/> which binds models from the request headers when a model
|
||||||
/// has the binding source <see cref="BindingSource.Header"/>/
|
/// has the binding source <see cref="BindingSource.Header"/>/
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class HeaderModelBinder : BindingSourceModelBinder
|
public class HeaderModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="HeaderModelBinder"/>.
|
|
||||||
/// </summary>
|
|
||||||
public HeaderModelBinder()
|
|
||||||
: base(BindingSource.Header)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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 request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
var modelMetadata = bindingContext.ModelMetadata;
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
typeof(MutableObjectModelBinder).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyAddRange));
|
typeof(MutableObjectModelBinder).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyAddRange));
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||||
if (!CanBindType(bindingContext.ModelMetadata))
|
if (!CanBindType(bindingContext.ModelMetadata))
|
||||||
{
|
{
|
||||||
return ModelBindingResult.NoResult;
|
return ModelBindingResult.NoResultAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mutableObjectBinderContext = new MutableObjectBinderContext()
|
var mutableObjectBinderContext = new MutableObjectBinderContext()
|
||||||
|
|
@ -37,9 +37,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
if (!(CanCreateModel(mutableObjectBinderContext)))
|
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.
|
// Create model first (if necessary) to avoid reporting errors about properties when activation fails.
|
||||||
var model = GetModel(bindingContext);
|
var model = GetModel(bindingContext);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
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
|
/// An <see cref="IModelBinder"/> which binds models from the request services when a model
|
||||||
/// has the binding source <see cref="BindingSource.Services"/>/
|
/// has the binding source <see cref="BindingSource.Services"/>/
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServicesModelBinder : BindingSourceModelBinder
|
public class ServicesModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="ServicesModelBinder"/>.
|
|
||||||
/// </summary>
|
|
||||||
public ServicesModelBinder()
|
|
||||||
: base(BindingSource.Services)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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 requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||||
var model = requestServices.GetRequiredService(bindingContext.ModelType);
|
var model = requestServices.GetRequiredService(bindingContext.ModelType);
|
||||||
var validationNode =
|
var validationNode =
|
||||||
|
|
|
||||||
|
|
@ -11,22 +11,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
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)
|
if (bindingContext.ModelMetadata.IsComplexType)
|
||||||
{
|
{
|
||||||
// this type cannot be converted
|
// this type cannot be converted
|
||||||
return ModelBindingResult.NoResult;
|
return ModelBindingResult.NoResultAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||||
if (valueProviderResult == ValueProviderResult.None)
|
if (valueProviderResult == ValueProviderResult.None)
|
||||||
{
|
{
|
||||||
// no entry
|
// no entry
|
||||||
return ModelBindingResult.NoResult;
|
return ModelBindingResult.NoResultAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||||
|
|
@ -54,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
bindingContext.ModelName,
|
bindingContext.ModelName,
|
||||||
Resources.FormatCommon_ValueNotValidForProperty(model));
|
Resources.FormatCommon_ValueNotValidForProperty(model));
|
||||||
|
|
||||||
return ModelBindingResult.Failed(bindingContext.ModelName);
|
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -63,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
bindingContext.ModelMetadata,
|
bindingContext.ModelMetadata,
|
||||||
model);
|
model);
|
||||||
|
|
||||||
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
|
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -72,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
// Were able to find a converter for the type but conversion failed.
|
// Were able to find a converter for the type but conversion failed.
|
||||||
// Tell the model binding system to skip other model binders.
|
// 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);
|
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>
|
/// <summary>
|
||||||
/// The property {0}.{1} could not be found.
|
/// The property {0}.{1} could not be found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -303,9 +303,6 @@
|
||||||
<data name="BindingSource_MustBeFromRequest" xml:space="preserve">
|
<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>
|
<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>
|
||||||
<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">
|
<data name="Common_PropertyNotFound" xml:space="preserve">
|
||||||
<value>The property {0}.{1} could not be found.</value>
|
<value>The property {0}.{1} could not be found.</value>
|
||||||
</data>
|
</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);
|
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)
|
private static ModelBindingContext GetBindingContext(Type modelType)
|
||||||
{
|
{
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
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()
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
: base(BindAddressAttribute.Source)
|
|
||||||
{
|
{
|
||||||
}
|
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);
|
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, new Address(), validationNode: null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,17 @@ using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
|
||||||
|
|
||||||
namespace ModelBindingWebSite
|
namespace ModelBindingWebSite
|
||||||
{
|
{
|
||||||
public class TestBindingSourceModelBinder : BindingSourceModelBinder
|
public class TestBindingSourceModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public TestBindingSourceModelBinder()
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
: base(FromTestAttribute.TestBindingSource)
|
|
||||||
{
|
{
|
||||||
}
|
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 attributes = ((DefaultModelMetadata)bindingContext.ModelMetadata).Attributes;
|
||||||
var metadata = attributes.Attributes.OfType<FromTestAttribute>().First();
|
var metadata = attributes.Attributes.OfType<FromTestAttribute>().First();
|
||||||
var model = metadata.Value;
|
var model = metadata.Value;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue