You've already forked linux-packaging-mono
410 lines
15 KiB
C#
410 lines
15 KiB
C#
![]() |
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
|
|||
|
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.ComponentModel;
|
|||
|
using System.Diagnostics.CodeAnalysis;
|
|||
|
using System.Globalization;
|
|||
|
using System.Linq;
|
|||
|
using System.Linq.Expressions;
|
|||
|
using System.Reflection;
|
|||
|
using System.Web.Mvc.ExpressionUtil;
|
|||
|
using System.Web.Mvc.Properties;
|
|||
|
|
|||
|
namespace System.Web.Mvc
|
|||
|
{
|
|||
|
public class ModelMetadata
|
|||
|
{
|
|||
|
public const int DefaultOrder = 10000;
|
|||
|
|
|||
|
private readonly Type _containerType;
|
|||
|
private readonly Type _modelType;
|
|||
|
private readonly string _propertyName;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Explicit backing store for the things we want initialized by default, so don't have to call
|
|||
|
/// the protected virtual setters of an auto-generated property
|
|||
|
/// </summary>
|
|||
|
private Dictionary<string, object> _additionalValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|||
|
private bool _convertEmptyStringToNull = true;
|
|||
|
private bool _isRequired;
|
|||
|
private object _model;
|
|||
|
private Func<object> _modelAccessor;
|
|||
|
private int _order = DefaultOrder;
|
|||
|
private IEnumerable<ModelMetadata> _properties;
|
|||
|
private Type _realModelType;
|
|||
|
private bool _requestValidationEnabled = true;
|
|||
|
private bool _showForDisplay = true;
|
|||
|
private bool _showForEdit = true;
|
|||
|
private string _simpleDisplayText;
|
|||
|
|
|||
|
public ModelMetadata(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
|
|||
|
{
|
|||
|
if (provider == null)
|
|||
|
{
|
|||
|
throw new ArgumentNullException("provider");
|
|||
|
}
|
|||
|
if (modelType == null)
|
|||
|
{
|
|||
|
throw new ArgumentNullException("modelType");
|
|||
|
}
|
|||
|
|
|||
|
Provider = provider;
|
|||
|
|
|||
|
_containerType = containerType;
|
|||
|
_isRequired = !TypeHelpers.TypeAllowsNullValue(modelType);
|
|||
|
_modelAccessor = modelAccessor;
|
|||
|
_modelType = modelType;
|
|||
|
_propertyName = propertyName;
|
|||
|
}
|
|||
|
|
|||
|
public virtual Dictionary<string, object> AdditionalValues
|
|||
|
{
|
|||
|
get { return _additionalValues; }
|
|||
|
}
|
|||
|
|
|||
|
public Type ContainerType
|
|||
|
{
|
|||
|
get { return _containerType; }
|
|||
|
}
|
|||
|
|
|||
|
public virtual bool ConvertEmptyStringToNull
|
|||
|
{
|
|||
|
get { return _convertEmptyStringToNull; }
|
|||
|
set { _convertEmptyStringToNull = value; }
|
|||
|
}
|
|||
|
|
|||
|
public virtual string DataTypeName { get; set; }
|
|||
|
|
|||
|
public virtual string Description { get; set; }
|
|||
|
|
|||
|
public virtual string DisplayFormatString { get; set; }
|
|||
|
|
|||
|
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The method is a delegating helper to choose among multiple property values")]
|
|||
|
public virtual string DisplayName { get; set; }
|
|||
|
|
|||
|
public virtual string EditFormatString { get; set; }
|
|||
|
|
|||
|
public virtual bool HideSurroundingHtml { get; set; }
|
|||
|
|
|||
|
public virtual bool IsComplexType
|
|||
|
{
|
|||
|
get { return !(TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string))); }
|
|||
|
}
|
|||
|
|
|||
|
public bool IsNullableValueType
|
|||
|
{
|
|||
|
get { return TypeHelpers.IsNullableValueType(ModelType); }
|
|||
|
}
|
|||
|
|
|||
|
public virtual bool IsReadOnly { get; set; }
|
|||
|
|
|||
|
public virtual bool IsRequired
|
|||
|
{
|
|||
|
get { return _isRequired; }
|
|||
|
set { _isRequired = value; }
|
|||
|
}
|
|||
|
|
|||
|
public object Model
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (_modelAccessor != null)
|
|||
|
{
|
|||
|
_model = _modelAccessor();
|
|||
|
_modelAccessor = null;
|
|||
|
}
|
|||
|
return _model;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
_model = value;
|
|||
|
_modelAccessor = null;
|
|||
|
_properties = null;
|
|||
|
_realModelType = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public Type ModelType
|
|||
|
{
|
|||
|
get { return _modelType; }
|
|||
|
}
|
|||
|
|
|||
|
public virtual string NullDisplayText { get; set; }
|
|||
|
|
|||
|
public virtual int Order
|
|||
|
{
|
|||
|
get { return _order; }
|
|||
|
set { _order = value; }
|
|||
|
}
|
|||
|
|
|||
|
public virtual IEnumerable<ModelMetadata> Properties
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (_properties == null)
|
|||
|
{
|
|||
|
_properties = Provider.GetMetadataForProperties(Model, RealModelType).OrderBy(m => m.Order);
|
|||
|
}
|
|||
|
return _properties;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public string PropertyName
|
|||
|
{
|
|||
|
get { return _propertyName; }
|
|||
|
}
|
|||
|
|
|||
|
protected ModelMetadataProvider Provider { get; set; }
|
|||
|
|
|||
|
internal Type RealModelType
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (_realModelType == null)
|
|||
|
{
|
|||
|
_realModelType = ModelType;
|
|||
|
|
|||
|
// Don't call GetType() if the model is Nullable<T>, because it will
|
|||
|
// turn Nullable<T> into T for non-null values
|
|||
|
if (Model != null && !TypeHelpers.IsNullableValueType(ModelType))
|
|||
|
{
|
|||
|
_realModelType = Model.GetType();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return _realModelType;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public virtual bool RequestValidationEnabled
|
|||
|
{
|
|||
|
get { return _requestValidationEnabled; }
|
|||
|
set { _requestValidationEnabled = value; }
|
|||
|
}
|
|||
|
|
|||
|
public virtual string ShortDisplayName { get; set; }
|
|||
|
|
|||
|
public virtual bool ShowForDisplay
|
|||
|
{
|
|||
|
get { return _showForDisplay; }
|
|||
|
set { _showForDisplay = value; }
|
|||
|
}
|
|||
|
|
|||
|
public virtual bool ShowForEdit
|
|||
|
{
|
|||
|
get { return _showForEdit; }
|
|||
|
set { _showForEdit = value; }
|
|||
|
}
|
|||
|
|
|||
|
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This property delegates to the method when the user has not yet set a simple display text value.")]
|
|||
|
public virtual string SimpleDisplayText
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (_simpleDisplayText == null)
|
|||
|
{
|
|||
|
_simpleDisplayText = GetSimpleDisplayText();
|
|||
|
}
|
|||
|
return _simpleDisplayText;
|
|||
|
}
|
|||
|
set { _simpleDisplayText = value; }
|
|||
|
}
|
|||
|
|
|||
|
public virtual string TemplateHint { get; set; }
|
|||
|
|
|||
|
public virtual string Watermark { get; set; }
|
|||
|
|
|||
|
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
|
|||
|
public static ModelMetadata FromLambdaExpression<TParameter, TValue>(Expression<Func<TParameter, TValue>> expression,
|
|||
|
ViewDataDictionary<TParameter> viewData)
|
|||
|
{
|
|||
|
return FromLambdaExpression(expression, viewData, metadataProvider: null);
|
|||
|
}
|
|||
|
|
|||
|
internal static ModelMetadata FromLambdaExpression<TParameter, TValue>(Expression<Func<TParameter, TValue>> expression,
|
|||
|
ViewDataDictionary<TParameter> viewData,
|
|||
|
ModelMetadataProvider metadataProvider)
|
|||
|
{
|
|||
|
if (expression == null)
|
|||
|
{
|
|||
|
throw new ArgumentNullException("expression");
|
|||
|
}
|
|||
|
if (viewData == null)
|
|||
|
{
|
|||
|
throw new ArgumentNullException("viewData");
|
|||
|
}
|
|||
|
|
|||
|
string propertyName = null;
|
|||
|
Type containerType = null;
|
|||
|
bool legalExpression = false;
|
|||
|
|
|||
|
// Need to verify the expression is valid; it needs to at least end in something
|
|||
|
// that we can convert to a meaningful string for model binding purposes
|
|||
|
|
|||
|
switch (expression.Body.NodeType)
|
|||
|
{
|
|||
|
case ExpressionType.ArrayIndex:
|
|||
|
// ArrayIndex always means a single-dimensional indexer; multi-dimensional indexer is a method call to Get()
|
|||
|
legalExpression = true;
|
|||
|
break;
|
|||
|
|
|||
|
case ExpressionType.Call:
|
|||
|
// Only legal method call is a single argument indexer/DefaultMember call
|
|||
|
legalExpression = ExpressionHelper.IsSingleArgumentIndexer(expression.Body);
|
|||
|
break;
|
|||
|
|
|||
|
case ExpressionType.MemberAccess:
|
|||
|
// Property/field access is always legal
|
|||
|
MemberExpression memberExpression = (MemberExpression)expression.Body;
|
|||
|
propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
|
|||
|
containerType = memberExpression.Expression.Type;
|
|||
|
legalExpression = true;
|
|||
|
break;
|
|||
|
|
|||
|
case ExpressionType.Parameter:
|
|||
|
// Parameter expression means "model => model", so we delegate to FromModel
|
|||
|
return FromModel(viewData, metadataProvider);
|
|||
|
}
|
|||
|
|
|||
|
if (!legalExpression)
|
|||
|
{
|
|||
|
throw new InvalidOperationException(MvcResources.TemplateHelpers_TemplateLimitations);
|
|||
|
}
|
|||
|
|
|||
|
TParameter container = viewData.Model;
|
|||
|
Func<object> modelAccessor = () =>
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
return CachedExpressionCompiler.Process(expression)(container);
|
|||
|
}
|
|||
|
catch (NullReferenceException)
|
|||
|
{
|
|||
|
return null;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
return GetMetadataFromProvider(modelAccessor, typeof(TValue), propertyName, containerType, metadataProvider);
|
|||
|
}
|
|||
|
|
|||
|
private static ModelMetadata FromModel(ViewDataDictionary viewData, ModelMetadataProvider metadataProvider)
|
|||
|
{
|
|||
|
return viewData.ModelMetadata ?? GetMetadataFromProvider(null, typeof(string), null, null, metadataProvider);
|
|||
|
}
|
|||
|
|
|||
|
public static ModelMetadata FromStringExpression(string expression, ViewDataDictionary viewData)
|
|||
|
{
|
|||
|
return FromStringExpression(expression, viewData, metadataProvider: null);
|
|||
|
}
|
|||
|
|
|||
|
internal static ModelMetadata FromStringExpression(string expression, ViewDataDictionary viewData, ModelMetadataProvider metadataProvider)
|
|||
|
{
|
|||
|
if (expression == null)
|
|||
|
{
|
|||
|
throw new ArgumentNullException("expression");
|
|||
|
}
|
|||
|
if (viewData == null)
|
|||
|
{
|
|||
|
throw new ArgumentNullException("viewData");
|
|||
|
}
|
|||
|
if (expression.Length == 0)
|
|||
|
{
|
|||
|
// Empty string really means "model metadata for the current model"
|
|||
|
return FromModel(viewData, metadataProvider);
|
|||
|
}
|
|||
|
|
|||
|
ViewDataInfo vdi = viewData.GetViewDataInfo(expression);
|
|||
|
Type containerType = null;
|
|||
|
Type modelType = null;
|
|||
|
Func<object> modelAccessor = null;
|
|||
|
string propertyName = null;
|
|||
|
|
|||
|
if (vdi != null)
|
|||
|
{
|
|||
|
if (vdi.Container != null)
|
|||
|
{
|
|||
|
containerType = vdi.Container.GetType();
|
|||
|
}
|
|||
|
|
|||
|
modelAccessor = () => vdi.Value;
|
|||
|
|
|||
|
if (vdi.PropertyDescriptor != null)
|
|||
|
{
|
|||
|
propertyName = vdi.PropertyDescriptor.Name;
|
|||
|
modelType = vdi.PropertyDescriptor.PropertyType;
|
|||
|
}
|
|||
|
else if (vdi.Value != null)
|
|||
|
{
|
|||
|
// We only need to delay accessing properties (for LINQ to SQL)
|
|||
|
modelType = vdi.Value.GetType();
|
|||
|
}
|
|||
|
}
|
|||
|
else if (viewData.ModelMetadata != null)
|
|||
|
{
|
|||
|
// Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
|
|||
|
ModelMetadata propertyMetadata = viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault();
|
|||
|
if (propertyMetadata != null)
|
|||
|
{
|
|||
|
return propertyMetadata;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return GetMetadataFromProvider(modelAccessor, modelType ?? typeof(string), propertyName, containerType, metadataProvider);
|
|||
|
}
|
|||
|
|
|||
|
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "The method is a delegating helper to choose among multiple property values")]
|
|||
|
public string GetDisplayName()
|
|||
|
{
|
|||
|
return DisplayName ?? PropertyName ?? ModelType.Name;
|
|||
|
}
|
|||
|
|
|||
|
private static ModelMetadata GetMetadataFromProvider(Func<object> modelAccessor, Type modelType, string propertyName, Type containerType, ModelMetadataProvider metadataProvider)
|
|||
|
{
|
|||
|
metadataProvider = metadataProvider ?? ModelMetadataProviders.Current;
|
|||
|
if (containerType != null && !String.IsNullOrEmpty(propertyName))
|
|||
|
{
|
|||
|
return metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName);
|
|||
|
}
|
|||
|
return metadataProvider.GetMetadataForType(modelAccessor, modelType);
|
|||
|
}
|
|||
|
|
|||
|
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method is used to resolve the simple display text when it was not explicitly set through other means.")]
|
|||
|
protected virtual string GetSimpleDisplayText()
|
|||
|
{
|
|||
|
if (Model == null)
|
|||
|
{
|
|||
|
return NullDisplayText;
|
|||
|
}
|
|||
|
|
|||
|
string toStringResult = Convert.ToString(Model, CultureInfo.CurrentCulture);
|
|||
|
if (toStringResult == null)
|
|||
|
{
|
|||
|
return String.Empty;
|
|||
|
}
|
|||
|
|
|||
|
if (!toStringResult.Equals(Model.GetType().FullName, StringComparison.Ordinal))
|
|||
|
{
|
|||
|
return toStringResult;
|
|||
|
}
|
|||
|
|
|||
|
ModelMetadata firstProperty = Properties.FirstOrDefault();
|
|||
|
if (firstProperty == null)
|
|||
|
{
|
|||
|
return String.Empty;
|
|||
|
}
|
|||
|
|
|||
|
if (firstProperty.Model == null)
|
|||
|
{
|
|||
|
return firstProperty.NullDisplayText;
|
|||
|
}
|
|||
|
|
|||
|
return Convert.ToString(firstProperty.Model, CultureInfo.CurrentCulture);
|
|||
|
}
|
|||
|
|
|||
|
public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context)
|
|||
|
{
|
|||
|
return ModelValidatorProviders.Providers.GetValidators(this, context);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|