コード例 #1
0
        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            var itemName = bindingContext.FieldName;

            // check if we can get the item from the RouteData
            var rvd = bindingContext.ActionContext.RouteData.Values;

            if (rvd.ContainsKey(itemName) &&
                bindingContext.ModelType.IsAssignableFrom(rvd[itemName].GetType()))
            {
                bindingContext.Model = rvd[itemName];

                if (this.propertyBinders == null || bindingContext.BindingSource != BindingSource.Body)
                {
                    // we aren't meant to bind request data onto the value from RouteData
                    bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
                    return;
                }

                // Check whether the underlying type of the data is different from the model type
                if (rvd[itemName].GetType() != bindingContext.ModelType)
                {
                    // if so recurse on model binding with the underlying model type
                    var servProv         = bindingContext.ActionContext.HttpContext.RequestServices;
                    var metadataProvider = servProv.GetRequiredService <IModelMetadataProvider>();
                    var modelMetadata    = metadataProvider.GetMetadataForType(bindingContext.Model.GetType());
                    bindingContext.ModelMetadata = modelMetadata;

                    var modelBinderFactory = servProv.GetRequiredService <IModelBinderFactory>();
                    var factoryContext     = new ModelBinderFactoryContext()
                    {
                        Metadata    = modelMetadata,
                        BindingInfo = new BindingInfo()
                        {
                            BinderModelName        = modelMetadata.BinderModelName,
                            BinderType             = modelMetadata.BinderType,
                            BindingSource          = modelMetadata.BindingSource,
                            PropertyFilterProvider = modelMetadata.PropertyFilterProvider,
                        },

                        // We're using the model metadata as the cache token here so that TryUpdateModelAsync calls
                        // for the same model type can share a binder. This won't overlap with normal model binding
                        // operations because they use the ParameterDescriptor for the token.
                        CacheToken = modelMetadata
                    };

                    var underlyingBinder = modelBinderFactory.CreateBinder(factoryContext);

                    await underlyingBinder.BindModelAsync(bindingContext);

                    return;
                }
            }

            if (bindingContext.IsTopLevelObject)
            {
                // We know we are editing the data, so we empty all collections, as if there
                // are no collection items present in the posted data, this means the collection is empty
                new ContentBindingPreparer().Visit(bindingContext.Model);

                bindingContext.ModelName = "";
            }

            if (!CanCreateModel(bindingContext))
            {
                return;
            }

            //Create model first(if necessary) to avoid reporting errors about properties when activation fails.
            //if (bindingContext.Model == null)
            //{
            //    bindingContext.Model = CreateModel(bindingContext);
            //}

            foreach (var property in bindingContext.ModelMetadata.Properties)
            {
                if (!CanBindProperty(bindingContext, property))
                {
                    continue;
                }

                // Pass complex (including collection) values down so that binding system does not unnecessarily
                // recreate instances or overwrite inner properties that are not bound. No need for this with simple
                // values because they will be overwritten if binding succeeds. Arrays are never reused because they
                // cannot be resized.
                object propertyModel = null;
                if (property.PropertyGetter != null &&
                    property.IsComplexType &&
                    !property.ModelType.IsArray)
                {
                    propertyModel = property.PropertyGetter(bindingContext.Model);
                }

                var fieldName = property.BinderModelName ?? property.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);

                ModelBindingResult result;
                using (bindingContext.EnterNestedScope(
                           modelMetadata: property,
                           fieldName: fieldName,
                           modelName: modelName,
                           model: propertyModel))
                {
                    await BindProperty(bindingContext);

                    result = bindingContext.Result;
                }

                if (result.IsModelSet)
                {
                    SetProperty(bindingContext, modelName, property, result);
                }
                else if (property.IsBindingRequired)
                {
                    var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
                    bindingContext.ModelState.TryAddModelError(modelName, message);
                }
            }

            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
        }
コード例 #2
0
        private async Task BindModelCoreAsync(ModelBindingContext bindingContext, ViewConfigure viewConfigure)
        {
            if (bindingContext.Model == null)
            {
                bindingContext.Model = CreateModel(bindingContext);
            }

            for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++)
            {
                var property = bindingContext.ModelMetadata.Properties[i];
                if (!CanBindProperty(bindingContext, property))
                {
                    continue;
                }

                object propertyModel = null;
                if (property.PropertyGetter != null &&
                    property.IsComplexType &&
                    !property.ModelType.IsArray)
                {
                    propertyModel = property.PropertyGetter(bindingContext.Model);
                }

                var fieldName = property.BinderModelName ?? property.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);

                ModelBindingResult result;
                using (bindingContext.EnterNestedScope(
                           modelMetadata: property,
                           fieldName: fieldName,
                           modelName: modelName,
                           model: propertyModel))
                {
                    await _modelBinderProviderContext.CreateBinder(property).BindModelAsync(bindingContext);

                    result = bindingContext.Result;
                }

                if (result.IsModelSet)
                {
                    SetProperty(bindingContext, modelName, property, result, viewConfigure);
                }
                else
                {
                    var descriptor = viewConfigure.GetViewPortDescriptor(modelName);
                    if (descriptor != null && bindingContext.ModelState.ContainsKey(modelName))
                    {
                        foreach (var valid in descriptor.Validator)
                        {
                            if (!valid.Validate(bindingContext.ModelState[modelName].RawValue))
                            {
                                valid.DisplayName = descriptor.DisplayName;
                                bindingContext.ModelState[modelName].Errors.Clear();
                                bindingContext.ModelState.TryAddModelError(modelName, valid.ErrorMessage);
                                break;
                            }
                        }
                    }

                    else if (property.IsBindingRequired)
                    {
                        var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
                        bindingContext.ModelState.TryAddModelError(modelName, message);
                    }
                }
            }

            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
        }
コード例 #3
0
        // Validates a single node (not including children)
        // Returns true if validation passes successfully
        private static bool ShallowValidate(
            string modelKey,
            ModelExplorer modelExplorer,
            ValidationContext validationContext,
            IList <IModelValidator> validators)
        {
            var isValid = true;

            var modelState           = validationContext.ModelValidationContext.ModelState;
            var fieldValidationState = modelState.GetFieldValidationState(modelKey);

            if (fieldValidationState == ModelValidationState.Invalid)
            {
                // Even if we have no validators it's possible that model binding may have added a
                // validation error (conversion error, missing data). We want to still run
                // validators even if that's the case.
                isValid = false;
            }

            // When the are no validators we bail quickly. This saves a GetEnumerator allocation.
            // In a large array (tens of thousands or more) scenario it's very significant.
            if (validators == null || validators.Count > 0)
            {
                var modelValidationContext = ModelValidationContext.GetChildValidationContext(
                    validationContext.ModelValidationContext,
                    modelExplorer);

                var modelValidationState = modelState.GetValidationState(modelKey);

                // If either the model or its properties are unvalidated, validate them now.
                if (modelValidationState == ModelValidationState.Unvalidated ||
                    fieldValidationState == ModelValidationState.Unvalidated)
                {
                    foreach (var validator in validators)
                    {
                        foreach (var error in validator.Validate(modelValidationContext))
                        {
                            var errorKey = ModelNames.CreatePropertyModelName(modelKey, error.MemberName);
                            if (!modelState.TryAddModelError(errorKey, error.Message) &&
                                modelState.GetFieldValidationState(errorKey) == ModelValidationState.Unvalidated)
                            {
                                // If we are not able to add a model error
                                // for instance when the max error count is reached, mark the model as skipped.
                                modelState.MarkFieldSkipped(errorKey);
                            }

                            isValid = false;
                        }
                    }
                }
            }

            // Add an entry only if there was an entry which was added by a model binder.
            // This prevents adding spurious entries.
            if (modelState.ContainsKey(modelKey) && isValid)
            {
                validationContext.ModelValidationContext.ModelState.MarkFieldValid(modelKey);
            }

            return(isValid);
        }
