// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
///
/// A context that contains operating information for model binding and validation.
///
public class DefaultModelBindingContext : ModelBindingContext
{
private static readonly IValueProvider EmptyValueProvider = new CompositeValueProvider();
private IValueProvider _originalValueProvider;
private ActionContext _actionContext;
private ModelStateDictionary _modelState;
private ValidationStateDictionary _validationState;
private State _state;
private readonly Stack _stack = new Stack();
///
public override ActionContext ActionContext
{
get { return _actionContext; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_actionContext = value;
}
}
///
public override string FieldName
{
get { return _state.FieldName; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_state.FieldName = value;
}
}
///
public override object Model
{
get { return _state.Model; }
set { _state.Model = value; }
}
///
public override ModelMetadata ModelMetadata
{
get { return _state.ModelMetadata; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_state.ModelMetadata = value;
}
}
///
public override string ModelName
{
get { return _state.ModelName; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_state.ModelName = value;
}
}
///
public override ModelStateDictionary ModelState
{
get { return _modelState; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_modelState = value;
}
}
///
public override string BinderModelName
{
get { return _state.BinderModelName; }
set { _state.BinderModelName = value; }
}
///
public override BindingSource BindingSource
{
get { return _state.BindingSource; }
set { _state.BindingSource = value; }
}
///
public override bool IsTopLevelObject
{
get { return _state.IsTopLevelObject; }
set { _state.IsTopLevelObject = value; }
}
///
/// Gets or sets the original value provider to be used when value providers are not filtered.
///
public IValueProvider OriginalValueProvider
{
get { return _originalValueProvider; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_originalValueProvider = value;
}
}
///
public override IValueProvider ValueProvider
{
get { return _state.ValueProvider; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_state.ValueProvider = value;
}
}
///
public override Func PropertyFilter
{
get { return _state.PropertyFilter; }
set { _state.PropertyFilter = value; }
}
///
public override ValidationStateDictionary ValidationState
{
get { return _validationState; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_validationState = value;
}
}
///
public override ModelBindingResult Result
{
get
{
return _state.Result;
}
set
{
_state.Result = value;
}
}
///
/// Creates a new for top-level model binding operation.
///
///
/// The associated with the binding operation.
///
/// The to use for binding.
/// associated with the model.
/// associated with the model.
/// The name of the property or parameter being bound.
/// A new instance of .
public static ModelBindingContext CreateBindingContext(
ActionContext actionContext,
IValueProvider valueProvider,
ModelMetadata metadata,
BindingInfo bindingInfo,
string modelName)
{
if (actionContext == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
if (modelName == null)
{
throw new ArgumentNullException(nameof(modelName));
}
var binderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName;
var propertyFilterProvider = bindingInfo?.PropertyFilterProvider ?? metadata.PropertyFilterProvider;
var bindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource;
return new DefaultModelBindingContext()
{
ActionContext = actionContext,
BinderModelName = binderModelName,
BindingSource = bindingSource,
PropertyFilter = propertyFilterProvider?.PropertyFilter,
// Because this is the top-level context, FieldName and ModelName should be the same.
FieldName = binderModelName ?? modelName,
ModelName = binderModelName ?? modelName,
IsTopLevelObject = true,
ModelMetadata = metadata,
ModelState = actionContext.ModelState,
OriginalValueProvider = valueProvider,
ValueProvider = FilterValueProvider(valueProvider, bindingSource),
ValidationState = new ValidationStateDictionary(),
};
}
///
public override NestedScope EnterNestedScope(
ModelMetadata modelMetadata,
string fieldName,
string modelName,
object model)
{
if (modelMetadata == null)
{
throw new ArgumentNullException(nameof(modelMetadata));
}
if (fieldName == null)
{
throw new ArgumentNullException(nameof(fieldName));
}
if (modelName == null)
{
throw new ArgumentNullException(nameof(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;
}
///
public override NestedScope EnterNestedScope()
{
_stack.Push(_state);
Result = default;
return new NestedScope(this);
}
///
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 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 PropertyFilter;
public string BinderModelName;
public BindingSource BindingSource;
public bool IsTopLevelObject;
public ModelBindingResult Result;
}
}
}