ControllerActionInvker
中有一个字段_arguments
,类型为Dictionary<string, object>
,用于存放参数绑定的结构。该类提供了BinArgumentsAsync()
方法用于绑定参数:
private Task BindArgumentsAsync()
{
var actionDescriptor = _controllerContext.ActionDescriptor;
if (actionDescriptor.BoundProperties.Count == 0 && actionDescriptor.Parameters.Count == 0) {
return Task.CompletedTask;
}
Debug.Assert(_cacheEntry.ControllerBinderDelegate != null);
return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments);
}
该方法执行后,会为字段_arguments
逐一添加以参数名为键名的键值。这里的ControllerBinderDelegate
是一个委托类型,负责绑定参数:
internal delegate Task ControllerBinderDelegate(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments);
该委托的实例由ControllerBinderDelegateProvider
的静态方法CreateBinderDelegate()
提供:
internal static class ControllerBinderDelegateProvider
{
public static ControllerBinderDelegate CreateBinderDelegate(ParameterBinder parameterBinder, IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions)
{
// ... check null
var parameterBindingInfo = GetParameterBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor, mvcOptions);
var propertyBindingInfo = GetPropertyBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor);
if (parameterBindingInfo == null && propertyBindingInfo == null)
{
return null;
}
return Bind;
}
}
可以看到,这里的有两个重要的方法调用:GetParameterBindingInfo()
和GetPropertyBindingInfo()
,分别用于生成参数绑定信息和属性绑定信息:
private static BinderItem[] GetParameterBindingInfo( IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions)
{
var parameters = actionDescriptor.Parameters;
if (parameters.Count == 0) { return null; }
var parameterBindingInfo = new BinderItem[parameters.Count];
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
ModelMetadata metadata;
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameter is ControllerParameterDescriptor controllerParameterDescriptor)
{
// The default model metadata provider derives from ModelMetadataProvider
// and can therefore supply information about attributes applied to parameters.
metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo);
}
else
{
// For backward compatibility, if there's a custom model metadata provider that
// only implements the older IModelMetadataProvider interface, access the more
// limited metadata information it supplies. In this scenario, validation attributes
// are not supported on parameters.
metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
}
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
BindingInfo = parameter.BindingInfo,
Metadata = metadata,
CacheToken = parameter,
});
parameterBindingInfo[i] = new BinderItem(binder, metadata);
}
return parameterBindingInfo;
}
private static BinderItem[] GetPropertyBindingInfo( IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor)
{
var properties = actionDescriptor.BoundProperties;
if (properties.Count == 0) { return null; }
var propertyBindingInfo = new BinderItem[properties.Count];
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
for (var i = 0; i < properties.Count; i++)
{
var property = properties[i];
var metadata = modelMetadataProvider.GetMetadataForProperty(controllerType, property.Name);
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
BindingInfo = property.BindingInfo,
Metadata = metadata,
CacheToken = property,
});
propertyBindingInfo[i] = new BinderItem(binder, metadata);
}
return propertyBindingInfo;
}
CreateBinderDelegate
方法调用的最后返回了一个Bind
函数,这个Bind
的实现是:
async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments)
{
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
var parameters = actionDescriptor.Parameters;
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
var bindingInfo = parameterBindingInfo[i];
var modelMetadata = bindingInfo.ModelMetadata;
if (!modelMetadata.IsBindingAllowed) { continue; }
var result = await parameterBinder.BindModelAsync(controllerContext, bindingInfo.ModelBinder, valueProvider, parameter, modelMetadata, value: null);
if (result.IsModelSet)
{
arguments[parameter.Name] = result.Model;
}
}
var properties = actionDescriptor.BoundProperties;
for (var i = 0; i < properties.Count; i++)
{
var property = properties[i];
var bindingInfo = propertyBindingInfo[i];
var modelMetadata = bindingInfo.ModelMetadata;
if (!modelMetadata.IsBindingAllowed) { continue; }
var result = await parameterBinder.BindModelAsync(controllerContext, bindingInfo.ModelBinder, valueProvider, property, modelMetadata, value: null);
if (result.IsModelSet)
{
PropertyValueSetter.SetValue(bindingInfo.ModelMetadata, controller, result.Model);
}
}
}
}
内部调用的是ParameterBinder
类,故名思义,该类负责绑定参数。其BindModelAsync()
有两部分工作:
- 模型绑定:
ParameterBinder
类通过依赖注入传入一个IModelMetadataProvider
和IModelBinderFactory
,然后由IModelMetadataProvider
创建metadata
,再由BinderFactory
生成binder
,最后调用binder
绑定参数, - 模型校验:
ParameterBinder
类通过依赖注入传入一个IObjectModelValidator
实例,在modelBinder.BindModelAsync()
完成之后,通过该IObjectModelValidator
实例来完成模型校验:
public virtual async Task<ModelBindingResult> BindModelAsync( ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, object value)
{
... check null
if (parameter.BindingInfo?.RequestPredicate?.Invoke(actionContext) == false)
{
Logger.ParameterBinderRequestPredicateShortCircuit(parameter, metadata);
return ModelBindingResult.Failed();
}
var modelBindingContext = DefaultModelBindingContext.CreateBindingContext( actionContext, valueProvider, metadata, parameter.BindingInfo, parameter.Name);
modelBindingContext.Model = value;
var parameterModelName = parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName;
if (parameterModelName != null)
{
// The name was set explicitly, always use that as the prefix.
modelBindingContext.ModelName = parameterModelName;
}
else if (modelBindingContext.ValueProvider.ContainsPrefix(parameter.Name))
{
// We have a match for the parameter name, use that as that prefix.
modelBindingContext.ModelName = parameter.Name;
}
else
{
// No match, fallback to empty string as the prefix.
modelBindingContext.ModelName = string.Empty;
}
await modelBinder.BindModelAsync(modelBindingContext);
Logger.DoneAttemptingToBindParameterOrProperty(parameter, metadata);
var modelBindingResult = modelBindingContext.Result;
if (_objectModelValidator is ObjectModelValidator baseObjectValidator)
{
Logger.AttemptingToValidateParameterOrProperty(parameter, metadata);
EnforceBindRequiredAndValidate( baseObjectValidator, actionContext, parameter, metadata, modelBindingContext, modelBindingResult);
Logger.DoneAttemptingToValidateParameterOrProperty(parameter, metadata);
}
else
{
// For legacy implementations (which directly implemented IObjectModelValidator), fall back to the
// back-compatibility logic. In this scenario, top-level validation attributes will be ignored like
// they were historically.
if (modelBindingResult.IsModelSet)
{
_objectModelValidator.Validate( actionContext, modelBindingContext.ValidationState, modelBindingContext.ModelName, modelBindingResult.Model);
}
}
return modelBindingResult;
}
这里的EnforceBindRequiredAndValidate
:
private void EnforceBindRequiredAndValidate( ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult)
{
RecalculateModelMetadata(parameter, modelBindingResult, ref metadata);
if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired)
{
// Enforce BindingBehavior.Required (e.g., [BindRequired])
var modelName = modelBindingContext.FieldName;
var message = metadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(modelName);
actionContext.ModelState.TryAddModelError(modelName, message);
}
else if (modelBindingResult.IsModelSet)
{
// Enforce any other validation rules
baseObjectValidator.Validate( actionContext, modelBindingContext.ValidationState, modelBindingContext.ModelName, modelBindingResult.Model, metadata);
}
else if (metadata.IsRequired)
{
// We need to special case the model name for cases where a 'fallback' to empty
// prefix occurred but binding wasn't successful. For these cases there will be no
// entry in validation state to match and determine the correct key.
//
// See https://github.com/aspnet/Mvc/issues/7503
//
// This is to avoid adding validation errors for an 'empty' prefix when a simple
// type fails to bind. The fix for #7503 uncovered this issue, and was likely the
// original problem being worked around that regressed #7503.
var modelName = modelBindingContext.ModelName;
if (string.IsNullOrEmpty(modelBindingContext.ModelName) &&
parameter.BindingInfo?.BinderModelName == null)
{
// If we get here then this is a fallback case. The model name wasn't explicitly set
// and we ended up with an empty prefix.
modelName = modelBindingContext.FieldName;
}
// Run validation, we expect this to validate [Required].
baseObjectValidator.Validate( actionContext, modelBindingContext.ValidationState, modelName, modelBindingResult.Model, metadata);
}
}