コード例 #4
0
        private bool CanValueBindAnyModelProperties(ModelBindingContext bindingContext)
        {
            // If there are no properties on the model, there is nothing to bind. We are here means this is not a top
            // level object. So we return false.
            if (bindingContext.ModelMetadata.Properties.Count == 0)
            {
                return(false);
            }

            // We want to check to see if any of the properties of the model can be bound using the value providers,
            // because that's all that MutableObjectModelBinder can handle.
            //
            // However, because a property might specify a custom binding source ([FromForm]), it's not correct
            // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName),
            // because that may include other value providers - that would lead us to mistakenly create the model
            // when the data is coming from a source we should use (ex: value found in query string, but the
            // model has [FromForm]).
            //
            // To do this we need to enumerate the properties, and see which of them provide a binding source
            // through metadata, then we decide what to do.
            //
            //      If a property has a binding source, and it's a greedy source, then it's not
            //      allowed to come from a value provider, so we skip it.
            //
            //      If a property has a binding source, and it's a non-greedy source, then we'll filter the
            //      the value providers to just that source, and see if we can find a matching prefix
            //      (see CanBindValue).
            //
            //      If a property does not have a binding source, then it's fair game for any value provider.
            //
            // If any property meets the above conditions and has a value from valueproviders, then we'll
            // create the model and try to bind it. OR if ALL properties of the model have a greedy source,
            // then we go ahead and create it.
            //
            var hasBindableProperty = false;
            var isAnyPropertyEnabledForValueProviderBasedBinding = false;

            foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties)
            {
                if (!CanBindProperty(bindingContext, propertyMetadata))
                {
                    continue;
                }

                hasBindableProperty = true;

                // This check will skip properties which are marked explicitly using a non value binder.
                var bindingSource = propertyMetadata.BindingSource;
                if (bindingSource == null || !bindingSource.IsGreedy)
                {
                    isAnyPropertyEnabledForValueProviderBasedBinding = true;

                    var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName;
                    var modelName = ModelNames.CreatePropertyModelName(
                        bindingContext.ModelName,
                        fieldName);

                    using (bindingContext.EnterNestedScope(
                               modelMetadata: propertyMetadata,
                               fieldName: fieldName,
                               modelName: modelName,
                               model: null))
                    {
                        // If any property can be bound from a value provider then continue.
                        if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
                        {
                            return(true);
                        }
                    }
                }
            }

            if (hasBindableProperty && !isAnyPropertyEnabledForValueProviderBasedBinding)
            {
                // All the properties are marked with a non value provider based marker like [FromHeader] or
                // [FromBody].
                return(true);
            }

            return(false);
        }
コード例 #5
0
        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            var constructors = bindingContext.ModelType.GetConstructors();

            if (constructors.Length != 1)
            {
                throw new InvalidOperationException($"The type '{bindingContext.ModelType}' must have exactly one constructor, but found {constructors.Length}");
            }

            var constructor = constructors[0];
            var parameters  = constructor.GetParameters();

            if (parameters.Length == 0)
            {
                throw new InvalidOperationException("the constructor must have at least one parameter, but has 0");
            }

            var propertyCount = bindingContext.ModelType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Length;

            if (parameters.Length != propertyCount)
            {
                throw new InvalidOperationException(FormattableString.Invariant($"The count of the properties ({propertyCount}) and parameters of the constructor ({parameters.Length}) must be the same"));
            }

            var allValuesBuilt = true;
            var modelValues    = new Dictionary <string, object>();

            foreach (var modelMetadataProperty in bindingContext.ModelMetadata.Properties)
            {
                var fieldName = modelMetadataProperty.BinderModelName ?? modelMetadataProperty.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
                using (bindingContext.EnterNestedScope(modelMetadataProperty, fieldName, modelName, null))
                {
                    var modelBinder = _modelBinders[bindingContext.ModelMetadata];
                    await modelBinder.BindModelAsync(bindingContext);

                    if (bindingContext.Result.IsModelSet)
                    {
                        // the property name is used, because the binder model name could be a different, then the parameter matching logic would not work
                        modelValues.Add(modelMetadataProperty.PropertyName, bindingContext.Result.Model);
                    }
                    else
                    {
                        // if a value for a value type is not present, the creation of an instance would be successful, but the instance would be inconsistent.
                        // Example:
                        // public class Foo { public Foo(int bar){...}...}
                        // invoking the constructor via reflection with null for bar, works. The value of the property is 0.
                        allValuesBuilt = false;
                        break;
                    }
                }
            }

            if (!allValuesBuilt)
            {
                bindingContext.Result = ModelBindingResult.Failed();
            }
            else
            {
                var parameterValues = new object[parameters.Length];
                for (var index = 0; index < parameters.Length; index++)
                {
                    var parameterInfo = parameters[index];
                    var(key, value) = modelValues.FirstOrDefault(prop =>
                                                                 PnCpComparer.Instance.Equals(prop.Key, parameterInfo.Name !));

                    Debug.Assert(key != null, "key != null");

                    parameterValues[index] = value;
                }

                var model = constructor.Invoke(parameterValues);
                bindingContext.Result = ModelBindingResult.Success(model);
            }
        }
