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
}
}