// 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.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc.ModelBinding { public sealed class KeyValuePairModelBinder : IModelBinder { public async Task BindModelAsync(ModelBindingContext bindingContext) { ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(KeyValuePair), allowNullModel: true); var childNodes = new List(); var keyResult = await TryBindStrongModel(bindingContext, "Key", childNodes); var valueResult = await TryBindStrongModel(bindingContext, "Value", childNodes); if (keyResult.IsModelSet && valueResult.IsModelSet) { var model = new KeyValuePair( ModelBindingHelper.CastOrDefault(keyResult.Model), ModelBindingHelper.CastOrDefault(valueResult.Model)); // Update the model for the top level validation node. var modelValidationNode = new ModelValidationNode( bindingContext.ModelName, bindingContext.ModelMetadata, model, childNodes); // Success return new ModelBindingResult( model, bindingContext.ModelName, isModelSet: true, validationNode: modelValidationNode); } else if (!keyResult.IsModelSet && valueResult.IsModelSet) { bindingContext.ModelState.TryAddModelError( keyResult.Key, Resources.KeyValuePair_BothKeyAndValueMustBePresent); // Were able to get some data for this model. // Always tell the model binding system to skip other model binders i.e. return non-null. return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false); } else if (keyResult.IsModelSet && !valueResult.IsModelSet) { bindingContext.ModelState.TryAddModelError( valueResult.Key, Resources.KeyValuePair_BothKeyAndValueMustBePresent); // Were able to get some data for this model. // Always tell the model binding system to skip other model binders i.e. return non-null. return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false); } else { // Caller (GenericModelBinder) was able to resolve a binder type and will create a ModelBindingResult // that exits current ModelBinding loop. return null; } } internal async Task TryBindStrongModel( ModelBindingContext parentBindingContext, string propertyName, List childNodes) { var propertyModelMetadata = parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel)); var propertyModelName = ModelNames.CreatePropertyModelName(parentBindingContext.ModelName, propertyName); var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext( parentBindingContext, propertyModelName, propertyModelMetadata); propertyBindingContext.BinderModelName = propertyModelMetadata.BinderModelName; var modelBindingResult = await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync( propertyBindingContext); if (modelBindingResult != null) { if (modelBindingResult.ValidationNode != null) { childNodes.Add(modelBindingResult.ValidationNode); } return modelBindingResult; } // Always return a ModelBindingResult to avoid an NRE in BindModelAsync. return new ModelBindingResult(model: default(TModel), key: propertyModelName, isModelSet: false); } } }