コード例 #6
0
    /// <inheritdoc />
    public override async Task <InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context,
        Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (encoding == null)
        {
            throw new ArgumentNullException(nameof(encoding));
        }

        var httpContext = context.HttpContext;
        var request     = httpContext.Request;

        var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering;

        var readStream        = request.Body;
        var disposeReadStream = false;

        if (readStream.CanSeek)
        {
            // The most common way of getting here is the user has request buffering on.
            // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
            // reads as part of the deserialization.
            // To avoid this, drain and reset the stream.
            var position = request.Body.Position;
            await readStream.DrainAsync(CancellationToken.None);

            readStream.Position = position;
        }
        else if (!suppressInputFormatterBuffering)
        {
            // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
            // read everything into a buffer, and then seek back to the beginning.
            var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold;
            var contentLength   = request.ContentLength.GetValueOrDefault();
            if (contentLength > 0 && contentLength < memoryThreshold)
            {
                // If the Content-Length is known and is smaller than the default buffer size, use it.
                memoryThreshold = (int)contentLength;
            }

            readStream = new FileBufferingReadStream(request.Body, memoryThreshold);
            // Ensure the file buffer stream is always disposed at the end of a request.
            httpContext.Response.RegisterForDispose(readStream);

            await readStream.DrainAsync(CancellationToken.None);

            readStream.Seek(0L, SeekOrigin.Begin);

            disposeReadStream = true;
        }

        var       successful = true;
        Exception?exception  = null;
        object?   model;

        using (var streamReader = context.ReaderFactory(readStream, encoding))
        {
            using var jsonReader  = new JsonTextReader(streamReader);
            jsonReader.ArrayPool  = _charPool;
            jsonReader.CloseInput = false;

            var type           = context.ModelType;
            var jsonSerializer = CreateJsonSerializer(context);
            jsonSerializer.Error += ErrorHandler;

            if (_jsonOptions.ReadJsonWithRequestCulture)
            {
                jsonSerializer.Culture = CultureInfo.CurrentCulture;
            }

            try
            {
                model = jsonSerializer.Deserialize(jsonReader, type);
            }
            finally
            {
                // Clean up the error handler since CreateJsonSerializer() pools instances.
                jsonSerializer.Error -= ErrorHandler;
                ReleaseJsonSerializer(jsonSerializer);

                if (disposeReadStream)
                {
                    await readStream.DisposeAsync();
                }
            }
        }

        if (successful)
        {
            if (model == null && !context.TreatEmptyInputAsDefaultValue)
            {
                // Some nonempty inputs might deserialize as null, for example whitespace,
                // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                // be notified that we don't regard this as a real input so it can register
                // a model binding error.
                return(InputFormatterResult.NoValue());
            }
            else
            {
                return(InputFormatterResult.Success(model));
            }
        }

        if (exception is not null && exception is not(JsonException or OverflowException or FormatException))
        {
            // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary.
            // We only need to rethrow an exception if we believe it needs to be handled by something further up
            // the stack.
            // JsonException, OverflowException, and FormatException are assumed to be only encountered when
            // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else
            // needs to be rethrown.

            var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
            exceptionDispatchInfo.Throw();
        }

        return(InputFormatterResult.Failure());

        void ErrorHandler(object?sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
        {
            // Skipping error, if it's already marked as handled
            // This allows user code to implement its own error handling
            if (eventArgs.ErrorContext.Handled)
            {
                return;
            }

            successful = false;

            // The following addMember logic is intended to append the names of missing required properties to the
            // ModelStateDictionary key. Normally, just the ModelName and ErrorContext.Path is used for this key,
            // but ErrorContext.Path does not include the missing required property name like we want it to.
            // For example, given the following class and input missing the required "Name" property:
            //
            // class Person
            // {
            //     [JsonProperty(Required = Required.Always)]
            //     public string Name { get; set; }
            // }
            //
            // We will see the following ErrorContext:
            //
            // Error   {"Required property 'Name' not found in JSON. Path 'Person'..."} System.Exception {Newtonsoft.Json.JsonSerializationException}
            // Member  "Name"   object {string}
            // Path    "Person" string
            //
            // So we update the path used for the ModelStateDictionary key to be "Person.Name" instead of just "Person".
            // See https://github.com/aspnet/Mvc/issues/8509
            var path   = eventArgs.ErrorContext.Path;
            var member = eventArgs.ErrorContext.Member as string;

            // There are some deserialization exceptions that include the member in the path but not at the end.
            // For example, given the following classes and invalid input like { "b": { "c": { "d": abc } } }:
            //
            // class A
            // {
            //     public B B { get; set; }
            // }
            // class B
            // {
            //     public C C { get; set; }
            // }
            // class C
            // {
            //     public string D { get; set; }
            // }
            //
            // We will see the following ErrorContext:
            //
            // Error   {"Unexpected character encountered while parsing value: b. Path 'b.c.d'..."} System.Exception {Newtonsoft.Json.JsonReaderException}
            // Member  "c"     object {string}
            // Path    "b.c.d" string
            //
            // Notice that Member "c" is in the middle of the Path "b.c.d". The error handler gets invoked for each level of nesting.
            // null, "b", "c" and "d" are each a Member in different ErrorContexts all reporting the same parsing error.
            //
            // The parsing error is reported as a JsonReaderException instead of as a JsonSerializationException like
            // for missing required properties. We use the exception type to filter out these errors and keep the path used
            // for the ModelStateDictionary key as "b.c.d" instead of "b.c.d.c"
            // See https://github.com/dotnet/aspnetcore/issues/33451
            var addMember = !string.IsNullOrEmpty(member) && eventArgs.ErrorContext.Error is JsonSerializationException;

            // There are still JsonSerilizationExceptions that set ErrorContext.Member but include it at the
            // end of ErrorContext.Path already. The following logic attempts to filter these out.
            if (addMember)
            {
                // Path.Member case (path.Length < member.Length) needs no further checks.
                if (path.Length == member !.Length)
                {
                    // Add Member in Path.Member case but not for Path.Path.
                    addMember = !string.Equals(path, member, StringComparison.Ordinal);
                }
                else if (path.Length > member.Length)
                {
                    // Finally, check whether Path already ends or starts with Member.
                    if (member[0] == '[')
                    {
                        addMember = !path.EndsWith(member, StringComparison.Ordinal);
                    }
                    else
                    {
                        addMember = !path.EndsWith($".{member}", StringComparison.Ordinal) &&
                                    !path.EndsWith($"['{member}']", StringComparison.Ordinal) &&
                                    !path.EndsWith($"[{member}]", StringComparison.Ordinal);
                    }
                }
            }

            if (addMember)
            {
                path = ModelNames.CreatePropertyModelName(path, member);
            }

            // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
            var key = ModelNames.CreatePropertyModelName(context.ModelName, path);

            exception = eventArgs.ErrorContext.Error;

            var metadata            = GetPathMetadata(context.Metadata, path);
            var modelStateException = WrapExceptionForModelState(exception);

            context.ModelState.TryAddModelError(key, modelStateException, metadata);
            Log.JsonInputException(_logger, exception);

            // Error must always be marked as handled
            // Failure to do so can cause the exception to be rethrown at every recursive level and
            // overflow the stack for x64 CLR processes
            eventArgs.ErrorContext.Handled = true;
        }
コード例 #7
0
        private async ValueTask <(bool attemptedBinding, bool bindingSucceeded)> BindPropertiesAsync(
            ModelBindingContext bindingContext,
            int propertyData,
            IReadOnlyList <ModelMetadata> boundProperties)
        {
            var attemptedBinding = false;
            var bindingSucceeded = false;

            if (boundProperties.Count == 0)
            {
                return(attemptedBinding, bindingSucceeded);
            }

            var postponePlaceholderBinding = false;

            for (var i = 0; i < boundProperties.Count; i++)
            {
                var property = boundProperties[i];
                if (!CanBindItem(bindingContext, property))
                {
                    continue;
                }

                var propertyBinder = _propertyBinders[property];
                if (propertyBinder is PlaceholderBinder)
                {
                    if (postponePlaceholderBinding)
                    {
                        // Decided to postpone binding properties that complete a loop in the model types when handling
                        // an earlier loop-completing property. Postpone binding this property too.
                        continue;
                    }
                    else if (!bindingContext.IsTopLevelObject &&
                             !bindingSucceeded &&
                             propertyData == GreedyPropertiesMayHaveData)
                    {
                        // Have no confirmation of data for the current instance. Postpone completing the loop until
                        // we _know_ the current instance is useful. Recursion would otherwise occur prior to the
                        // block with a similar condition after the loop.
                        //
                        // Example cases include an Employee class containing
                        // 1. a Manager property of type Employee
                        // 2. an Employees property of type IList<Employee>
                        postponePlaceholderBinding = true;
                        continue;
                    }
                }

                var fieldName = property.BinderModelName ?? property.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
                var result    = await BindPropertyAsync(bindingContext, property, propertyBinder, fieldName, modelName);

                if (result.IsModelSet)
                {
                    attemptedBinding = true;
                    bindingSucceeded = true;
                }
                else if (property.IsBindingRequired)
                {
                    attemptedBinding = true;
                }
            }

            if (postponePlaceholderBinding && bindingSucceeded)
            {
                // Have some data for this instance. Continue with the model type loop.
                for (var i = 0; i < boundProperties.Count; i++)
                {
                    var property = boundProperties[i];
                    if (!CanBindItem(bindingContext, property))
                    {
                        continue;
                    }

                    var propertyBinder = _propertyBinders[property];
                    if (propertyBinder is PlaceholderBinder)
                    {
                        var fieldName = property.BinderModelName ?? property.PropertyName;
                        var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);

                        await BindPropertyAsync(bindingContext, property, propertyBinder, fieldName, modelName);
                    }
                }
            }

            return(attemptedBinding, bindingSucceeded);
        }
