// 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 System.Linq; #if NETSTANDARD1_3 using System.Reflection; #endif using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc { /// /// This attribute can be used on action parameters and types, to indicate model level metadata. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingPredicateProvider { private static readonly Func _defaultFilter = (context, propertyName) => true; private ObjectFactory _factory; private Func _predicateFromInclude; /// /// Creates a new instace of . /// /// Names of parameters to include in binding. public BindAttribute(params string[] include) { var items = new List(); foreach (var item in include) { items.AddRange(SplitString(item)); } Include = items.ToArray(); } /// /// Creates a new instance of . /// /// The type which implements /// . /// public BindAttribute(Type predicateProviderType) { if (predicateProviderType == null) { throw new ArgumentNullException(nameof(predicateProviderType)); } if (!typeof(IPropertyBindingPredicateProvider).IsAssignableFrom(predicateProviderType)) { var message = Resources.FormatPropertyBindingPredicateProvider_WrongType( predicateProviderType.FullName, typeof(IPropertyBindingPredicateProvider).FullName); throw new ArgumentException(message, nameof(predicateProviderType)); } PredicateProviderType = predicateProviderType; } /// public Type PredicateProviderType { get; } /// /// Gets the names of properties to include in model binding. /// public string[] Include { get; } /// /// Allows a user to specify a particular prefix to match during model binding. /// // This property is exposed for back compat reasons. public string Prefix { get; set; } /// /// Represents the model name used during model binding. /// string IModelNameProvider.Name { get { return Prefix; } } /// public Func PropertyFilter { get { if (PredicateProviderType != null) { var factory = GetFactory(); return CreatePredicateFromProviderType(factory); } else if (Include != null && Include.Length > 0) { if (_predicateFromInclude == null) { _predicateFromInclude = (context, propertyName) => Include.Contains(propertyName, StringComparer.Ordinal); } return _predicateFromInclude; } else { return _defaultFilter; } } } private ObjectFactory GetFactory() { if (_factory == null) { _factory = ActivatorUtilities.CreateFactory(PredicateProviderType, Type.EmptyTypes); } return _factory; } private static Func CreatePredicateFromProviderType( ObjectFactory factory) { // Holding state to avoid execessive creation of the provider. var initialized = false; Func predicate = null; return (ModelBindingContext context, string propertyName) => { if (!initialized) { var services = context.OperationBindingContext.HttpContext.RequestServices; var provider = (IPropertyBindingPredicateProvider)factory(services, arguments: null); initialized = true; predicate = provider.PropertyFilter ?? _defaultFilter; } return predicate(context, propertyName); }; } private static IEnumerable SplitString(string original) { if (string.IsNullOrEmpty(original)) { return new string[0]; } var split = original.Split(',').Select(piece => piece.Trim()).Where(piece => !string.IsNullOrEmpty(piece)); return split; } } }