示例#1
0
        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);
        }