コード例 #8
0
        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            var postedFiles = new List <IRemoteStreamContent>();

            // If we're at the top level, then use the FieldName (parameter or property name).
            // This handles the fact that there will be nothing in the ValueProviders for this parameter
            // and so we'll do the right thing even though we 'fell-back' to the empty prefix.
            var modelName = bindingContext.IsTopLevelObject
                ? bindingContext.BinderModelName ?? bindingContext.FieldName
                : bindingContext.ModelName;

            await GetFormFilesAsync(modelName, bindingContext, postedFiles);

            // If ParameterBinder incorrectly overrode ModelName, fall back to OriginalModelName prefix. Comparisons
            // are tedious because e.g. top-level parameter or property is named Blah and it contains a BlahBlah
            // property. OriginalModelName may be null in tests.
            if (postedFiles.Count == 0 &&
                bindingContext.OriginalModelName != null &&
                !string.Equals(modelName, bindingContext.OriginalModelName, StringComparison.Ordinal) &&
                !modelName.StartsWith(bindingContext.OriginalModelName + "[", StringComparison.Ordinal) &&
                !modelName.StartsWith(bindingContext.OriginalModelName + ".", StringComparison.Ordinal))
            {
                modelName = ModelNames.CreatePropertyModelName(bindingContext.OriginalModelName, modelName);
                await GetFormFilesAsync(modelName, bindingContext, postedFiles);
            }

            object value;

            if (bindingContext.ModelType == typeof(IRemoteStreamContent) || bindingContext.ModelType == typeof(RemoteStreamContent))
            {
                if (postedFiles.Count == 0)
                {
                    // Silently fail if the named file does not exist in the request.
                    return;
                }

                value = postedFiles.First();
            }
            else
            {
                if (postedFiles.Count == 0 && !bindingContext.IsTopLevelObject)
                {
                    // Silently fail if no files match. Will bind to an empty collection (treat empty as a success
                    // case and not reach here) if binding to a top-level object.
                    return;
                }

                // Perform any final type mangling needed.
                var modelType = bindingContext.ModelType;
                if (modelType == typeof(IRemoteStreamContent[]) || modelType == typeof(RemoteStreamContent[]))
                {
                    value = postedFiles.ToArray();
                }
                else
                {
                    value = postedFiles;
                }
            }

            // We need to add a ValidationState entry because the modelName might be non-standard. Otherwise
            // the entry we create in model state might not be marked as valid.
            bindingContext.ValidationState.Add(value, new ValidationStateEntry()
            {
                Key = modelName,
            });

            bindingContext.ModelState.SetModelValue(
                modelName,
                rawValue: null,
                attemptedValue: null);

            bindingContext.Result = ModelBindingResult.Success(value);
        }
コード例 #9
0
            private static string GetName(string containerName, ApiParameterDescriptionContext metadata)
            {
                var propertyName = !string.IsNullOrEmpty(metadata.BinderModelName) ? metadata.BinderModelName : metadata.PropertyName;

                return(ModelNames.CreatePropertyModelName(containerName, propertyName));
            }
コード例 #10
0
        private async Task BindModelCoreAsync(ModelBindingContext bindingContext, int propertyData)
        {
            Debug.Assert(propertyData == GreedyPropertiesMayHaveData || propertyData == ValueProviderDataAvailable);

            // Create model first (if necessary) to avoid reporting errors about properties when activation fails.
            if (bindingContext.Model == null)
            {
                bindingContext.Model = CreateModel(bindingContext);
            }

            var modelMetadata              = bindingContext.ModelMetadata;
            var attemptedPropertyBinding   = false;
            var propertyBindingSucceeded   = false;
            var postponePlaceholderBinding = false;

            for (var i = 0; i < modelMetadata.Properties.Count; i++)
            {
                var property = modelMetadata.Properties[i];
                if (!CanBindProperty(bindingContext, property))
                {
                    continue;
                }

                if (_propertyBinders[property] is PlaceholderBinder)
                {
                    if (postponePlaceholderBinding)
                    {
                        // Decided to postpone binding properties that complete a loop in the model types when handling
                        // an earlier loop-completing property. Postpone binding this property too.
                        continue;
                    }
                    else if (!bindingContext.IsTopLevelObject &&
                             !propertyBindingSucceeded &&
                             propertyData == GreedyPropertiesMayHaveData)
                    {
                        // Have no confirmation of data for the current instance. Postpone completing the loop until
                        // we _know_ the current instance is useful. Recursion would otherwise occur prior to the
                        // block with a similar condition after the loop.
                        //
                        // Example cases include an Employee class containing
                        // 1. a Manager property of type Employee
                        // 2. an Employees property of type IList<Employee>
                        postponePlaceholderBinding = true;
                        continue;
                    }
                }

                var fieldName = property.BinderModelName ?? property.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
                var result    = await BindProperty(bindingContext, property, fieldName, modelName);

                if (result.IsModelSet)
                {
                    attemptedPropertyBinding = true;
                    propertyBindingSucceeded = true;
                }
                else if (property.IsBindingRequired)
                {
                    attemptedPropertyBinding = true;
                }
            }

            if (postponePlaceholderBinding && propertyBindingSucceeded)
            {
                // Have some data for this instance. Continue with the model type loop.
                for (var i = 0; i < modelMetadata.Properties.Count; i++)
                {
                    var property = modelMetadata.Properties[i];
                    if (!CanBindProperty(bindingContext, property))
                    {
                        continue;
                    }

                    if (_propertyBinders[property] is PlaceholderBinder)
                    {
                        var fieldName = property.BinderModelName ?? property.PropertyName;
                        var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
                        await BindProperty(bindingContext, property, fieldName, modelName);
                    }
                }
            }

            // Have we created a top-level model despite an inability to bind anything in said model and a lack of
            // other IsBindingRequired errors? Does that violate [BindRequired] on the model? This case occurs when
            // 1. The top-level model has no public settable properties.
            // 2. All properties in a [BindRequired] model have [BindNever] or are otherwise excluded from binding.
            // 3. No data exists for any property.
            if (!attemptedPropertyBinding &&
                bindingContext.IsTopLevelObject &&
                modelMetadata.IsBindingRequired)
            {
                var messageProvider = modelMetadata.ModelBindingMessageProvider;
                var message         = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName);
                bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
            }

            _logger.DoneAttemptingToBindModel(bindingContext);

            // Have all binders failed because no data was available?
            //
            // If CanCreateModel determined a property has data, failures are likely due to conversion errors. For
            // example, user may submit ?[0].id=twenty&[1].id=twenty-one&[2].id=22 for a collection of a complex type
            // with an int id property. In that case, the bound model should be [ {}, {}, { id = 22 }] and
            // ModelState should contain errors about both [0].id and [1].id. Do not inform higher-level binders of the
            // failure in this and similar cases.
            //
            // If CanCreateModel could not find data for non-greedy properties, failures indicate greedy binders were
            // unsuccessful. For example, user may submit file attachments [0].File and [1].File but not [2].File for
            // a collection of a complex type containing an IFormFile property. In that case, we have exhausted the
            // attached files and checking for [3].File is likely be pointless. (And, if it had a point, would we stop
            // after 10 failures, 100, or more -- all adding redundant errors to ModelState?) Inform higher-level
            // binders of the failure.
            //
            // Required properties do not change the logic below. Missed required properties cause ModelState errors
            // but do not necessarily prevent further attempts to bind.
            //
            // This logic is intended to maximize correctness but does not avoid infinite loops or recursion when a
            // greedy model binder succeeds unconditionally.
            if (!bindingContext.IsTopLevelObject &&
                !propertyBindingSucceeded &&
                propertyData == GreedyPropertiesMayHaveData)
            {
                bindingContext.Result = ModelBindingResult.Failed();
                return;
            }

            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
        }
