File: DataAnnotations\ValidationContext.cs
Project: ndp\fx\src\xsp\system\DataAnnotations\System.ComponentModel.DataAnnotations.csproj (System.ComponentModel.DataAnnotations)
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
 
#if SILVERLIGHT
using System.Reflection;
#endif
 
namespace System.ComponentModel.DataAnnotations {
    /// <summary>
    /// Describes the context in which a validation is being performed.
    /// </summary>
    /// <remarks>
    /// This class contains information describing the instance on which
    /// validation is being performed.
    /// <para>
    /// It supports <see cref="IServiceProvider"/> so that custom validation
    /// code can acquire additional services to help it perform its validation.
    /// </para>
    /// <para>
    /// An <see cref="Items"/> property bag is available for additional contextual
    /// information about the validation.  Values stored in <see cref="Items"/>
    /// will be available to validation methods that use this <see cref="ValidationContext"/>
    /// </para>
    /// </remarks>
    [SuppressMessage("Microsoft.Usage", "CA2302:FlagServiceProviders", Justification = "The actual IServiceProvider implementation lies with the underlying service provider.")]
    public sealed class ValidationContext : IServiceProvider
    {
        #region Member Fields
 
        private Func<Type, object> _serviceProvider;
        private object _objectInstance;
        private string _memberName;
        private string _displayName;
        private Dictionary<object, object> _items;
 
        #endregion
 
        #region Constructors
 
        /// <summary>
        /// Construct a <see cref="ValidationContext"/> for a given object instance being validated.
        /// </summary>
        /// <param name="instance">The object instance being validated.  It cannot be <c>null</c>.</param>
        /// <exception cref="ArgumentNullException">When <paramref name="instance"/> is <c>null</c></exception>
        public ValidationContext(object instance)
            : this(instance, null, null) {
        }
 
