e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
446 lines
19 KiB
C#
446 lines
19 KiB
C#
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
|
|
}
|
|
}
|