コード例 #11
0
        /// <inheritdoc />
        public override async Task <InputFormatterResult> ReadRequestBodyAsync(
            InputFormatterContext context,
            Encoding encoding)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (encoding == null)
            {
                throw new ArgumentNullException(nameof(encoding));
            }

            var request = context.HttpContext.Request;

            var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering;

            if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
            {
                // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                // read everything into a buffer, and then seek back to the beginning.
                request.EnableBuffering();
                Debug.Assert(request.Body.CanSeek);

                await request.Body.DrainAsync(CancellationToken.None);

                request.Body.Seek(0L, SeekOrigin.Begin);
            }

            using (var streamReader = context.ReaderFactory(request.Body, encoding))
            {
                using (var jsonReader = new JsonTextReader(streamReader))
                {
                    jsonReader.ArrayPool  = _charPool;
                    jsonReader.CloseInput = false;

                    var       successful = true;
                    Exception exception  = null;
                    void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
                    {
                        successful = false;

                        // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path.
                        var path      = eventArgs.ErrorContext.Path;
                        var member    = eventArgs.ErrorContext.Member?.ToString();
                        var addMember = !string.IsNullOrEmpty(member);

                        if (addMember)
                        {
                            // Path.Member case (path.Length < member.Length) needs no further checks.
                            if (path.Length == member.Length)
                            {
                                // Add Member in Path.Memb case but not for Path.Path.
                                addMember = !string.Equals(path, member, StringComparison.Ordinal);
                            }
                            else if (path.Length > member.Length)
                            {
                                // Finally, check whether Path already ends with Member.
                                if (member[0] == '[')
                                {
                                    addMember = !path.EndsWith(member, StringComparison.Ordinal);
                                }
                                else
                                {
                                    addMember = !path.EndsWith("." + member, StringComparison.Ordinal);
                                }
                            }
                        }

                        if (addMember)
                        {
                            path = ModelNames.CreatePropertyModelName(path, member);
                        }

                        // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
                        var key = ModelNames.CreatePropertyModelName(context.ModelName, path);

                        exception = eventArgs.ErrorContext.Error;

                        var metadata            = GetPathMetadata(context.Metadata, path);
                        var modelStateException = WrapExceptionForModelState(exception);

                        context.ModelState.TryAddModelError(key, modelStateException, metadata);

                        _logger.JsonInputException(exception);

                        // Error must always be marked as handled
                        // Failure to do so can cause the exception to be rethrown at every recursive level and
                        // overflow the stack for x64 CLR processes
                        eventArgs.ErrorContext.Handled = true;
                    }

                    var type           = context.ModelType;
                    var jsonSerializer = CreateJsonSerializer();
                    jsonSerializer.Error += ErrorHandler;
                    object model;
                    try
                    {
                        model = jsonSerializer.Deserialize(jsonReader, type);
                    }
                    finally
                    {
                        // Clean up the error handler since CreateJsonSerializer() pools instances.
                        jsonSerializer.Error -= ErrorHandler;
                        ReleaseJsonSerializer(jsonSerializer);
                    }

                    if (successful)
                    {
                        if (model == null && !context.TreatEmptyInputAsDefaultValue)
                        {
                            // Some nonempty inputs might deserialize as null, for example whitespace,
                            // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                            // be notified that we don't regard this as a real input so it can register
                            // a model binding error.
                            return(InputFormatterResult.NoValue());
                        }
                        else
                        {
                            return(InputFormatterResult.Success(model));
                        }
                    }

                    if (!(exception is JsonException || exception is OverflowException))
                    {
                        var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
                        exceptionDispatchInfo.Throw();
                    }

                    return(InputFormatterResult.Failure());
                }
            }
        }
コード例 #12
0
        /// <inheritdoc cref="IModelBinder.BindModelAsync(ModelBindingContext)" />
        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (!CanCreateModel(bindingContext))
            {
                return;
            }

            if (bindingContext.Model == null)
            {
                bindingContext.Model = CreateModel(bindingContext);
            }

            var modelMetadata            = bindingContext.ModelMetadata;
            var attemptedPropertyBinding = false;

            for (var i = 0; i < modelMetadata.Properties.Count; i++)
            {
                var property            = modelMetadata.Properties[i];
                var dataMemberAttribute = ((DefaultModelMetadata)property).Attributes.Attributes.OfType <DataMemberAttribute>().FirstOrDefault();

                if (!CanBindProperty(bindingContext, property))
                {
                    continue;
                }

                object propertyModel = null;

                if (property.PropertyGetter != null && property.IsComplexType && !property.ModelType.IsArray)
                {
                    propertyModel = property.PropertyGetter(bindingContext.Model);
                }

                var fieldName = property.BinderModelName ?? property.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);

                ModelBindingResult result;

                using (bindingContext.EnterNestedScope(property, fieldName, modelName, propertyModel))
                {
                    var innerBindinContext = new DefaultModelBindingContext
                    {
                        ModelMetadata = property,
                        ModelName     = dataMemberAttribute.Name,
                        ModelState    = bindingContext.ModelState,
                        FieldName     = dataMemberAttribute.Name,
                        ValueProvider = bindingContext.ValueProvider
                    };

                    await BindProperty(innerBindinContext);

                    result = innerBindinContext.Result;
                }

                if (result.IsModelSet)
                {
                    attemptedPropertyBinding = true;

                    SetProperty(bindingContext, modelName, property, result);
                }
                else if (property.IsBindingRequired)
                {
                    attemptedPropertyBinding = true;

                    var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);

                    bindingContext.ModelState.TryAddModelError(modelName, message);
                }
            }

            if (!attemptedPropertyBinding && bindingContext.IsTopLevelObject && modelMetadata.IsBindingRequired)
            {
                var messageProvider = modelMetadata.ModelBindingMessageProvider;
                var message         = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName);

                bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
            }

            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
        }
