/Trunk/Sources/System.Web.Mvc/SystemWebMvc/Mvc/ModelMetadata.cs

# · C# · 376 lines · 307 code · 58 blank · 11 comment · 52 complexity · 3639b46eecbaf06468bdbdfc559391a3 MD5 · raw file

  1. namespace System.Web.Mvc {
  2. using System;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Linq.Expressions;
  9. using System.Reflection;
  10. using System.Web.Mvc.ExpressionUtil;
  11. using System.Web.Mvc.Resources;
  12. public class ModelMetadata {
  13. // Explicit backing store for the things we want initialized by default, so don't have to call
  14. // the protected virtual setters of an auto-generated property
  15. private Dictionary<string, object> _additionalValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
  16. private readonly Type _containerType;
  17. private bool _convertEmptyStringToNull = true;
  18. private bool _isRequired;
  19. private object _model;
  20. private Func<object> _modelAccessor;
  21. private readonly Type _modelType;
  22. private IEnumerable<ModelMetadata> _properties;
  23. private readonly string _propertyName;
  24. private Type _realModelType;
  25. private bool _showForDisplay = true;
  26. private bool _showForEdit = true;
  27. private string _simpleDisplayText;
  28. public ModelMetadata(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {
  29. if (provider == null) {
  30. throw new ArgumentNullException("provider");
  31. }
  32. if (modelType == null) {
  33. throw new ArgumentNullException("modelType");
  34. }
  35. Provider = provider;
  36. _containerType = containerType;
  37. _isRequired = !TypeHelpers.TypeAllowsNullValue(modelType);
  38. _modelAccessor = modelAccessor;
  39. _modelType = modelType;
  40. _propertyName = propertyName;
  41. }
  42. public virtual Dictionary<string, object> AdditionalValues {
  43. get {
  44. return _additionalValues;
  45. }
  46. }
  47. public Type ContainerType {
  48. get {
  49. return _containerType;
  50. }
  51. }
  52. public virtual bool ConvertEmptyStringToNull {
  53. get {
  54. return _convertEmptyStringToNull;
  55. }
  56. set {
  57. _convertEmptyStringToNull = value;
  58. }
  59. }
  60. public virtual string DataTypeName {
  61. get;
  62. set;
  63. }
  64. public virtual string Description {
  65. get;
  66. set;
  67. }
  68. public virtual string DisplayFormatString {
  69. get;
  70. set;
  71. }
  72. [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The method is a delegating helper to choose among multiple property values")]
  73. public virtual string DisplayName {
  74. get;
  75. set;
  76. }
  77. public virtual string EditFormatString {
  78. get;
  79. set;
  80. }
  81. public virtual bool HideSurroundingHtml {
  82. get;
  83. set;
  84. }
  85. public virtual bool IsComplexType {
  86. get {
  87. return !(TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string)));
  88. }
  89. }
  90. public bool IsNullableValueType {
  91. get {
  92. return TypeHelpers.IsNullableValueType(ModelType);
  93. }
  94. }
  95. public virtual bool IsReadOnly {
  96. get;
  97. set;
  98. }
  99. public virtual bool IsRequired {
  100. get {
  101. return _isRequired;
  102. }
  103. set {
  104. _isRequired = value;
  105. }
  106. }
  107. public object Model {
  108. get {
  109. if (_modelAccessor != null) {
  110. _model = _modelAccessor();
  111. _modelAccessor = null;
  112. }
  113. return _model;
  114. }
  115. set {
  116. _model = value;
  117. _modelAccessor = null;
  118. }
  119. }
  120. public Type ModelType {
  121. get {
  122. return _modelType;
  123. }
  124. }
  125. public virtual string NullDisplayText { get; set; }
  126. public virtual IEnumerable<ModelMetadata> Properties {
  127. get {
  128. if (_properties == null) {
  129. _properties = Provider.GetMetadataForProperties(Model, RealModelType);
  130. }
  131. return _properties;
  132. }
  133. }
  134. public string PropertyName {
  135. get {
  136. return _propertyName;
  137. }
  138. }
  139. protected ModelMetadataProvider Provider {
  140. get;
  141. set;
  142. }
  143. internal Type RealModelType {
  144. get {
  145. if (_realModelType == null) {
  146. _realModelType = ModelType;
  147. // Don't call GetType() if the model is Nullable<T>, because it will
  148. // turn Nullable<T> into T for non-null values
  149. if (Model != null && !TypeHelpers.IsNullableValueType(ModelType)) {
  150. _realModelType = Model.GetType();
  151. }
  152. }
  153. return _realModelType;
  154. }
  155. }
  156. public virtual string ShortDisplayName {
  157. get;
  158. set;
  159. }
  160. public virtual bool ShowForDisplay {
  161. get {
  162. return _showForDisplay;
  163. }
  164. set {
  165. _showForDisplay = value;
  166. }
  167. }
  168. public virtual bool ShowForEdit {
  169. get {
  170. return _showForEdit;
  171. }
  172. set {
  173. _showForEdit = value;
  174. }
  175. }
  176. [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This property delegates to the method when the user has not yet set a simple display text value.")]
  177. public virtual string SimpleDisplayText {
  178. get {
  179. if (_simpleDisplayText == null) {
  180. _simpleDisplayText = GetSimpleDisplayText();
  181. }
  182. return _simpleDisplayText;
  183. }
  184. set {
  185. _simpleDisplayText = value;
  186. }
  187. }
  188. public virtual string TemplateHint {
  189. get;
  190. set;
  191. }
  192. public virtual string Watermark {
  193. get;
  194. set;
  195. }
  196. [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
  197. public static ModelMetadata FromLambdaExpression<TParameter, TValue>(Expression<Func<TParameter, TValue>> expression,
  198. ViewDataDictionary<TParameter> viewData) {
  199. if (expression == null) {
  200. throw new ArgumentNullException("expression");
  201. }
  202. if (viewData == null) {
  203. throw new ArgumentNullException("viewData");
  204. }
  205. string propertyName = null;
  206. Type containerType = null;
  207. bool legalExpression = false;
  208. // Need to verify the expression is valid; it needs to at least end in something
  209. // that we can convert to a meaningful string for model binding purposes
  210. switch (expression.Body.NodeType) {
  211. // ArrayIndex always means a single-dimensional indexer; multi-dimensional indexer is a method call to Get()
  212. case ExpressionType.ArrayIndex:
  213. legalExpression = true;
  214. break;
  215. // Only legal method call is a single argument indexer/DefaultMember call
  216. case ExpressionType.Call:
  217. legalExpression = ExpressionHelper.IsSingleArgumentIndexer(expression.Body);
  218. break;
  219. // Property/field access is always legal
  220. case ExpressionType.MemberAccess:
  221. MemberExpression memberExpression = (MemberExpression)expression.Body;
  222. propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
  223. containerType = memberExpression.Member.DeclaringType;
  224. legalExpression = true;
  225. break;
  226. // Parameter expression means "model => model", so we delegate to FromModel
  227. case ExpressionType.Parameter:
  228. return FromModel(viewData);
  229. }
  230. if (!legalExpression) {
  231. throw new InvalidOperationException(MvcResources.TemplateHelpers_TemplateLimitations);
  232. }
  233. TParameter container = viewData.Model;
  234. Func<object> modelAccessor = () =>
  235. {
  236. try {
  237. return CachedExpressionCompiler.Process(expression)(container);
  238. }
  239. catch (NullReferenceException) {
  240. return null;
  241. }
  242. };
  243. return GetMetadataFromProvider(modelAccessor, typeof(TValue), propertyName, containerType);
  244. }
  245. private static ModelMetadata FromModel(ViewDataDictionary viewData) {
  246. return viewData.ModelMetadata ?? GetMetadataFromProvider(null, typeof(string), null, null);
  247. }
  248. public static ModelMetadata FromStringExpression(string expression, ViewDataDictionary viewData) {
  249. if (expression == null) {
  250. throw new ArgumentNullException("expression");
  251. }
  252. if (viewData == null) {
  253. throw new ArgumentNullException("viewData");
  254. }
  255. if (expression.Length == 0) { // Empty string really means "model metadata for the current model"
  256. return FromModel(viewData);
  257. }
  258. ViewDataInfo vdi = viewData.GetViewDataInfo(expression);
  259. Type containerType = null;
  260. Type modelType = null;
  261. Func<object> modelAccessor = null;
  262. string propertyName = null;
  263. if (vdi != null) {
  264. if (vdi.Container != null) {
  265. containerType = vdi.Container.GetType();
  266. }
  267. modelAccessor = () => vdi.Value;
  268. if (vdi.PropertyDescriptor != null) {
  269. propertyName = vdi.PropertyDescriptor.Name;
  270. modelType = vdi.PropertyDescriptor.PropertyType;
  271. }
  272. else if (vdi.Value != null) { // We only need to delay accessing properties (for LINQ to SQL)
  273. modelType = vdi.Value.GetType();
  274. }
  275. }
  276. // Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
  277. else if (viewData.ModelMetadata != null) {
  278. ModelMetadata propertyMetadata = viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault();
  279. if (propertyMetadata != null) {
  280. return propertyMetadata;
  281. }
  282. }
  283. return GetMetadataFromProvider(modelAccessor, modelType ?? typeof(string), propertyName, containerType);
  284. }
  285. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "The method is a delegating helper to choose among multiple property values")]
  286. public string GetDisplayName() {
  287. return DisplayName ?? PropertyName ?? ModelType.Name;
  288. }
  289. private static ModelMetadata GetMetadataFromProvider(Func<object> modelAccessor, Type modelType, string propertyName, Type containerType) {
  290. if (containerType != null && !String.IsNullOrEmpty(propertyName)) {
  291. return ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor, containerType, propertyName);
  292. }
  293. return ModelMetadataProviders.Current.GetMetadataForType(modelAccessor, modelType);
  294. }
  295. [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.")]
  296. protected virtual string GetSimpleDisplayText() {
  297. if (Model == null) {
  298. return NullDisplayText;
  299. }
  300. string toStringResult = Convert.ToString(Model, CultureInfo.CurrentCulture);
  301. if (!toStringResult.Equals(Model.GetType().FullName, StringComparison.Ordinal)) {
  302. return toStringResult;
  303. }
  304. ModelMetadata firstProperty = Properties.FirstOrDefault();
  305. if (firstProperty == null) {
  306. return String.Empty;
  307. }
  308. if (firstProperty.Model == null) {
  309. return firstProperty.NullDisplayText;
  310. }
  311. return Convert.ToString(firstProperty.Model, CultureInfo.CurrentCulture);
  312. }
  313. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method may perform non-trivial work.")]
  314. public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context) {
  315. return ModelValidatorProviders.Providers.GetValidators(this, context);
  316. }
  317. }
  318. }