        /// <summary>
        /// Construct a <see cref="ValidationContext"/> for a given object instance and an optional
        /// property bag of <paramref name="items"/>.
        /// </summary>
        /// <param name="instance">The object instance being validated.  It cannot be null.</param>
        /// <param name="items">Optional set of key/value pairs to make available to consumers via <see cref="Items"/>.
        /// If null, an empty dictionary will be created.  If not null, the set of key/value pairs will be copied into a
        /// new dictionary, preventing consumers from modifying the original dictionary.
        /// </param>
        /// <exception cref="ArgumentNullException">When <paramref name="instance"/> is <c>null</c></exception>
        public ValidationContext(object instance, IDictionary<object, object> items)
            : this(instance, null, items) {
        }
 
#if SILVERLIGHT
        /// <summary>
        /// Construct a <see cref="ValidationContext"/> for a given object instance, an optional <paramref name="serviceProvider"/>, and an optional
        /// property bag of <paramref name="items"/>.
        /// </summary>
        /// <param name="instance">The object instance being validated.  It cannot be null.</param>
        /// <param name="serviceProvider">
        /// Optional <see cref="IServiceProvider"/> to use when <see cref="GetService"/> is called.
        /// If it is null, <see cref="GetService"/> will always return null.
        /// </param>
        /// <param name="items">Optional set of key/value pairs to make available to consumers via <see cref="Items"/>.
        /// If null, an empty dictionary will be created.  If not null, the set of key/value pairs will be copied into a
        /// new dictionary, preventing consumers from modifying the original dictionary.
        /// </param>
        /// <exception cref="ArgumentNullException">When <paramref name="instance"/> is <c>null</c></exception>
#else
        /// <summary>
        /// Construct a <see cref="ValidationContext"/> for a given object instance, an optional <paramref name="serviceProvider"/>, and an optional
        /// property bag of <paramref name="items"/>.
        /// </summary>
        /// <param name="instance">The object instance being validated.  It cannot be null.</param>
        /// <param name="serviceProvider">
        /// Optional <see cref="IServiceProvider"/> to use when <see cref="GetService"/> is called.
        /// <para>
        /// If the <paramref name="serviceProvider"/> specified implements <see cref="Design.IServiceContainer"/>,
        /// then it will be used as the <see cref="ServiceContainer"/> but its services can still be retrieved
        /// through <see cref="GetService"/> as well.
        /// </para>
        /// </param>
        /// <param name="items">Optional set of key/value pairs to make available to consumers via <see cref="Items"/>.
        /// If null, an empty dictionary will be created.  If not null, the set of key/value pairs will be copied into a
        /// new dictionary, preventing consumers from modifying the original dictionary.
        /// </param>
        /// <exception cref="ArgumentNullException">When <paramref name="instance"/> is <c>null</c></exception>
#endif
        public ValidationContext(object instance, IServiceProvider serviceProvider, IDictionary<object, object> items) {
            if (instance == null) {
                throw new ArgumentNullException("instance");
            }
 
            if (serviceProvider != null) {
                this.InitializeServiceProvider(serviceType => serviceProvider.GetService(serviceType));
            }
 
#if !SILVERLIGHT
            Design.IServiceContainer container = serviceProvider as Design.IServiceContainer;
 
            if (container != null) {
                this._serviceContainer = new ValidationContextServiceContainer(container);
            } else {
                this._serviceContainer = new ValidationContextServiceContainer();
            }
#endif
 
            if (items != null) {
                this._items = new Dictionary<object, object>(items);
            } else {
                this._items = new Dictionary<object, object>();
            }
 
            this._objectInstance = instance;
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Gets the object instance being validated.  While it will not be null, the state of the instance is indeterminate
        /// as it might only be partially initialized during validation.
        /// <para>Consume this instance with caution!</para>
        /// </summary>
        /// <remarks>
        /// During validation, especially property-level validation, the object instance might be in an indeterminate state.
        /// For example, the property being validated, as well as other properties on the instance might not have been
        /// updated to their new values.
        /// </remarks>
        public object ObjectInstance {
            get {
                return this._objectInstance;
            }
        }
 
        /// <summary>
        /// Gets the type of the object being validated.  It will not be null.
        /// </summary>
        public Type ObjectType {
            get {
#if SILVERLIGHT
                return this.ObjectInstance.GetCustomOrCLRType();
#else
                return this.ObjectInstance.GetType();
#endif
            }
        }
 
        /// <summary>
        /// Gets or sets the user-visible name of the type or property being validated.
        /// </summary>
        /// <value>If this name was not explicitly set, this property will consult an associated <see cref="DisplayAttribute"/>
        /// to see if can use that instead.  Lacking that, it returns <see cref="MemberName"/>.  The <see cref="ObjectInstance"/>
        /// type name will be used if MemberName has not been set.
        /// </value>
        public string DisplayName {
            get {
                if (string.IsNullOrEmpty(this._displayName)) {
                    this._displayName = this.GetDisplayName();
 
                    if (string.IsNullOrEmpty(this._displayName)) {
                        this._displayName = this.MemberName;
 
                        if (string.IsNullOrEmpty(this._displayName)) {
                            this._displayName = this.ObjectType.Name;
                        }
                    }
                }
                return this._displayName;
            }
            set {
                if (string.IsNullOrEmpty(value)) {
                    throw new ArgumentNullException("value");
                }
                this._displayName = value;
            }
        }
 
        /// <summary>
        /// Gets or sets the name of the type or property being validated.
        /// </summary>
        /// <value>This name reflects the API name of the member being validated, not a localized name.  It should be set
        /// only for property or parameter contexts.</value>
        public string MemberName {
            get {
                return this._memberName;
            }
            set {
                this._memberName = value;
            }
        }
 
        /// <summary>
        /// Gets the dictionary of key/value pairs associated with this context.
        /// </summary>
        /// <value>This property will never be null, but the dictionary may be empty.  Changes made
        /// to items in this dictionary will never affect the original dictionary specified in the constructor.</value>
        public IDictionary<object, object> Items {
            get {
                return this._items;
            }
        }
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Looks up the display name using the DisplayAttribute attached to the respective type or property.
        /// </summary>
        /// <returns>A display-friendly name of the member represented by the <see cref="MemberName"/>.</returns>
        private string GetDisplayName() {
            string displayName = null;
            ValidationAttributeStore store = ValidationAttributeStore.Instance;
            DisplayAttribute displayAttribute = null;
 
            if (string.IsNullOrEmpty(this._memberName)) {
                displayAttribute = store.GetTypeDisplayAttribute(this);
            } else if (store.IsPropertyContext(this)) {
                displayAttribute = store.GetPropertyDisplayAttribute(this);
            }
 
            if (displayAttribute != null) {
                displayName = displayAttribute.GetName();
            }
 
            return displayName ?? this.MemberName;
        }
 
        /// <summary>
        /// Initializes the <see cref="ValidationContext"/> with a service provider that can return
        /// service instances by <see cref="Type"/> when <see cref="GetService"/> is called.
        /// </summary>
        /// <param name="serviceProvider">
        /// A <see cref="Func{T, TResult}"/> that can return service instances given the
        /// desired <see cref="Type"/> when <see cref="GetService"/> is called.
        /// If it is <c>null</c>, <see cref="GetService"/> will always return <c>null</c>.
        /// </param>
        public void InitializeServiceProvider(Func<Type, object> serviceProvider) {
            this._serviceProvider = serviceProvider;
        }
 
        #endregion
 
        #region IServiceProvider Members
 
#if SILVERLIGHT
        /// <summary>
        /// See <see cref="IServiceProvider.GetService(Type)"/>.
        /// </summary>
        /// <param name="serviceType">The type of the service needed.</param>
        /// <returns>An instance of that service or null if it is not available.</returns>
#else
        /// <summary>
        /// See <see cref="IServiceProvider.GetService(Type)"/>.
        /// When the <see cref="ServiceContainer"/> is in use, it will be used
        /// first to retrieve the requested service.  If the <see cref="ServiceContainer"/>
        /// is not being used or it cannot resolve the service, then the
        /// <see cref="IServiceProvider"/> provided to this <see cref="ValidationContext"/>
        /// will be queried for the service type.
        /// </summary>
        /// <param name="serviceType">The type of the service needed.</param>
        /// <returns>An instance of that service or null if it is not available.</returns>
#endif
        public object GetService(Type serviceType) {
            object service = null;
 
#if !SILVERLIGHT
            if (this._serviceContainer != null) {
                service = this._serviceContainer.GetService(serviceType);
            }
#endif
 
            if (service == null && this._serviceProvider != null) {
                service = this._serviceProvider(serviceType);
            }
 
            return service;
        }
 
        #endregion
 
#if !SILVERLIGHT
        #region Service Container
 
        private Design.IServiceContainer _serviceContainer;
 
        /// <summary>
        /// A <see cref="Design.IServiceContainer"/> that can be used for adding,
        /// removing, and getting services during validation.  <see cref="GetService"/>
        /// will query into this container as well as the <see cref="IServiceProvider"/>
        /// specified in the constructor.
        /// </summary>
        /// <remarks>
        /// If the <see cref="IServiceProvider"/> specified to the constructor implements
        /// <see cref="Design.IServiceContainer"/>, then it will be used as the
        /// <see cref="ServiceContainer"/>, otherwise an empty container will be initialized.
        /// </remarks>
        public Design.IServiceContainer ServiceContainer {
            get {
                if (this._serviceContainer == null) {
                    this._serviceContainer = new ValidationContextServiceContainer();
                }
 
                return this._serviceContainer;
            }
        }
 
        /// <summary>
        /// Private implementation of <see cref="Design.IServiceContainer"/> to act
        /// as a default service container on <see cref="ValidationContext"/>.
        /// </summary>
        [SuppressMessage("Microsoft.Usage", "CA2302:FlagServiceProviders", Justification = "ValidationContext does not need to work with COM interop types. So this is fine.")]
        private class ValidationContextServiceContainer : Design.IServiceContainer
        {
        #region Member Fields
 
            private Design.IServiceContainer _parentContainer;
            private Dictionary<Type, object> _services = new Dictionary<Type, object>();
            private readonly object _lock = new object();
 
        #endregion
 
        #region Constructors
 
            /// <summary>
            /// Constructs a new service container that does not have a parent container
            /// </summary>
            internal ValidationContextServiceContainer() {
            }
 
            /// <summary>
            /// Contstructs a new service container that has a parent container, making this container
            /// a wrapper around the parent container.  Calls to <c>AddService</c> and <c>RemoveService</c>
            /// will promote to the parent container by default, unless <paramref name="promote"/> is
            /// specified as <c>false</c> on those calls.
            /// </summary>
            /// <param name="parentContainer">The parent container to wrap into this container.</param>
            internal ValidationContextServiceContainer(Design.IServiceContainer parentContainer) {
                this._parentContainer = parentContainer;
            }
 
        #endregion
 
        #region IServiceContainer Members
 
            [SuppressMessage("Microsoft.Usage", "CA2301:EmbeddableTypesInContainersRule", MessageId = "_services", Justification = "ValidationContext does not need to work with COM interop types. So this is fine.")]
            public void AddService(Type serviceType, Design.ServiceCreatorCallback callback, bool promote)
            {
                if (promote && this._parentContainer != null) {
                    this._parentContainer.AddService(serviceType, callback, promote);
                } else {
                    lock (this._lock) {
                        if (this._services.ContainsKey(serviceType)) {
                            throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.DataAnnotationsResources.ValidationContextServiceContainer_ItemAlreadyExists, serviceType), "serviceType");
                        }
 
                        this._services.Add(serviceType, callback);
                    }
                }
            }
 