コード例 #13
0
 public override async Task<InputFormatterResult> ReadRequestBodyAsync(
     InputFormatterContext context,
     Encoding encoding)
 {
     if (context == null)
     {
         throw new ArgumentNullException(nameof(context));
     }
     if (encoding == null)
     {
         throw new ArgumentNullException(nameof(encoding));
     }
     var request = context.HttpContext.Request;
     var suppressInputFormatterBuffering = options?.SuppressInputFormatterBuffering ?? false;
     if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
     {
         // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
         // read everything into a buffer, and then seek back to the beginning.
         request.EnableBuffering();
         Debug.Assert(request.Body.CanSeek);
         await request.Body.DrainAsync(CancellationToken.None);
         request.Body.Seek(0L, SeekOrigin.Begin);
     }
     using (var streamReader = context.ReaderFactory(request.Body, encoding))
     {
         using (var jsonReader = new JsonTextReader(streamReader))
         {
             jsonReader.ArrayPool = charPool;
             jsonReader.CloseInput = false;
             var successful = true;
             Exception exception = null;
             void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
             {
                 successful = false;
                 var path = eventArgs.ErrorContext.Path;
                 var key = ModelNames.CreatePropertyModelName(context.ModelName, path);
                 context.ModelState.TryAddModelError(key, $"Invalid value specified for {path}");
                 eventArgs.ErrorContext.Handled = true;
             }
             var type = context.ModelType;
             var jsonSerializer = CreateJsonSerializer();
             jsonSerializer.Error += ErrorHandler;
             object model;
             try
             {
                 model = jsonSerializer.Deserialize(jsonReader, type);
             }
             finally
             {
                 // Clean up the error handler since CreateJsonSerializer() pools instances.
                 jsonSerializer.Error -= ErrorHandler;
                 ReleaseJsonSerializer(jsonSerializer);
             }
             if (successful)
             {
                 if (model == null && !context.TreatEmptyInputAsDefaultValue)
                 {
                     // Some nonempty inputs might deserialize as null, for example whitespace,
                     // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                     // be notified that we don't regard this as a real input so it can register
                     // a model binding error.
                     return InputFormatterResult.NoValue();
                 }
                 else
                 {
                     return InputFormatterResult.Success(model);
                 }
             }
             if (!(exception is JsonException || exception is OverflowException))
             {
                 var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
                 exceptionDispatchInfo.Throw();
             }
             return InputFormatterResult.Failure();
         }
     }
 }
コード例 #14
0
        /// <inheritdoc />
        public override async Task <InputFormatterResult> ReadRequestBodyAsync(
            InputFormatterContext context,
            Encoding encoding)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (encoding == null)
            {
                throw new ArgumentNullException(nameof(encoding));
            }

            var httpContext = context.HttpContext;
            var request     = httpContext.Request;

            var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering;

            var readStream        = request.Body;
            var disposeReadStream = false;

            if (readStream.CanSeek)
            {
                // The most common way of getting here is the user has request buffering on.
                // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
                // reads as part of the deserialization.
                // To avoid this, drain and reset the stream.
                var position = request.Body.Position;
                await readStream.DrainAsync(CancellationToken.None);

                readStream.Position = position;
            }
            else if (!suppressInputFormatterBuffering)
            {
                // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                // read everything into a buffer, and then seek back to the beginning.
                var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold;
                var contentLength   = request.ContentLength.GetValueOrDefault();
                if (contentLength > 0 && contentLength < memoryThreshold)
                {
                    // If the Content-Length is known and is smaller than the default buffer size, use it.
                    memoryThreshold = (int)contentLength;
                }

                readStream = new FileBufferingReadStream(request.Body, memoryThreshold);
                // Ensure the file buffer stream is always disposed at the end of a request.
                httpContext.Response.RegisterForDispose(readStream);

                await readStream.DrainAsync(CancellationToken.None);

                readStream.Seek(0L, SeekOrigin.Begin);

                disposeReadStream = true;
            }

            var       successful = true;
            Exception exception  = null;
            object    model;

            using (var streamReader = context.ReaderFactory(readStream, encoding))
            {
                using var jsonReader  = new JsonTextReader(streamReader);
                jsonReader.ArrayPool  = _charPool;
                jsonReader.CloseInput = false;

                var type           = context.ModelType;
                var jsonSerializer = CreateJsonSerializer(context);
                jsonSerializer.Error += ErrorHandler;
                try
                {
                    model = jsonSerializer.Deserialize(jsonReader, type);
                }
                finally
                {
                    // Clean up the error handler since CreateJsonSerializer() pools instances.
                    jsonSerializer.Error -= ErrorHandler;
                    ReleaseJsonSerializer(jsonSerializer);

                    if (disposeReadStream)
                    {
                        await readStream.DisposeAsync();
                    }
                }
            }

            if (successful)
            {
                if (model == null && !context.TreatEmptyInputAsDefaultValue)
                {
                    // Some nonempty inputs might deserialize as null, for example whitespace,
                    // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                    // be notified that we don't regard this as a real input so it can register
                    // a model binding error.
                    return(InputFormatterResult.NoValue());
                }
                else
                {
                    return(InputFormatterResult.Success(model));
                }
            }

            if (!(exception is JsonException || exception is OverflowException || exception is FormatException))
            {
                // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary.
                // We only need to rethrow an exception if we believe it needs to be handled by something further up
                // the stack.
                // JsonException, OverflowException, and FormatException are assumed to be only encountered when
                // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else
                // needs to be rethrown.

                var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
                exceptionDispatchInfo.Throw();
            }

            return(InputFormatterResult.Failure());

            void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
            {
                successful = false;

                // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path.
                var path      = eventArgs.ErrorContext.Path;
                var member    = eventArgs.ErrorContext.Member?.ToString();
                var addMember = !string.IsNullOrEmpty(member);

                if (addMember)
                {
                    // Path.Member case (path.Length < member.Length) needs no further checks.
                    if (path.Length == member.Length)
                    {
                        // Add Member in Path.Memb case but not for Path.Path.
                        addMember = !string.Equals(path, member, StringComparison.Ordinal);
                    }
                    else if (path.Length > member.Length)
                    {
                        // Finally, check whether Path already ends with Member.
                        if (member[0] == '[')
                        {
                            addMember = !path.EndsWith(member, StringComparison.Ordinal);
                        }
                        else
                        {
                            addMember = !path.EndsWith("." + member, StringComparison.Ordinal) &&
                                        !path.EndsWith("['" + member + "']", StringComparison.Ordinal) &&
                                        !path.EndsWith("[" + member + "]", StringComparison.Ordinal);
                        }
                    }
                }

                if (addMember)
                {
                    path = ModelNames.CreatePropertyModelName(path, member);
                }

                // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
                var key = ModelNames.CreatePropertyModelName(context.ModelName, path);

                exception = eventArgs.ErrorContext.Error;

                var metadata            = GetPathMetadata(context.Metadata, path);
                var modelStateException = WrapExceptionForModelState(exception);

                context.ModelState.TryAddModelError(key, modelStateException, metadata);

                _logger.JsonInputException(exception);

                // Error must always be marked as handled
                // Failure to do so can cause the exception to be rethrown at every recursive level and
                // overflow the stack for x64 CLR processes
                eventArgs.ErrorContext.Handled = true;
            }
        }
