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 ModelMetadata _modelMetadata;
\r
30 private readonly ModelStateDictionary _modelState = new ModelStateDictionary();
\r
31 private TemplateInfo _templateMetadata;
\r
33 public ViewDataDictionary()
\r
34 : this((object)null) {
\r
37 [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
\r
38 Justification = "See note on SetModel() method.")]
\r
39 public ViewDataDictionary(object model) {
\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
50 foreach (var entry in dictionary) {
\r
51 _innerDictionary.Add(entry.Key, entry.Value);
\r
53 foreach (var entry in dictionary.ModelState) {
\r
54 ModelState.Add(entry.Key, entry.Value);
\r
57 Model = dictionary.Model;
\r
58 TemplateInfo = dictionary.TemplateInfo;
\r
60 // PERF: Don't unnecessarily instantiate the model metadata
\r
61 _modelMetadata = dictionary._modelMetadata;
\r
66 return _innerDictionary.Count;
\r
70 public bool IsReadOnly {
\r
72 return ((IDictionary<string, object>)_innerDictionary).IsReadOnly;
\r
76 public ICollection<string> Keys {
\r
78 return _innerDictionary.Keys;
\r
82 public object Model {
\r
87 _modelMetadata = null;
\r
92 public virtual ModelMetadata ModelMetadata {
\r
94 if (_modelMetadata == null && _model != null) {
\r
95 _modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => _model, _model.GetType());
\r
97 return _modelMetadata;
\r
100 _modelMetadata = value;
\r
104 public ModelStateDictionary ModelState {
\r
106 return _modelState;
\r
110 public object this[string key] {
\r
113 _innerDictionary.TryGetValue(key, out value);
\r
117 _innerDictionary[key] = value;
\r
121 public TemplateInfo TemplateInfo {
\r
123 if (_templateMetadata == null) {
\r
124 _templateMetadata = new TemplateInfo();
\r
126 return _templateMetadata;
\r
129 _templateMetadata = value;
\r
133 public ICollection<object> Values {
\r
135 return _innerDictionary.Values;
\r
139 public void Add(KeyValuePair<string, object> item) {
\r
140 ((IDictionary<string, object>)_innerDictionary).Add(item);
\r
143 public void Add(string key, object value) {
\r
144 _innerDictionary.Add(key, value);
\r
147 public void Clear() {
\r
148 _innerDictionary.Clear();
\r
151 public bool Contains(KeyValuePair<string, object> item) {
\r
152 return ((IDictionary<string, object>)_innerDictionary).Contains(item);
\r
155 public bool ContainsKey(string key) {
\r
156 return _innerDictionary.ContainsKey(key);
\r
159 public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) {
\r
160 ((IDictionary<string, object>)_innerDictionary).CopyTo(array, arrayIndex);
\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
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
175 if (value == null) {
\r
176 return String.Empty;
\r
179 if (String.IsNullOrEmpty(format)) {
\r
180 return Convert.ToString(value, CultureInfo.CurrentCulture);
\r
183 return String.Format(CultureInfo.CurrentCulture, format, value);
\r
187 public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
\r
188 return _innerDictionary.GetEnumerator();
\r
191 public ViewDataInfo GetViewDataInfo(string expression) {
\r
192 if (String.IsNullOrEmpty(expression)) {
\r
193 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "expression");
\r
196 return ViewDataEvaluator.Eval(this, expression);
\r
199 public bool Remove(KeyValuePair<string, object> item) {
\r
200 return ((IDictionary<string, object>)_innerDictionary).Remove(item);
\r
203 public bool Remove(string key) {
\r
204 return _innerDictionary.Remove(key);
\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
214 public bool TryGetValue(string key, out object value) {
\r
215 return _innerDictionary.TryGetValue(key, out value);
\r
218 internal static class ViewDataEvaluator {
\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
231 ViewDataInfo evaluated = EvalComplexExpression(vdd, expression);
\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
240 ViewDataInfo subTargetInfo = GetPropertyValue(indexableObject, subExpression);
\r
241 if (subTargetInfo != null) {
\r
242 if (String.IsNullOrEmpty(postExpression)) {
\r
243 return subTargetInfo;
\r
246 if (subTargetInfo.Value != null) {
\r
247 ViewDataInfo potential = EvalComplexExpression(subTargetInfo.Value, postExpression);
\r
248 if (potential != null) {
\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
262 yield return new ExpressionPair(expression, String.Empty);
\r
264 int lastDot = expression.LastIndexOf('.');
\r
266 string subExpression = expression;
\r
267 string postExpression = string.Empty;
\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
274 lastDot = subExpression.LastIndexOf('.');
\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
283 if (dict != null) {
\r
284 success = dict.TryGetValue(key, out value);
\r
287 TryGetValueDelegate tgvDel = TypeHelpers.CreateTryGetValueDelegate(indexableObject.GetType());
\r
288 if (tgvDel != null) {
\r
289 success = tgvDel(indexableObject, key, out value);
\r
294 return new ViewDataInfo() {
\r
295 Container = indexableObject,
\r
303 private static ViewDataInfo GetPropertyValue(object container, string propertyName) {
\r
304 // This method handles one "segment" of a complex property expression
\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
312 // If the indexer didn't return anything useful, continue...
\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
318 container = vdd.Model;
\r
321 // If the container is null, we're out of options
\r
322 if (container == null) {
\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
332 return new ViewDataInfo(() => descriptor.GetValue(container)) {
\r
333 Container = container,
\r
334 PropertyDescriptor = descriptor
\r
338 private struct ExpressionPair {
\r
339 public readonly string Left;
\r
340 public readonly string Right;
\r
342 public ExpressionPair(string left, string right) {
\r
349 #region IEnumerable Members
\r
350 IEnumerator IEnumerable.GetEnumerator() {
\r
351 return ((IEnumerable)_innerDictionary).GetEnumerator();
\r