public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } // Specify a default argument name if none is set by ModelBinderAttribute // This is the name of the query parameter on the URL. if (string.IsNullOrEmpty(bindingContext.BinderModelName)) { bindingContext.BinderModelName = bindingContext.ModelName; } var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.BinderModelName); // This is the name of the dataSource that has been requested. var requestedDataSource = valueProviderResult.FirstValue; var(servedType, declaredFor) = GetStrategyTypes(bindingContext, typeof(IDataSource <>)); object dataSource; try { dataSource = dataSourceFactory.GetDataSource(servedType, declaredFor, requestedDataSource); } catch (DataSourceNotFoundException ex) { // A data-source that doesn't exist was requested. Add a binding error and quit. // The ApiController's IActionFilter (or individual actions) are responsible for handling the response for this error condition. bindingContext.ModelState.TryAddModelError(bindingContext.BinderModelName, ex.Message); return; } // We now have an IDataSource<> instance. Get its actual type so we can reflect on it. var dataSourceType = dataSource.GetType(); // From our concrete dataSource, figure out which properties on it are injectable parameters. var desiredPropertyViewModels = new ReflectionTypeViewModel(dataSourceType).ClassViewModel.DataSourceParameters; // Get the ASP.NET MVC metadata objects for these properties. var desiredPropertiesMetadata = desiredPropertyViewModels .Select(propViewModel => bindingContext.ModelMetadata.GetMetadataForProperty(dataSourceType, propViewModel.Name)) .ToList(); // Tell the validation stage that it should only perform validation // on the specific properties which we are binding to (and not ALL properties on the dataSource). bindingContext.ValidationState[dataSource] = new ValidationStateEntry() { Strategy = new SelectivePropertyComplexObjectValidationStrategy(desiredPropertiesMetadata) }; // Hijack ComplexTypeModelBinder to do our binding for us on the properties we care about. var childBinder = new ComplexTypeModelBinder(desiredPropertiesMetadata.ToDictionary( property => property, property => modelBinderFactory.CreateBinder(new ModelBinderFactoryContext { BindingInfo = new BindingInfo() { BinderModelName = property.BinderModelName, BinderType = property.BinderType, BindingSource = property.BindingSource, PropertyFilterProvider = property.PropertyFilterProvider, }, Metadata = property, CacheToken = property, }) ), new LoggerFactory()); // Enter a nested scope for binding the properties on our dataSource // (we're now 1 level deep instead of 0 levels deep). using (bindingContext.EnterNestedScope( bindingContext.ModelMetadata.GetMetadataForType(dataSourceType), bindingContext.FieldName, bindingContext.ModelName, dataSource)) { bindingContext.PropertyFilter = p => desiredPropertiesMetadata.Contains(p); // We call the private method "BindModelCoreAsync" here because // "BindModelAsync" performs a check to see if we should bother instantiating the root model (our dataSource). // We already instantiated our dataSource, so this check is meaningless. The consequence of this frivolous check is that // it causeses validation of client parameter properties // to not occurr if the client didn't provide any values for those parameters. // The alternative to do this would be to make a full copy of ComplexTypeModelBinder.cs and // change out the desired pieces. await(childBinder .GetType() .GetMethod("BindModelCoreAsync", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(childBinder, new[] { bindingContext }) as Task); // await childBinder.BindModelAsync(bindingContext); } // Everything worked out; we have a dataSource! // Hand back our resulting object, and we're done. bindingContext.Result = ModelBindingResult.Success(dataSource); }