using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; #if SILVERLIGHT using System.Reflection; #endif namespace System.ComponentModel.DataAnnotations { /// /// Describes the context in which a validation is being performed. /// /// /// This class contains information describing the instance on which /// validation is being performed. /// /// It supports so that custom validation /// code can acquire additional services to help it perform its validation. /// /// /// An property bag is available for additional contextual /// information about the validation. Values stored in /// will be available to validation methods that use this /// /// [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 _serviceProvider; private object _objectInstance; private string _memberName; private string _displayName; private Dictionary _items; #endregion #region Constructors /// /// Construct a for a given object instance being validated. /// /// The object instance being validated. It cannot be null. /// When is null public ValidationContext(object instance) : this(instance, null, null) { } /// /// Construct a for a given object instance and an optional /// property bag of . /// /// The object instance being validated. It cannot be null. /// Optional set of key/value pairs to make available to consumers via . /// 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. /// /// When is null public ValidationContext(object instance, IDictionary items) : this(instance, null, items) { } #if SILVERLIGHT /// /// Construct a for a given object instance, an optional , and an optional /// property bag of . /// /// The object instance being validated. It cannot be null. /// /// Optional to use when is called. /// If it is null, will always return null. /// /// Optional set of key/value pairs to make available to consumers via . /// 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. /// /// When is null #else /// /// Construct a for a given object instance, an optional , and an optional /// property bag of . /// /// The object instance being validated. It cannot be null. /// /// Optional to use when is called. /// /// If the specified implements , /// then it will be used as the but its services can still be retrieved /// through as well. /// /// /// Optional set of key/value pairs to make available to consumers via . /// 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. /// /// When is null #endif public ValidationContext(object instance, IServiceProvider serviceProvider, IDictionary 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(items); } else { this._items = new Dictionary(); } this._objectInstance = instance; } #endregion #region Properties /// /// 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. /// Consume this instance with caution! /// /// /// 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. /// public object ObjectInstance { get { return this._objectInstance; } } /// /// Gets the type of the object being validated. It will not be null. /// public Type ObjectType { get { #if SILVERLIGHT return this.ObjectInstance.GetCustomOrCLRType(); #else return this.ObjectInstance.GetType(); #endif } } /// /// Gets or sets the user-visible name of the type or property being validated. /// /// If this name was not explicitly set, this property will consult an associated /// to see if can use that instead. Lacking that, it returns . The /// type name will be used if MemberName has not been set. /// 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; } } /// /// Gets or sets the name of the type or property being validated. /// /// 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. public string MemberName { get { return this._memberName; } set { this._memberName = value; } } /// /// Gets the dictionary of key/value pairs associated with this context. /// /// 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. public IDictionary Items { get { return this._items; } } #endregion #region Methods /// /// Looks up the display name using the DisplayAttribute attached to the respective type or property. /// /// A display-friendly name of the member represented by the . 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; } /// /// Initializes the with a service provider that can return /// service instances by when is called. /// /// /// A that can return service instances given the /// desired when is called. /// If it is null, will always return null. /// public void InitializeServiceProvider(Func serviceProvider) { this._serviceProvider = serviceProvider; } #endregion #region IServiceProvider Members #if SILVERLIGHT /// /// See . /// /// The type of the service needed. /// An instance of that service or null if it is not available. #else /// /// See . /// When the is in use, it will be used /// first to retrieve the requested service. If the /// is not being used or it cannot resolve the service, then the /// provided to this /// will be queried for the service type. /// /// The type of the service needed. /// An instance of that service or null if it is not available. #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; /// /// A that can be used for adding, /// removing, and getting services during validation. /// will query into this container as well as the /// specified in the constructor. /// /// /// If the specified to the constructor implements /// , then it will be used as the /// , otherwise an empty container will be initialized. /// public Design.IServiceContainer ServiceContainer { get { if (this._serviceContainer == null) { this._serviceContainer = new ValidationContextServiceContainer(); } return this._serviceContainer; } } /// /// Private implementation of to act /// as a default service container on . /// [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 _services = new Dictionary(); private readonly object _lock = new object(); #endregion #region Constructors /// /// Constructs a new service container that does not have a parent container /// internal ValidationContextServiceContainer() { } /// /// Contstructs a new service container that has a parent container, making this container /// a wrapper around the parent container. Calls to AddService and RemoveService /// will promote to the parent container by default, unless is /// specified as false on those calls. /// /// The parent container to wrap into this container. 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 } }