            public void AddService(Type serviceType, Design.ServiceCreatorCallback callback) {
                this.AddService(serviceType, callback, true);
            }
 
            [SuppressMessage("Microsoft.Usage", "CA2301:EmbeddableTypesInContainersRule", MessageId = "_services", Justification = "ValidationContext does not need to work with COM interop types. So this is fine.")]
            public void AddService(Type serviceType, object serviceInstance, bool promote)
            {
                if (promote && this._parentContainer != null) {
                    this._parentContainer.AddService(serviceType, serviceInstance, promote);
                } else {
                    lock (this._lock) {
                        if (this._services.ContainsKey(serviceType)) {
                            throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.DataAnnotationsResources.ValidationContextServiceContainer_ItemAlreadyExists, serviceType), "serviceType");
                        }
 
                        this._services.Add(serviceType, serviceInstance);
                    }
                }
            }
 
            public void AddService(Type serviceType, object serviceInstance) {
                this.AddService(serviceType, serviceInstance, true);
            }
 
            public void RemoveService(Type serviceType, bool promote) {
                lock (this._lock) {
                    if (this._services.ContainsKey(serviceType)) {
                        this._services.Remove(serviceType);
                    }
                }
 
                if (promote && this._parentContainer != null) {
                    this._parentContainer.RemoveService(serviceType);
                }
            }
 
            public void RemoveService(Type serviceType) {
                this.RemoveService(serviceType, true);
            }
 
        #endregion
 
        #region IServiceProvider Members
 
            public object GetService(Type serviceType) {
                if (serviceType == null) {
                    throw new ArgumentNullException("serviceType");
                }
 
                object service = null;
                this._services.TryGetValue(serviceType, out service);
 
                if (service == null && this._parentContainer != null) {
                    service = this._parentContainer.GetService(serviceType);
                }
 
                Design.ServiceCreatorCallback callback = service as Design.ServiceCreatorCallback;
 
                if (callback != null) {
                    service = callback(this, serviceType);
                }
 
                return service;
            }
 
        #endregion
        }
 
        #endregion
#endif
    }
}