Fix bugs in sizing TableLayoutPanel (Xamarin bug 18638)
[mono.git] / mcs / class / System.Web.Mvc / System.Web.Mvc / ViewDataDictionary.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\r
5  * This software is subject to the Microsoft Public License (Ms-PL). \r
6  * A copy of the license can be found in the license.htm file included \r
7  * in this distribution.\r
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc {\r
14     using System;\r
15     using System.Collections;\r
16     using System.Collections.Generic;\r
17     using System.ComponentModel;\r
18     using System.Diagnostics.CodeAnalysis;\r
19     using System.Globalization;\r
20     using System.Reflection;\r
21     using System.Web.Mvc.Resources;\r
22 \r
23     // TODO: Unit test ModelState interaction with VDD\r
24 \r
25     public class ViewDataDictionary : IDictionary<string, object> {\r
26 \r
27         private readonly Dictionary<string, object> _innerDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);\r
28         private object _model;\r
29         private readonly ModelStateDictionary _modelState = new ModelStateDictionary();\r
30 \r
31         public ViewDataDictionary()\r
32             : this((object)null) {\r
33         }\r
34 \r
35         [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",\r
36             Justification = "See note on SetModel() method.")]\r
37         public ViewDataDictionary(object model) {\r
38             Model = model;\r
39         }\r
40 \r
41         [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",\r
42             Justification = "See note on SetModel() method.")]\r
43         public ViewDataDictionary(ViewDataDictionary dictionary) {\r
44             if (dictionary == null) {\r
45                 throw new ArgumentNullException("dictionary");\r
46             }\r
47 \r
48             foreach (var entry in dictionary) {\r
49                 _innerDictionary.Add(entry.Key, entry.Value);\r
50             }\r
51             foreach (var entry in dictionary.ModelState) {\r
52                 ModelState.Add(entry.Key, entry.Value);\r
53             }\r
54             Model = dictionary.Model;\r
55         }\r
56 \r
57         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
58         public int Count {\r
59             get {\r
60                 return _innerDictionary.Count;\r
61             }\r
62         }\r
63 \r
64         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
65         public bool IsReadOnly {\r
66             get {\r
67                 return ((IDictionary<string, object>)_innerDictionary).IsReadOnly;\r
68             }\r
69         }\r
70 \r
71         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
72         public ICollection<string> Keys {\r
73             get {\r
74                 return _innerDictionary.Keys;\r
75             }\r
76         }\r
77 \r
78         public object Model {\r
79             get {\r
80                 return _model;\r
81             }\r
82             set {\r
83                 SetModel(value);\r
84             }\r
85         }\r
86 \r
87         public ModelStateDictionary ModelState {\r
88             get {\r
89                 return _modelState;\r
90             }\r
91         }\r
92 \r
93         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
94         public object this[string key] {\r
95             get {\r
96                 object value;\r
97                 _innerDictionary.TryGetValue(key, out value);\r
98                 return value;\r
99             }\r
100             set {\r
101                 _innerDictionary[key] = value;\r
102             }\r
103         }\r
104 \r
105         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
106         public ICollection<object> Values {\r
107             get {\r
108                 return _innerDictionary.Values;\r
109             }\r
110         }\r
111 \r
112         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
113         public void Add(KeyValuePair<string, object> item) {\r
114             ((IDictionary<string, object>)_innerDictionary).Add(item);\r
115         }\r
116 \r
117         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
118         public void Add(string key, object value) {\r
119             _innerDictionary.Add(key, value);\r
120         }\r
121 \r
122         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
123         public void Clear() {\r
124             _innerDictionary.Clear();\r
125         }\r
126 \r
127         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
128         public bool Contains(KeyValuePair<string, object> item) {\r
129             return ((IDictionary<string, object>)_innerDictionary).Contains(item);\r
130         }\r
131 \r
132         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
133         public bool ContainsKey(string key) {\r
134             return _innerDictionary.ContainsKey(key);\r
135         }\r
136 \r
137         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
138         public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) {\r
139             ((IDictionary<string, object>)_innerDictionary).CopyTo(array, arrayIndex);\r
140         }\r
141 \r
142         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Eval",\r
143             Justification = "Commonly used shorthand for Evaluate.")]\r
144         public object Eval(string expression) {\r
145             if (String.IsNullOrEmpty(expression)) {\r
146                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "expression");\r
147             }\r
148 \r
149             return ViewDataEvaluator.Eval(this, expression);\r
150         }\r
151 \r
152         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Eval",\r
153             Justification = "Commonly used shorthand for Evaluate.")]\r
154         public string Eval(string expression, string format) {\r
155             object value = Eval(expression);\r
156 \r
157             if (value == null) {\r
158                 return String.Empty;\r
159             }\r
160 \r
161             if (String.IsNullOrEmpty(format)) {\r
162                 return Convert.ToString(value, CultureInfo.CurrentCulture);\r
163             }\r
164             else {\r
165                 return String.Format(CultureInfo.CurrentCulture, format, value);\r
166             }\r
167         }\r
168 \r
169         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
170         public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {\r
171             return _innerDictionary.GetEnumerator();\r
172         }\r
173 \r
174         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
175         public bool Remove(KeyValuePair<string, object> item) {\r
176             return ((IDictionary<string, object>)_innerDictionary).Remove(item);\r
177         }\r
178 \r
179         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
180         public bool Remove(string key) {\r
181             return _innerDictionary.Remove(key);\r
182         }\r
183 \r
184         // This method will execute before the derived type's instance constructor executes. Derived types must\r
185         // be aware of this and should plan accordingly. For example, the logic in SetModel() should be simple\r
186         // enough so as not to depend on the "this" pointer referencing a fully constructed object.\r
187         protected virtual void SetModel(object value) {\r
188             _model = value;\r
189         }\r
190 \r
191         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
192         public bool TryGetValue(string key, out object value) {\r
193             return _innerDictionary.TryGetValue(key, out value);\r
194         }\r
195 \r
196         internal static class ViewDataEvaluator {\r
197             public static object Eval(ViewDataDictionary vdd, string expression) {\r
198                 //Given an expression "foo.bar.baz" we look up the following (pseudocode):\r
199                 //  this["foo.bar.baz.quux"]\r
200                 //  this["foo.bar.baz"]["quux"]\r
201                 //  this["foo.bar"]["baz.quux]\r
202                 //  this["foo.bar"]["baz"]["quux"]\r
203                 //  this["foo"]["bar.baz.quux"]\r
204                 //  this["foo"]["bar.baz"]["quux"]\r
205                 //  this["foo"]["bar"]["baz.quux"]\r
206                 //  this["foo"]["bar"]["baz"]["quux"]\r
207 \r
208                 object evaluated = EvalComplexExpression(vdd, expression);\r
209                 return evaluated;\r
210             }\r
211 \r
212             private static object EvalComplexExpression(object indexableObject, string expression) {\r
213                 foreach (ExpressionPair expressionPair in GetRightToLeftExpressions(expression)) {\r
214                     string subExpression = expressionPair.Left;\r
215                     string postExpression = expressionPair.Right;\r
216 \r
217                     object subtarget = GetPropertyValue(indexableObject, subExpression);\r
218                     if (subtarget != null) {\r
219                         if (String.IsNullOrEmpty(postExpression))\r
220                             return subtarget;\r
221 \r
222                         object potential = EvalComplexExpression(subtarget, postExpression);\r
223                         if (potential != null) {\r
224                             return potential;\r
225                         }\r
226                     }\r
227                 }\r
228                 return null;\r
229             }\r
230 \r
231             private static IEnumerable<ExpressionPair> GetRightToLeftExpressions(string expression) {\r
232                 // Produces an enumeration of all the combinations of complex property names\r
233                 // given a complex expression. See the list above for an example of the result\r
234                 // of the enumeration.\r
235 \r
236                 yield return new ExpressionPair(expression, String.Empty);\r
237 \r
238                 int lastDot = expression.LastIndexOf('.');\r
239 \r
240                 string subExpression = expression;\r
241                 string postExpression = string.Empty;\r
242 \r
243                 while (lastDot > -1) {\r
244                     subExpression = expression.Substring(0, lastDot);\r
245                     postExpression = expression.Substring(lastDot + 1);\r
246                     yield return new ExpressionPair(subExpression, postExpression);\r
247 \r
248                     lastDot = subExpression.LastIndexOf('.');\r
249                 }\r
250             }\r
251 \r
252             private static object GetIndexedPropertyValue(object indexableObject, string key) {\r
253                 Type indexableType = indexableObject.GetType();\r
254 \r
255                 ViewDataDictionary vdd = indexableObject as ViewDataDictionary;\r
256                 if (vdd != null) {\r
257                     return vdd[key];\r
258                 }\r
259 \r
260                 MethodInfo containsKeyMethod = indexableType.GetMethod("ContainsKey", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);\r
261                 if (containsKeyMethod != null) {\r
262                     if (!(bool)containsKeyMethod.Invoke(indexableObject, new object[] { key })) {\r
263                         return null;\r
264                     }\r
265                 }\r
266 \r
267                 PropertyInfo info = indexableType.GetProperty("Item", BindingFlags.Public | BindingFlags.Instance, null, null, new Type[] { typeof(string) }, null);\r
268                 if (info != null) {\r
269                     return info.GetValue(indexableObject, new object[] { key });\r
270                 }\r
271 \r
272                 PropertyInfo objectInfo = indexableType.GetProperty("Item", BindingFlags.Public | BindingFlags.Instance, null, null, new Type[] { typeof(object) }, null);\r
273                 if (objectInfo != null) {\r
274                     return objectInfo.GetValue(indexableObject, new object[] { key });\r
275                 }\r
276                 return null;\r
277             }\r
278 \r
279             private static object GetPropertyValue(object container, string propertyName) {\r
280                 // This method handles one "segment" of a complex property expression\r
281 \r
282                 // First, we try to evaluate the property based on its indexer\r
283                 object value = GetIndexedPropertyValue(container, propertyName);\r
284                 if (value != null) {\r
285                     return value;\r
286                 }\r
287 \r
288                 // If the indexer didn't return anything useful, continue...\r
289 \r
290                 // If the container is a ViewDataDictionary then treat its Model property\r
291                 // as the container instead of the ViewDataDictionary itself.\r
292                 ViewDataDictionary vdd = container as ViewDataDictionary;\r
293                 if (vdd != null) {\r
294                     container = vdd.Model;\r
295                 }\r
296 \r
297                 // Second, we try to use PropertyDescriptors and treat the expression as a property name\r
298                 PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true);\r
299                 if (descriptor == null) {\r
300                     return null;\r
301                 }\r
302 \r
303                 return descriptor.GetValue(container);\r
304             }\r
305 \r
306             private struct ExpressionPair {\r
307                 public readonly string Left;\r
308                 public readonly string Right;\r
309 \r
310                 public ExpressionPair(string left, string right) {\r
311                     Left = left;\r
312                     Right = right;\r
313                 }\r
314             }\r
315         }\r
316 \r
317         #region IEnumerable Members\r
318         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]\r
319         IEnumerator IEnumerable.GetEnumerator() {\r
320             return ((IEnumerable)_innerDictionary).GetEnumerator();\r
321         }\r
322         #endregion\r
323 \r
324     }\r
325 }\r