Fix operator != handling of LHS null
[mono.git] / mcs / class / System.Web.Mvc2 / 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 ModelMetadata _modelMetadata;\r
30         private readonly ModelStateDictionary _modelState = new ModelStateDictionary();\r
31         private TemplateInfo _templateMetadata;\r
32 \r
33         public ViewDataDictionary()\r
34             : this((object)null) {\r
35         }\r
36 \r
37         [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",\r
38             Justification = "See note on SetModel() method.")]\r
39         public ViewDataDictionary(object model) {\r
40             Model = model;\r
41         }\r
42 \r
43         [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",\r
44             Justification = "See note on SetModel() method.")]\r
45         public ViewDataDictionary(ViewDataDictionary dictionary) {\r
46             if (dictionary == null) {\r
47                 throw new ArgumentNullException("dictionary");\r
48             }\r
49 \r
50             foreach (var entry in dictionary) {\r
51                 _innerDictionary.Add(entry.Key, entry.Value);\r
52             }\r
53             foreach (var entry in dictionary.ModelState) {\r
54                 ModelState.Add(entry.Key, entry.Value);\r
55             }\r
56 \r
57             Model = dictionary.Model;\r
58             TemplateInfo = dictionary.TemplateInfo;\r
59 \r
60             // PERF: Don't unnecessarily instantiate the model metadata\r
61             _modelMetadata = dictionary._modelMetadata;\r
62         }\r
63 \r
64         public int Count {\r
65             get {\r
66                 return _innerDictionary.Count;\r
67             }\r
68         }\r
69 \r
70         public bool IsReadOnly {\r
71             get {\r
72                 return ((IDictionary<string, object>)_innerDictionary).IsReadOnly;\r
73             }\r
74         }\r
75 \r
76         public ICollection<string> Keys {\r
77             get {\r
78                 return _innerDictionary.Keys;\r
79             }\r
80         }\r
81 \r
82         public object Model {\r
83             get {\r
84                 return _model;\r
85             }\r
86             set {\r
87                 _modelMetadata = null;\r
88                 SetModel(value);\r
89             }\r
90         }\r
91 \r
92         public virtual ModelMetadata ModelMetadata {\r
93             get {\r
94                 if (_modelMetadata == null && _model != null) {\r
95                     _modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => _model, _model.GetType());\r
96                 }\r
97                 return _modelMetadata;\r
98             }\r
99             set {\r
100                 _modelMetadata = value;\r
101             }\r
102         }\r
103 \r
104         public ModelStateDictionary ModelState {\r
105             get {\r
106                 return _modelState;\r
107             }\r
108         }\r
109 \r
110         public object this[string key] {\r
111             get {\r
112                 object value;\r
113                 _innerDictionary.TryGetValue(key, out value);\r
114                 return value;\r
115             }\r
116             set {\r
117                 _innerDictionary[key] = value;\r
118             }\r
119         }\r
120 \r
121         public TemplateInfo TemplateInfo {\r
122             get {\r
123                 if (_templateMetadata == null) {\r
124                     _templateMetadata = new TemplateInfo();\r
125                 }\r
126                 return _templateMetadata;\r
127             }\r
128             set {\r
129                 _templateMetadata = value;\r
130             }\r
131         }\r
132 \r
133         public ICollection<object> Values {\r
134             get {\r
135                 return _innerDictionary.Values;\r
136             }\r
137         }\r
138 \r
139         public void Add(KeyValuePair<string, object> item) {\r
140             ((IDictionary<string, object>)_innerDictionary).Add(item);\r
141         }\r
142 \r
143         public void Add(string key, object value) {\r
144             _innerDictionary.Add(key, value);\r
145         }\r
146 \r
147         public void Clear() {\r
148             _innerDictionary.Clear();\r
149         }\r
150 \r
151         public bool Contains(KeyValuePair<string, object> item) {\r
152             return ((IDictionary<string, object>)_innerDictionary).Contains(item);\r
153         }\r
154 \r
155         public bool ContainsKey(string key) {\r
156             return _innerDictionary.ContainsKey(key);\r
157         }\r
158 \r
159         public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) {\r
160             ((IDictionary<string, object>)_innerDictionary).CopyTo(array, arrayIndex);\r
161         }\r
162 \r
163         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Eval",\r
164             Justification = "Commonly used shorthand for Evaluate.")]\r
165         public object Eval(string expression) {\r
166             ViewDataInfo info = GetViewDataInfo(expression);\r
167             return (info != null) ? info.Value : null;\r
168         }\r
169 \r
170         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Eval",\r
171             Justification = "Commonly used shorthand for Evaluate.")]\r
172         public string Eval(string expression, string format) {\r
173             object value = Eval(expression);\r
174 \r
175             if (value == null) {\r
176                 return String.Empty;\r
177             }\r
178 \r
179             if (String.IsNullOrEmpty(format)) {\r
180                 return Convert.ToString(value, CultureInfo.CurrentCulture);\r
181             }\r
182             else {\r
183                 return String.Format(CultureInfo.CurrentCulture, format, value);\r
184             }\r
185         }\r
186 \r
187         public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {\r
188             return _innerDictionary.GetEnumerator();\r
189         }\r
190 \r
191         public ViewDataInfo GetViewDataInfo(string expression) {\r
192             if (String.IsNullOrEmpty(expression)) {\r
193                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "expression");\r
194             }\r
195 \r
196             return ViewDataEvaluator.Eval(this, expression);\r
197         }\r
198 \r
199         public bool Remove(KeyValuePair<string, object> item) {\r
200             return ((IDictionary<string, object>)_innerDictionary).Remove(item);\r
201         }\r
202 \r
203         public bool Remove(string key) {\r
204             return _innerDictionary.Remove(key);\r
205         }\r
206 \r
207         // This method will execute before the derived type's instance constructor executes. Derived types must\r
208         // be aware of this and should plan accordingly. For example, the logic in SetModel() should be simple\r
209         // enough so as not to depend on the "this" pointer referencing a fully constructed object.\r
210         protected virtual void SetModel(object value) {\r
211             _model = value;\r
212         }\r
213 \r
214         public bool TryGetValue(string key, out object value) {\r
215             return _innerDictionary.TryGetValue(key, out value);\r
216         }\r
217 \r
218         internal static class ViewDataEvaluator {\r
219 \r
220             public static ViewDataInfo Eval(ViewDataDictionary vdd, string expression) {\r
221                 //Given an expression "foo.bar.baz" we look up the following (pseudocode):\r
222                 //  this["foo.bar.baz.quux"]\r
223                 //  this["foo.bar.baz"]["quux"]\r
224                 //  this["foo.bar"]["baz.quux]\r
225                 //  this["foo.bar"]["baz"]["quux"]\r
226                 //  this["foo"]["bar.baz.quux"]\r
227                 //  this["foo"]["bar.baz"]["quux"]\r
228                 //  this["foo"]["bar"]["baz.quux"]\r
229                 //  this["foo"]["bar"]["baz"]["quux"]\r
230 \r
231                 ViewDataInfo evaluated = EvalComplexExpression(vdd, expression);\r
232                 return evaluated;\r
233             }\r
234 \r
235             private static ViewDataInfo EvalComplexExpression(object indexableObject, string expression) {\r
236                 foreach (ExpressionPair expressionPair in GetRightToLeftExpressions(expression)) {\r
237                     string subExpression = expressionPair.Left;\r
238                     string postExpression = expressionPair.Right;\r
239 \r
240                     ViewDataInfo subTargetInfo = GetPropertyValue(indexableObject, subExpression);\r
241                     if (subTargetInfo != null) {\r
242                         if (String.IsNullOrEmpty(postExpression)) {\r
243                             return subTargetInfo;\r
244                         }\r
245 \r
246                         if (subTargetInfo.Value != null) {\r
247                             ViewDataInfo potential = EvalComplexExpression(subTargetInfo.Value, postExpression);\r
248                             if (potential != null) {\r
249                                 return potential;\r
250                             }\r
251                         }\r
252                     }\r
253                 }\r
254                 return null;\r
255             }\r
256 \r
257             private static IEnumerable<ExpressionPair> GetRightToLeftExpressions(string expression) {\r
258                 // Produces an enumeration of all the combinations of complex property names\r
259                 // given a complex expression. See the list above for an example of the result\r
260                 // of the enumeration.\r
261 \r
262                 yield return new ExpressionPair(expression, String.Empty);\r
263 \r
264                 int lastDot = expression.LastIndexOf('.');\r
265 \r
266                 string subExpression = expression;\r
267                 string postExpression = string.Empty;\r
268 \r
269                 while (lastDot > -1) {\r
270                     subExpression = expression.Substring(0, lastDot);\r
271                     postExpression = expression.Substring(lastDot + 1);\r
272                     yield return new ExpressionPair(subExpression, postExpression);\r
273 \r
274                     lastDot = subExpression.LastIndexOf('.');\r
275                 }\r
276             }\r
277 \r
278             private static ViewDataInfo GetIndexedPropertyValue(object indexableObject, string key) {\r
279                 IDictionary<string, object> dict = indexableObject as IDictionary<string, object>;\r
280                 object value = null;\r
281                 bool success = false;\r
282                 \r
283                 if (dict != null) {\r
284                     success = dict.TryGetValue(key, out value);\r
285                 }\r
286                 else {\r
287                     TryGetValueDelegate tgvDel = TypeHelpers.CreateTryGetValueDelegate(indexableObject.GetType());\r
288                     if (tgvDel != null) {\r
289                         success = tgvDel(indexableObject, key, out value);\r
290                     }\r
291                 }\r
292                 \r
293                 if (success) {\r
294                     return new ViewDataInfo() {\r
295                         Container = indexableObject,\r
296                         Value = value\r
297                     };\r
298                 }\r
299 \r
300                 return null;\r
301             }\r
302 \r
303             private static ViewDataInfo GetPropertyValue(object container, string propertyName) {\r
304                 // This method handles one "segment" of a complex property expression\r
305 \r
306                 // First, we try to evaluate the property based on its indexer\r
307                 ViewDataInfo value = GetIndexedPropertyValue(container, propertyName);\r
308                 if (value != null) {\r
309                     return value;\r
310                 }\r
311 \r
312                 // If the indexer didn't return anything useful, continue...\r
313 \r
314                 // If the container is a ViewDataDictionary then treat its Model property\r
315                 // as the container instead of the ViewDataDictionary itself.\r
316                 ViewDataDictionary vdd = container as ViewDataDictionary;\r
317                 if (vdd != null) {\r
318                     container = vdd.Model;\r
319                 }\r
320 \r
321                 // If the container is null, we're out of options\r
322                 if (container == null) {\r
323                     return null;\r
324                 }\r
325 \r
326                 // Second, we try to use PropertyDescriptors and treat the expression as a property name\r
327                 PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true);\r
328                 if (descriptor == null) {\r
329                     return null;\r
330                 }\r
331 \r
332                 return new ViewDataInfo(() => descriptor.GetValue(container)) {\r
333                     Container = container,\r
334                     PropertyDescriptor = descriptor\r
335                 };\r
336             }\r
337 \r
338             private struct ExpressionPair {\r
339                 public readonly string Left;\r
340                 public readonly string Right;\r
341 \r
342                 public ExpressionPair(string left, string right) {\r
343                     Left = left;\r
344                     Right = right;\r
345                 }\r
346             }\r
347         }\r
348 \r
349         #region IEnumerable Members\r
350         IEnumerator IEnumerable.GetEnumerator() {\r
351             return ((IEnumerable)_innerDictionary).GetEnumerator();\r
352         }\r
353         #endregion\r
354 \r
355     }\r
356 }\r