// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering.Expressions; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc { public class ViewDataDictionary : IDictionary { private readonly IDictionary _data; private readonly Type _declaredModelType; private readonly IModelMetadataProvider _metadataProvider; /// /// Initializes a new instance of the class. /// /// /// instance used to calculate /// values. /// /// instance for this scope. /// For use when creating a for a new top-level scope. public ViewDataDictionary([NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState) : this(metadataProvider, modelState, declaredModelType: typeof(object)) { } /// /// Initializes a new instance of the class based entirely on an existing /// instance. /// /// instance to copy initial values from. /// /// For use when copying a instance and the declared /// will not change e.g. when copying from a /// instance to a base instance. /// public ViewDataDictionary([NotNull] ViewDataDictionary source) : this(source, source.Model, source._declaredModelType) { } /// /// Initializes a new instance of the class based in part on an existing /// instance. This constructor is careful to avoid exceptions may throw when /// is null. /// /// instance to copy initial values from. /// Value for the property. /// /// For use when the new instance's declared is unknown but its /// is known. In this case, is the best possible guess about the /// declared type when is null. /// public ViewDataDictionary([NotNull] ViewDataDictionary source, object model) : this(source, model, declaredModelType: typeof(object)) { } /// /// Initializes a new instance of the class. /// /// /// instance used to calculate /// values. /// /// Internal for testing. internal ViewDataDictionary([NotNull] IModelMetadataProvider metadataProvider) : this(metadataProvider, new ModelStateDictionary()) { } /// /// Initializes a new instance of the class. /// /// /// instance used to calculate /// values. /// /// /// of values expected. Used to set /// when is null. /// /// /// For use when creating a derived for a new top-level scope. /// protected ViewDataDictionary( [NotNull] IModelMetadataProvider metadataProvider, [NotNull] Type declaredModelType) : this(metadataProvider, new ModelStateDictionary(), declaredModelType) { } /// /// Initializes a new instance of the class. /// /// /// instance used to calculate /// values. /// /// instance for this scope. /// /// of values expected. Used to set /// when is null. /// /// /// For use when creating a derived for a new top-level scope. /// protected ViewDataDictionary( [NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState, [NotNull] Type declaredModelType) : this(metadataProvider, modelState, declaredModelType, data: new Dictionary(StringComparer.OrdinalIgnoreCase), templateInfo: new TemplateInfo()) { // This is the core constructor called when Model is unknown. Base ModelMetadata on the declared type. ModelExplorer = _metadataProvider.GetModelExplorerForType(declaredModelType, model: null); } /// /// Initializes a new instance of the class based in part on an existing /// instance. /// /// instance to copy initial values from. /// /// of values expected. Used to set /// when is null. /// /// /// /// For use when copying a instance and new instance's declared /// is known but should be copied from the existing /// instance e.g. when copying from a base instance to a /// instance. /// /// /// This constructor may throw if source.Model is non-null and incompatible with /// . Pass model: null to /// to ignore source.Model. /// /// protected ViewDataDictionary([NotNull] ViewDataDictionary source, Type declaredModelType) : this(source, model: source.Model, declaredModelType: declaredModelType) { } /// /// Initializes a new instance of the class based in part on an existing /// instance. This constructor is careful to avoid exceptions may throw when /// is null. /// /// instance to copy initial values from. /// Value for the property. /// /// of values expected. Used to set /// when is null. /// /// /// /// For use when copying a instance and new instance's declared /// and are known. /// /// /// This constructor may throw if is non-null and incompatible with /// . /// /// protected ViewDataDictionary([NotNull] ViewDataDictionary source, object model, Type declaredModelType) : this(source._metadataProvider, new ModelStateDictionary(source.ModelState), declaredModelType, data: new CopyOnWriteDictionary(source, StringComparer.OrdinalIgnoreCase), templateInfo: new TemplateInfo(source.TemplateInfo)) { // This is the core constructor called when Model is known. var modelType = GetModelType(model); if (modelType == source.ModelMetadata.ModelType && model == source.ModelExplorer.Model) { // Preserve any customizations made to source.ModelExplorer.ModelMetadata if the Type // that will be calculated in SetModel() and source.Model match new instance's values. ModelExplorer = source.ModelExplorer; } else if (model == null) { // Ensure ModelMetadata is never null though SetModel() isn't called below. ModelExplorer = _metadataProvider.GetModelExplorerForType(_declaredModelType, model: null); } // If we're constructing a ViewDataDictionary where TModel is a non-Nullable value type, // SetModel() will throw if we try to call it with null. We should not throw in that case. if (model != null) { SetModel(model); } } private ViewDataDictionary( IModelMetadataProvider metadataProvider, ModelStateDictionary modelState, Type declaredModelType, IDictionary data, TemplateInfo templateInfo) { _metadataProvider = metadataProvider; ModelState = modelState; _declaredModelType = declaredModelType; _data = data; TemplateInfo = templateInfo; } public object Model { get { return ModelExplorer.Model; } set { // Reset ModelExplorer to ensure Model and ModelMetadata.Model remain equal. SetModel(value); } } public ModelStateDictionary ModelState { get; } /// /// for the current value or the declared if /// is null. /// /// /// Value is never null but may describe the class in some cases. This may for /// example occur in controllers if is null. /// public ModelMetadata ModelMetadata { get { return ModelExplorer.Metadata; } } /// /// Gets or sets the for the . /// public ModelExplorer ModelExplorer { get; set; } public TemplateInfo TemplateInfo { get; } #region IDictionary properties // Do not just pass through to _data: Indexer should not throw a KeyNotFoundException. public object this[string index] { get { object result; _data.TryGetValue(index, out result); return result; } set { _data[index] = value; } } public int Count { get { return _data.Count; } } public bool IsReadOnly { get { return _data.IsReadOnly; } } public ICollection Keys { get { return _data.Keys; } } public ICollection Values { get { return _data.Values; } } #endregion // for unit testing internal IDictionary Data { get { return _data; } } public object Eval(string expression) { var info = GetViewDataInfo(expression); return (info != null) ? info.Value : null; } public string Eval(string expression, string format) { var value = Eval(expression); return FormatValue(value, format); } public static string FormatValue(object value, string format) { if (value == null) { return string.Empty; } if (string.IsNullOrEmpty(format)) { return Convert.ToString(value, CultureInfo.CurrentCulture); } else { return string.Format(CultureInfo.CurrentCulture, format, value); } } public ViewDataInfo GetViewDataInfo(string expression) { if (string.IsNullOrEmpty(expression)) { // Null or empty expression name means current model. return new ViewDataInfo(container: null, value: Model); } return ViewDataEvaluator.Eval(this, expression); } // This method will execute before the derived type's instance constructor executes. Derived types must // be aware of this and should plan accordingly. For example, the logic in SetModel() should be simple // enough so as not to depend on the "this" pointer referencing a fully constructed object. protected virtual void SetModel(object value) { EnsureCompatible(value); // Reset or override ModelMetadata based on runtime value type. Fall back to declared type if value is // null. When called from the Model setter, ModelMetadata will (temporarily) be null. When called from // a constructor, current ModelMetadata may already be set to preserve customizations made in parent scope. var modelType = GetModelType(value); if (ModelExplorer?.Metadata.ModelType != modelType) { ModelExplorer = _metadataProvider.GetModelExplorerForType(modelType, value); } else if (object.ReferenceEquals(value, Model)) { // The metadata already matches, and the model is literally the same, nothing // to do here. This will likely occur when using one of the copy constructors. } else { // The metadata matches, but it's a new value. ModelExplorer = new ModelExplorer(_metadataProvider, ModelExplorer.Container, ModelMetadata, value); } } // Throw if given value is incompatible with the declared Model Type. private void EnsureCompatible(object value) { // IsCompatibleObject verifies if the value is either an instance of _declaredModelType or (if value is // null) that _declaredModelType is a nullable type. var castWillSucceed = _declaredModelType.IsCompatibleWith(value); if (!castWillSucceed) { string message; if (value == null) { message = Resources.FormatViewData_ModelCannotBeNull(_declaredModelType); } else { message = Resources.FormatViewData_WrongTModelType(value.GetType(), _declaredModelType); } throw new InvalidOperationException(message); } } private Type GetModelType(object value) { return (value == null) ? _declaredModelType : value.GetType(); } #region IDictionary methods public void Add([NotNull] string key, object value) { _data.Add(key, value); } public bool ContainsKey([NotNull] string key) { return _data.ContainsKey(key); } public bool Remove([NotNull] string key) { return _data.Remove(key); } public bool TryGetValue([NotNull] string key, out object value) { return _data.TryGetValue(key, out value); } public void Add([NotNull] KeyValuePair item) { _data.Add(item); } public void Clear() { _data.Clear(); } public bool Contains([NotNull] KeyValuePair item) { return _data.Contains(item); } public void CopyTo([NotNull] KeyValuePair[] array, int arrayIndex) { _data.CopyTo(array, arrayIndex); } public bool Remove([NotNull] KeyValuePair item) { return _data.Remove(item); } IEnumerator> IEnumerable>.GetEnumerator() { return _data.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } #endregion } }