1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation. All rights reserved.
\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
9 * You must not remove this notice, or any other, from this software.
\r
11 * ***************************************************************************/
\r
13 namespace System.Web.Mvc {
\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
23 // TODO: Unit test ModelState interaction with VDD
\r
25 public class ViewDataDictionary : IDictionary<string, object> {
\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
31 public ViewDataDictionary()
\r
32 : this((object)null) {
\r
35 [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
\r
36 Justification = "See note on SetModel() method.")]
\r
37 public ViewDataDictionary(object model) {
\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
48 foreach (var entry in dictionary) {
\r
49 _innerDictionary.Add(entry.Key, entry.Value);
\r
51 foreach (var entry in dictionary.ModelState) {
\r
52 ModelState.Add(entry.Key, entry.Value);
\r
54 Model = dictionary.Model;
\r
57 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
60 return _innerDictionary.Count;
\r
64 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
65 public bool IsReadOnly {
\r
67 return ((IDictionary<string, object>)_innerDictionary).IsReadOnly;
\r
71 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
72 public ICollection<string> Keys {
\r
74 return _innerDictionary.Keys;
\r
78 public object Model {
\r
87 public ModelStateDictionary ModelState {
\r
93 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
94 public object this[string key] {
\r
97 _innerDictionary.TryGetValue(key, out value);
\r
101 _innerDictionary[key] = value;
\r
105 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
106 public ICollection<object> Values {
\r
108 return _innerDictionary.Values;
\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
117 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
118 public void Add(string key, object value) {
\r
119 _innerDictionary.Add(key, value);
\r
122 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
123 public void Clear() {
\r
124 _innerDictionary.Clear();
\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
132 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
133 public bool ContainsKey(string key) {
\r
134 return _innerDictionary.ContainsKey(key);
\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
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
149 return ViewDataEvaluator.Eval(this, expression);
\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
157 if (value == null) {
\r
158 return String.Empty;
\r
161 if (String.IsNullOrEmpty(format)) {
\r
162 return Convert.ToString(value, CultureInfo.CurrentCulture);
\r
165 return String.Format(CultureInfo.CurrentCulture, format, value);
\r
169 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
170 public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
\r
171 return _innerDictionary.GetEnumerator();
\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
179 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
180 public bool Remove(string key) {
\r
181 return _innerDictionary.Remove(key);
\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
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
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
208 object evaluated = EvalComplexExpression(vdd, expression);
\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
217 object subtarget = GetPropertyValue(indexableObject, subExpression);
\r
218 if (subtarget != null) {
\r
219 if (String.IsNullOrEmpty(postExpression))
\r
222 object potential = EvalComplexExpression(subtarget, postExpression);
\r
223 if (potential != null) {
\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
236 yield return new ExpressionPair(expression, String.Empty);
\r
238 int lastDot = expression.LastIndexOf('.');
\r
240 string subExpression = expression;
\r
241 string postExpression = string.Empty;
\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
248 lastDot = subExpression.LastIndexOf('.');
\r
252 private static object GetIndexedPropertyValue(object indexableObject, string key) {
\r
253 Type indexableType = indexableObject.GetType();
\r
255 ViewDataDictionary vdd = indexableObject as ViewDataDictionary;
\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
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
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
279 private static object GetPropertyValue(object container, string propertyName) {
\r
280 // This method handles one "segment" of a complex property expression
\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
288 // If the indexer didn't return anything useful, continue...
\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
294 container = vdd.Model;
\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
303 return descriptor.GetValue(container);
\r
306 private struct ExpressionPair {
\r
307 public readonly string Left;
\r
308 public readonly string Right;
\r
310 public ExpressionPair(string left, string right) {
\r
317 #region IEnumerable Members
\r
318 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
\r
319 IEnumerator IEnumerable.GetEnumerator() {
\r
320 return ((IEnumerable)_innerDictionary).GetEnumerator();
\r