|
// 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 System.Collections.ObjectModel;
namespace Microsoft.AspNetCore.Mvc.ModelBinding;
/// <summary>
/// Represents a <see cref="IValueProvider"/> whose values come from a collection of <see cref="IValueProvider"/>s.
/// </summary>
public class CompositeValueProvider :
Collection<IValueProvider>,
IEnumerableValueProvider,
IBindingSourceValueProvider,
IKeyRewriterValueProvider
{
/// <summary>
/// Initializes a new instance of <see cref="CompositeValueProvider"/>.
/// </summary>
public CompositeValueProvider()
{
}
/// <summary>
/// Initializes a new instance of <see cref="CompositeValueProvider"/>.
/// </summary>
/// <param name="valueProviders">The sequence of <see cref="IValueProvider"/> to add to this instance of
/// <see cref="CompositeValueProvider"/>.</param>
public CompositeValueProvider(IList<IValueProvider> valueProviders)
: base(valueProviders)
{
}
/// <summary>
/// Asynchronously creates a <see cref="CompositeValueProvider"/> using the provided
/// <paramref name="controllerContext"/>.
/// </summary>
/// <param name="controllerContext">The <see cref="ControllerContext"/> associated with the current request.</param>
/// <returns>
/// A <see cref="Task{TResult}"/> which, when completed, asynchronously returns a
/// <see cref="CompositeValueProvider"/>.
/// </returns>
public static async Task<CompositeValueProvider> CreateAsync(ControllerContext controllerContext)
{
ArgumentNullException.ThrowIfNull(controllerContext);
var factories = controllerContext.ValueProviderFactories;
return await CreateAsync(controllerContext, factories);
}
/// <summary>
/// Asynchronously creates a <see cref="CompositeValueProvider"/> using the provided
/// <paramref name="actionContext"/>.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param>
/// <param name="factories">The <see cref="IValueProviderFactory"/> to be applied to the context.</param>
/// <returns>
/// A <see cref="Task{TResult}"/> which, when completed, asynchronously returns a
/// <see cref="CompositeValueProvider"/>.
/// </returns>
public static async Task<CompositeValueProvider> CreateAsync(
ActionContext actionContext,
IList<IValueProviderFactory> factories)
{
var valueProviderFactoryContext = new ValueProviderFactoryContext(actionContext);
for (var i = 0; i < factories.Count; i++)
{
var factory = factories[i];
await factory.CreateValueProviderAsync(valueProviderFactoryContext);
}
return new CompositeValueProvider(valueProviderFactoryContext.ValueProviders);
}
internal static async ValueTask<(bool success, CompositeValueProvider? valueProvider)> TryCreateAsync(
ActionContext actionContext,
IList<IValueProviderFactory> factories)
{
try
{
var valueProvider = await CreateAsync(actionContext, factories);
return (true, valueProvider);
}
catch (ValueProviderException exception)
{
actionContext.ModelState.TryAddModelException(key: string.Empty, exception);
return (false, null);
}
}
/// <inheritdoc />
public virtual bool ContainsPrefix(string prefix)
{
for (var i = 0; i < Count; i++)
{
if (this[i].ContainsPrefix(prefix))
{
return true;
}
}
return false;
}
/// <inheritdoc />
public virtual ValueProviderResult GetValue(string key)
{
// Performance-sensitive
// Caching the count is faster for IList<T>
var itemCount = Items.Count;
for (var i = 0; i < itemCount; i++)
{
var valueProvider = Items[i];
var result = valueProvider.GetValue(key);
if (result != ValueProviderResult.None)
{
return result;
}
}
return ValueProviderResult.None;
}
/// <inheritdoc />
public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
{
foreach (var valueProvider in this)
{
if (valueProvider is IEnumerableValueProvider enumeratedProvider)
{
var result = enumeratedProvider.GetKeysFromPrefix(prefix);
if (result != null && result.Count > 0)
{
return result;
}
}
}
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc />
protected override void InsertItem(int index, IValueProvider item)
{
ArgumentNullException.ThrowIfNull(item);
base.InsertItem(index, item);
}
/// <inheritdoc />
protected override void SetItem(int index, IValueProvider item)
{
ArgumentNullException.ThrowIfNull(item);
base.SetItem(index, item);
}
/// <inheritdoc />
public IValueProvider? Filter(BindingSource bindingSource)
{
ArgumentNullException.ThrowIfNull(bindingSource);
var shouldFilter = false;
for (var i = 0; i < Count; i++)
{
var valueProvider = Items[i];
if (valueProvider is IBindingSourceValueProvider)
{
shouldFilter = true;
break;
}
}
if (!shouldFilter)
{
// No inner IBindingSourceValueProvider implementations. Result will be empty.
return null;
}
var filteredValueProviders = new List<IValueProvider>();
for (var i = 0; i < Count; i++)
{
var valueProvider = Items[i];
if (valueProvider is IBindingSourceValueProvider bindingSourceValueProvider)
{
var result = bindingSourceValueProvider.Filter(bindingSource);
if (result != null)
{
filteredValueProviders.Add(result);
}
}
}
if (filteredValueProviders.Count == 0)
{
// Do not create an empty CompositeValueProvider.
return null;
}
return new CompositeValueProvider(filteredValueProviders);
}
/// <inheritdoc />
/// <remarks>
/// Value providers are included by default. If a contained <see cref="IValueProvider"/> does not implement
/// <see cref="IKeyRewriterValueProvider"/>, <see cref="Filter()"/> will not remove it.
/// </remarks>
public IValueProvider? Filter()
{
var shouldFilter = false;
for (var i = 0; i < Count; i++)
{
var valueProvider = Items[i];
if (valueProvider is IKeyRewriterValueProvider)
{
shouldFilter = true;
break;
}
}
if (!shouldFilter)
{
// No inner IKeyRewriterValueProvider implementations. Nothing to exclude.
return this;
}
var filteredValueProviders = new List<IValueProvider>();
for (var i = 0; i < Count; i++)
{
var valueProvider = Items[i];
if (valueProvider is IKeyRewriterValueProvider keyRewriterValueProvider)
{
var result = keyRewriterValueProvider.Filter();
if (result != null)
{
filteredValueProviders.Add(result);
}
}
else
{
// Assume value providers that aren't rewriter-aware do not rewrite their keys.
filteredValueProviders.Add(valueProvider);
}
}
if (filteredValueProviders.Count == 0)
{
// Do not create an empty CompositeValueProvider.
return null;
}
return new CompositeValueProvider(filteredValueProviders);
}
}
|