コード例 #15
0
        private int CanBindAnyModelItem(ModelBindingContext bindingContext)
        {
            // If there are no properties on the model, and no constructor parameters, there is nothing to bind. We are here means this is not a top
            // level object. So we return false.
            var modelMetadata = bindingContext.ModelMetadata;
            var performsConstructorBinding = bindingContext.Model == null && modelMetadata.BoundConstructor != null;

            if (modelMetadata.Properties.Count == 0 &&
                (!performsConstructorBinding || modelMetadata.BoundConstructor.BoundConstructorParameters.Count == 0))
            {
                Log.NoPublicSettableItems(_logger, bindingContext);
                return(NoDataAvailable);
            }

            // We want to check to see if any of the properties of the model can be bound using the value providers or
            // a greedy binder.
            //
            // Because a property might specify a custom binding source ([FromForm]), it's not correct
            // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName);
            // that may include other value providers - that would lead us to mistakenly create the model
            // when the data is coming from a source we should use (ex: value found in query string, but the
            // model has [FromForm]).
            //
            // To do this we need to enumerate the properties, and see which of them provide a binding source
            // through metadata, then we decide what to do.
            //
            //      If a property has a binding source, and it's a greedy source, then it's always bound.
            //
            //      If a property has a binding source, and it's a non-greedy source, then we'll filter the
            //      the value providers to just that source, and see if we can find a matching prefix
            //      (see CanBindValue).
            //
            //      If a property does not have a binding source, then it's fair game for any value provider.
            //
            // Bottom line, if any property meets the above conditions and has a value from ValueProviders, then we'll
            // create the model and try to bind it. Of, if ANY properties of the model have a greedy source,
            // then we go ahead and create it.
            var hasGreedyBinders = false;

            for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++)
            {
                var propertyMetadata = bindingContext.ModelMetadata.Properties[i];
                if (!CanBindItem(bindingContext, propertyMetadata))
                {
                    continue;
                }

                // If any property can be bound from a greedy binding source, then success.
                var bindingSource = propertyMetadata.BindingSource;
                if (bindingSource != null && bindingSource.IsGreedy)
                {
                    hasGreedyBinders = true;
                    continue;
                }

                // Otherwise, check whether the (perhaps filtered) value providers have a match.
                var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
                using (bindingContext.EnterNestedScope(
                           modelMetadata: propertyMetadata,
                           fieldName: fieldName,
                           modelName: modelName,
                           model: null))
                {
                    // If any property can be bound from a value provider, then success.
                    if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
                    {
                        return(ValueProviderDataAvailable);
                    }
                }
            }

            if (performsConstructorBinding)
            {
                var parameters = bindingContext.ModelMetadata.BoundConstructor.BoundConstructorParameters;
                for (var i = 0; i < parameters.Count; i++)
                {
                    var parameterMetadata = parameters[i];
                    if (!CanBindItem(bindingContext, parameterMetadata))
                    {
                        continue;
                    }

                    // If any parameter can be bound from a greedy binding source, then success.
                    var bindingSource = parameterMetadata.BindingSource;
                    if (bindingSource != null && bindingSource.IsGreedy)
                    {
                        hasGreedyBinders = true;
                        continue;
                    }

                    // Otherwise, check whether the (perhaps filtered) value providers have a match.
                    var fieldName = parameterMetadata.BinderModelName ?? parameterMetadata.ParameterName;
                    var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
                    using (bindingContext.EnterNestedScope(
                               modelMetadata: parameterMetadata,
                               fieldName: fieldName,
                               modelName: modelName,
                               model: null))
                    {
                        // If any parameter can be bound from a value provider, then success.
                        if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
                        {
                            return(ValueProviderDataAvailable);
                        }
                    }
                }
            }

            if (hasGreedyBinders)
            {
                return(GreedyPropertiesMayHaveData);
            }

            _logger.CannotBindToComplexType(bindingContext);

            return(NoDataAvailable);
        }
コード例 #16
0
        /// <inheritdoc />
        public override async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            await base.BindModelAsync(bindingContext);

            if (!bindingContext.Result.IsModelSet)
            {
                // No match for the prefix at all.
                return;
            }

            var result = bindingContext.Result;

            Debug.Assert(result.Model != null);
            var model = (IDictionary <TKey, TValue>)result.Model;

            if (model.Count != 0)
            {
                // ICollection<KeyValuePair<TKey, TValue>> approach was successful.
                return;
            }

            Logger.NoKeyValueFormatForDictionaryModelBinder(bindingContext);

            if (!(bindingContext.ValueProvider is IEnumerableValueProvider enumerableValueProvider))
            {
                // No IEnumerableValueProvider available for the fallback approach. For example the user may have
                // replaced the ValueProvider with something other than a CompositeValueProvider.
                if (bindingContext.IsTopLevelObject)
                {
                    AddErrorIfBindingRequired(bindingContext);
                }

                return;
            }

            // Attempt to bind dictionary from a set of prefix[key]=value entries. Get the short and long keys first.
            var prefix = bindingContext.ModelName;
            var keys   = enumerableValueProvider.GetKeysFromPrefix(prefix);

            if (keys.Count == 0)
            {
                // No entries with the expected keys.
                if (bindingContext.IsTopLevelObject)
                {
                    AddErrorIfBindingRequired(bindingContext);
                }

                return;
            }

            // Update the existing successful but empty ModelBindingResult.
            var elementMetadata = bindingContext.ModelMetadata.ElementMetadata;
            var valueMetadata   = elementMetadata.Properties[nameof(KeyValuePair <TKey, TValue> .Value)];

            var keyMappings = new Dictionary <string, TKey>(StringComparer.Ordinal);

            foreach (var kvp in keys)
            {
                // Use InvariantCulture to convert the key since ExpressionHelper.GetExpressionText() would use
                // that culture when rendering a form.
                var convertedKey = ModelBindingHelper.ConvertTo <TKey>(kvp.Key, culture: null);

                using (bindingContext.EnterNestedScope(
                           modelMetadata: valueMetadata,
                           fieldName: bindingContext.FieldName,
                           modelName: kvp.Value,
                           model: null))
                {
                    await _valueBinder.BindModelAsync(bindingContext);

                    var valueResult = bindingContext.Result;
                    if (!valueResult.IsModelSet)
                    {
                        // Factories for IKeyRewriterValueProvider implementations are not all-or-nothing i.e.
                        // "[key][propertyName]" may be rewritten as ".key.propertyName" or "[key].propertyName". Try
                        // again in case this scope is binding a complex type and rewriting
                        // landed on ".key.propertyName" or in case this scope is binding another collection and an
                        // IKeyRewriterValueProvider implementation was first (hiding the original "[key][next key]").
                        if (kvp.Value.EndsWith("]"))
                        {
                            bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, kvp.Key);
                        }
                        else
                        {
                            bindingContext.ModelName = ModelNames.CreateIndexModelName(prefix, kvp.Key);
                        }

                        await _valueBinder.BindModelAsync(bindingContext);

                        valueResult = bindingContext.Result;
                    }

                    // Always add an entry to the dictionary but validate only if binding was successful.
                    model[convertedKey] = ModelBindingHelper.CastOrDefault <TValue>(valueResult.Model);
                    keyMappings.Add(bindingContext.ModelName, convertedKey);
                }
            }

            bindingContext.ValidationState.Add(model, new ValidationStateEntry()
            {
                Strategy = new ShortFormDictionaryValidationStrategy <TKey, TValue>(keyMappings, valueMetadata),
            });
        }
