// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc { /// /// Provides a default implementation of . /// Uses ModelBinding to populate action parameters. /// public class DefaultControllerActionArgumentBinder : IControllerActionArgumentBinder { private readonly IModelMetadataProvider _modelMetadataProvider; private readonly MvcOptions _options; private readonly IObjectModelValidator _validator; public DefaultControllerActionArgumentBinder( IModelMetadataProvider modelMetadataProvider, IObjectModelValidator validator, IOptions optionsAccessor) { _modelMetadataProvider = modelMetadataProvider; _options = optionsAccessor.Options; _validator = validator; } public async Task> BindActionArgumentsAsync( ActionContext actionContext, ActionBindingContext actionBindingContext, object controller) { var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor; if (actionDescriptor == null) { throw new ArgumentException( Resources.FormatActionDescriptorMustBeBasedOnControllerAction( typeof(ControllerActionDescriptor)), nameof(actionContext)); } var operationBindingContext = GetOperationBindingContext(actionContext, actionBindingContext); var controllerProperties = new Dictionary(StringComparer.Ordinal); await PopulateArgumentsAsync( operationBindingContext, actionContext.ModelState, controllerProperties, actionDescriptor.BoundProperties); var controllerType = actionDescriptor.ControllerTypeInfo.AsType(); ActivateProperties(controller, controllerType, controllerProperties); var actionArguments = new Dictionary(StringComparer.Ordinal); await PopulateArgumentsAsync( operationBindingContext, actionContext.ModelState, actionArguments, actionDescriptor.Parameters); return actionArguments; } private void ActivateProperties(object controller, Type containerType, Dictionary properties) { var propertyHelpers = PropertyHelper.GetProperties(controller); foreach (var property in properties) { var propertyHelper = propertyHelpers.First(helper => string.Equals(helper.Name, property.Key, StringComparison.Ordinal)); if (propertyHelper.Property == null || !propertyHelper.Property.CanWrite) { // nothing to do return; } propertyHelper.SetValue(controller, property.Value); } } private async Task PopulateArgumentsAsync( OperationBindingContext operationContext, ModelStateDictionary modelState, IDictionary arguments, IEnumerable parameterMetadata) { modelState.MaxAllowedErrors = _options.MaxModelValidationErrors; foreach (var parameter in parameterMetadata) { var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType); var parameterType = parameter.ParameterType; var modelBindingContext = GetModelBindingContext( parameter.Name, metadata, parameter.BindingInfo, modelState, operationContext); var modelBindingResult = await operationContext.ModelBinder.BindModelAsync(modelBindingContext); if (modelBindingResult != null && modelBindingResult.IsModelSet) { var modelExplorer = new ModelExplorer( _modelMetadataProvider, metadata, modelBindingResult.Model); arguments[parameter.Name] = modelBindingResult.Model; var validationContext = new ModelValidationContext( modelBindingResult.Key, modelBindingContext.BindingSource, operationContext.ValidatorProvider, modelState, modelExplorer); _validator.Validate(validationContext); } } } private static ModelBindingContext GetModelBindingContext( string parameterName, ModelMetadata metadata, BindingInfo bindingInfo, ModelStateDictionary modelState, OperationBindingContext operationBindingContext) { var modelBindingContext = ModelBindingContext.GetModelBindingContext( metadata, bindingInfo, parameterName); modelBindingContext.ModelState = modelState; modelBindingContext.ValueProvider = operationBindingContext.ValueProvider; modelBindingContext.OperationBindingContext = operationBindingContext; return modelBindingContext; } private OperationBindingContext GetOperationBindingContext( ActionContext actionContext, ActionBindingContext bindingContext) { return new OperationBindingContext { ModelBinder = bindingContext.ModelBinder, ValidatorProvider = bindingContext.ValidatorProvider, MetadataProvider = _modelMetadataProvider, HttpContext = actionContext.HttpContext, ValueProvider = bindingContext.ValueProvider, }; } } }