| File: ModelBinding\DefaultModelBindingContext.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ModelBinding; /// <summary> /// A context that contains operating information for model binding and validation. /// </summary> public class DefaultModelBindingContext : ModelBindingContext { private static readonly IValueProvider EmptyValueProvider = new CompositeValueProvider(); private IValueProvider _originalValueProvider = default!; private ActionContext _actionContext = default!; private ModelStateDictionary _modelState = default!; private ValidationStateDictionary _validationState = default!; private int? _maxModelBindingRecursionDepth; private State _state; private readonly Stack<State> _stack = new Stack<State>(); /// <inheritdoc /> public override ActionContext ActionContext { get { return _actionContext; } set { ArgumentNullException.ThrowIfNull(value); _actionContext = value; } } /// <inheritdoc /> public override string FieldName { get { return _state.FieldName; } set { ArgumentNullException.ThrowIfNull(value); _state.FieldName = value; } } /// <inheritdoc /> public override object? Model { get { return _state.Model; } set { _state.Model = value; } } /// <inheritdoc /> public override ModelMetadata ModelMetadata { get { return _state.ModelMetadata; } set { ArgumentNullException.ThrowIfNull(value); _state.ModelMetadata = value; } } /// <inheritdoc /> public override string ModelName { get { return _state.ModelName; } set { ArgumentNullException.ThrowIfNull(value); _state.ModelName = value; } } /// <inheritdoc /> public override ModelStateDictionary ModelState { get { return _modelState; } set { ArgumentNullException.ThrowIfNull(value); _modelState = value; } } /// <inheritdoc /> public override string? BinderModelName { get => _state.BinderModelName; set => _state.BinderModelName = value; } /// <inheritdoc /> public override BindingSource? BindingSource { get { return _state.BindingSource; } set { _state.BindingSource = value; } } /// <inheritdoc /> public override bool IsTopLevelObject { get { return _state.IsTopLevelObject; } set { _state.IsTopLevelObject = value; } } /// <summary> /// Gets or sets the original value provider to be used when value providers are not filtered. /// </summary> public IValueProvider OriginalValueProvider { get { return _originalValueProvider; } set { ArgumentNullException.ThrowIfNull(value); _originalValueProvider = value; } } /// <inheritdoc /> public override IValueProvider ValueProvider { get { return _state.ValueProvider; } set { ArgumentNullException.ThrowIfNull(value); _state.ValueProvider = value; } } /// <inheritdoc /> public override Func<ModelMetadata, bool>? PropertyFilter { get { return _state.PropertyFilter; } set { _state.PropertyFilter = value; } } /// <inheritdoc /> public override ValidationStateDictionary ValidationState { get { return _validationState; } set { ArgumentNullException.ThrowIfNull(value); _validationState = value; } } /// <inheritdoc /> public override ModelBindingResult Result { get { return _state.Result; } set { _state.Result = value; } } private int MaxModelBindingRecursionDepth { get { if (!_maxModelBindingRecursionDepth.HasValue) { // Ignore incomplete initialization. This must be a test scenario because CreateBindingContext(...) // has not been called or was called without MvcOptions in the service provider. _maxModelBindingRecursionDepth = MvcOptions.DefaultMaxModelBindingRecursionDepth; } return _maxModelBindingRecursionDepth.Value; } set { _maxModelBindingRecursionDepth = value; } } /// <summary> /// Creates a new <see cref="DefaultModelBindingContext"/> for top-level model binding operation. /// </summary> /// <param name="actionContext"> /// The <see cref="ActionContext"/> associated with the binding operation. /// </param> /// <param name="valueProvider">The <see cref="IValueProvider"/> to use for binding.</param> /// <param name="metadata"><see cref="ModelMetadata"/> associated with the model.</param> /// <param name="bindingInfo"><see cref="BindingInfo"/> associated with the model.</param> /// <param name="modelName">The name of the property or parameter being bound.</param> /// <returns>A new instance of <see cref="DefaultModelBindingContext"/>.</returns> public static ModelBindingContext CreateBindingContext( ActionContext actionContext, IValueProvider valueProvider, ModelMetadata metadata, BindingInfo? bindingInfo, string modelName) { ArgumentNullException.ThrowIfNull(actionContext); ArgumentNullException.ThrowIfNull(valueProvider); ArgumentNullException.ThrowIfNull(metadata); ArgumentNullException.ThrowIfNull(modelName); var binderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName; var bindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource; var propertyFilterProvider = bindingInfo?.PropertyFilterProvider ?? metadata.PropertyFilterProvider; var bindingContext = new DefaultModelBindingContext() { ActionContext = actionContext, BinderModelName = binderModelName, BindingSource = bindingSource, PropertyFilter = propertyFilterProvider?.PropertyFilter, ValidationState = new ValidationStateDictionary(), // Because this is the top-level context, FieldName and ModelName should be the same. FieldName = binderModelName ?? modelName, ModelName = binderModelName ?? modelName, OriginalModelName = binderModelName ?? modelName, IsTopLevelObject = true, ModelMetadata = metadata, ModelState = actionContext.ModelState, OriginalValueProvider = valueProvider, ValueProvider = FilterValueProvider(valueProvider, bindingSource), }; // mvcOptions may be null when this method is called in test scenarios. var mvcOptions = actionContext.HttpContext.RequestServices?.GetService<IOptions<MvcOptions>>(); if (mvcOptions != null) { bindingContext.MaxModelBindingRecursionDepth = mvcOptions.Value.MaxModelBindingRecursionDepth; } return bindingContext; } /// <inheritdoc /> public override NestedScope EnterNestedScope( ModelMetadata modelMetadata, string fieldName, string modelName, object? model) { ArgumentNullException.ThrowIfNull(modelMetadata); ArgumentNullException.ThrowIfNull(fieldName); ArgumentNullException.ThrowIfNull(modelName); var scope = EnterNestedScope(); // Only filter if the new BindingSource affects the value providers. Otherwise we want // to preserve the current state. if (modelMetadata.BindingSource != null && !modelMetadata.BindingSource.IsGreedy) { ValueProvider = FilterValueProvider(OriginalValueProvider, modelMetadata.BindingSource); } Model = model; ModelMetadata = modelMetadata; ModelName = modelName; FieldName = fieldName; BinderModelName = modelMetadata.BinderModelName; BindingSource = modelMetadata.BindingSource; PropertyFilter = modelMetadata.PropertyFilterProvider?.PropertyFilter; IsTopLevelObject = false; return scope; } /// <inheritdoc /> public override NestedScope EnterNestedScope() { _stack.Push(_state); // Would this new scope (which isn't in _stack) exceed the allowed recursion depth? That is, has the model // binding system already nested MaxModelBindingRecursionDepth binders? if (_stack.Count >= MaxModelBindingRecursionDepth) { // Find the root of this deeply-nested model. var states = _stack.ToArray(); var rootModelType = states[states.Length - 1].ModelMetadata.ModelType; throw new InvalidOperationException(Resources.FormatModelBinding_ExceededMaxModelBindingRecursionDepth( nameof(MvcOptions), nameof(MvcOptions.MaxModelBindingRecursionDepth), MaxModelBindingRecursionDepth, rootModelType)); } Result = default; return new NestedScope(this); } /// <inheritdoc /> protected override void ExitNestedScope() { _state = _stack.Pop(); } private static IValueProvider FilterValueProvider(IValueProvider valueProvider, BindingSource? bindingSource) { if (bindingSource == null || bindingSource.IsGreedy) { return valueProvider; } if (valueProvider is not IBindingSourceValueProvider bindingSourceValueProvider) { return valueProvider; } return bindingSourceValueProvider.Filter(bindingSource) ?? EmptyValueProvider; } private struct State { public string FieldName; public object? Model; public ModelMetadata ModelMetadata; public string ModelName; public IValueProvider ValueProvider; public Func<ModelMetadata, bool>? PropertyFilter; public string? BinderModelName; public BindingSource? BindingSource; public bool IsTopLevelObject; public ModelBindingResult Result; } }