コード例 #17
0
        /// <summary>
        /// Validates a single node in a model object graph.
        /// </summary>
        /// <returns><c>true</c> if the node is valid, otherwise <c>false</c>.</returns>
        protected virtual bool ValidateNode()
        {
            var state = ModelState.GetValidationState(Key);

            // Rationale: we might see the same model state key used for two different objects.
            // We want to run validation unless it's already known that this key is invalid.
            if (state != ModelValidationState.Invalid)
            {
                var validators = Cache.GetValidators(Metadata, ValidatorProvider);

                var count = validators.Count;
                if (count > 0)
                {
                    var context = new ModelValidationContext(
                        Context,
                        Metadata,
                        MetadataProvider,
                        Container,
                        Model);

                    var results = new List <ModelValidationResult>();
                    for (var i = 0; i < count; i++)
                    {
                        results.AddRange(validators[i].Validate(context));
                    }

                    var resultsCount = results.Count;
                    for (var i = 0; i < resultsCount; i++)
                    {
                        var result = results[i];
                        var key    = ModelNames.CreatePropertyModelName(Key, result.MemberName);

                        // If this is a top-level parameter/property, the key would be empty,
                        // so use the name of the top-level property
                        if (string.IsNullOrEmpty(key) && Metadata.PropertyName != null)
                        {
                            key = Metadata.PropertyName;
                        }

                        ModelState.TryAddModelError(key, result.Message);
                    }
                }
            }

            state = ModelState.GetFieldValidationState(Key);
            if (state == ModelValidationState.Invalid)
            {
                return(false);
            }
            else
            {
                // If the field has an entry in ModelState, then record it as valid. Don't create
                // extra entries if they don't exist already.
                var entry = ModelState[Key];
                if (entry != null)
                {
                    entry.ValidationState = ModelValidationState.Valid;
                }

                return(true);
            }
        }
コード例 #18
0
        protected override bool ValidateNode()
        {
            // private field workaround...
            var _key       = _keyGetter(this);
            var _metadata  = _metadataGetter(this);
            var _container = _containerGetter(this);
            var _model     = _modelGetter(this);

            // This method is dupliated from MVC's VlaidationVisitor.cs
            // They only validate if validationstate != invalid, but if there's a FV validator, we want to override this behaviour.

            var fluentValidator = _validatorFactory.GetValidator(_metadata.ModelType);

            var state = _modelState.GetValidationState(_key);

            // Rationale: we might see the same model state key used for two different objects.
            // We want to run validation unless it's already known that this key is invalid.
            if (state != ModelValidationState.Invalid || fluentValidator != null)
            {
                var validators = _validatorCache.GetValidators(_metadata, _validatorProvider).ToList();

                if (fluentValidator != null)
                {
                    validators.Add(new FluentValidationModelValidator(fluentValidator, _cutomizations));
                }

                var count = validators.Count;
                if (count > 0)
                {
                    var context = new ModelValidationContext(
                        _actionContext,
                        _metadata,
                        _metadataProvider,
                        _container,
                        _model);

                    var results = new List <ModelValidationResult>();
                    for (var i = 0; i < count; i++)
                    {
                        results.AddRange(validators[i].Validate(context));
                    }

                    var resultsCount = results.Count;
                    for (var i = 0; i < resultsCount; i++)
                    {
                        var result = results[i];
                        var key    = ModelNames.CreatePropertyModelName(_key, result.MemberName);
                        _modelState.TryAddModelError(key, result.Message);
                    }
                }
            }

            state = _modelState.GetFieldValidationState(_key);

            if (state == ModelValidationState.Invalid)
            {
                return(false);
            }
            else
            {
                // If the field has an entry in ModelState, then record it as valid. Don't create
                // extra entries if they don't exist already.
                var entry = _modelState[_key];
                if (entry != null)
                {
                    entry.ValidationState = ModelValidationState.Valid;
                }

                return(true);
            }
        }
コード例 #19
0
ファイル: ComplexTypeModelBinder.cs プロジェクト: yunly/Mvc
        private async Task BindModelCoreAsync(ModelBindingContext bindingContext)
        {
            // Create model first (if necessary) to avoid reporting errors about properties when activation fails.
            if (bindingContext.Model == null)
            {
                bindingContext.Model = CreateModel(bindingContext);
            }

            var modelMetadata            = bindingContext.ModelMetadata;
            var attemptedPropertyBinding = false;

            for (var i = 0; i < modelMetadata.Properties.Count; i++)
            {
                var property = modelMetadata.Properties[i];
                if (!CanBindProperty(bindingContext, property))
                {
                    continue;
                }

                // Pass complex (including collection) values down so that binding system does not unnecessarily
                // recreate instances or overwrite inner properties that are not bound. No need for this with simple
                // values because they will be overwritten if binding succeeds. Arrays are never reused because they
                // cannot be resized.
                object propertyModel = null;
                if (property.PropertyGetter != null &&
                    property.IsComplexType &&
                    !property.ModelType.IsArray)
                {
                    propertyModel = property.PropertyGetter(bindingContext.Model);
                }

                var fieldName = property.BinderModelName ?? property.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);

                ModelBindingResult result;
                using (bindingContext.EnterNestedScope(
                           modelMetadata: property,
                           fieldName: fieldName,
                           modelName: modelName,
                           model: propertyModel))
                {
                    await BindProperty(bindingContext);

                    result = bindingContext.Result;
                }

                if (result.IsModelSet)
                {
                    attemptedPropertyBinding = true;
                    SetProperty(bindingContext, modelName, property, result);
                }
                else if (property.IsBindingRequired)
                {
                    attemptedPropertyBinding = true;
                    var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
                    bindingContext.ModelState.TryAddModelError(modelName, message);
                }
            }

            // Have we created a top-level model despite an inability to bind anything in said model and a lack of
            // other IsBindingRequired errors? Does that violate [BindRequired] on the model? This case occurs when
            // 1. The top-level model has no public settable properties.
            // 2. All properties in a [BindRequired] model have [BindNever] or are otherwise excluded from binding.
            // 3. No data exists for any property.
            if (AllowValidatingTopLevelNodes &&
                !attemptedPropertyBinding &&
                bindingContext.IsTopLevelObject &&
                modelMetadata.IsBindingRequired)
            {
                var messageProvider = modelMetadata.ModelBindingMessageProvider;
                var message         = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName);
                bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
            }

            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
            _logger.DoneAttemptingToBindModel(bindingContext);
        }