1 namespace System.Web.ModelBinding {
3 using System.Collections.Generic;
4 using System.ComponentModel.DataAnnotations;
5 using System.Diagnostics.CodeAnalysis;
6 using System.Globalization;
8 using System.Reflection;
9 using System.Threading;
11 // A factory for validators based on ValidationAttribute
12 public delegate ModelValidator DataAnnotationsModelValidationFactory(ModelMetadata metadata, ModelBindingExecutionContext context, ValidationAttribute attribute);
14 // A factory for validators based on IValidatableObject
15 public delegate ModelValidator DataAnnotationsValidatableObjectAdapterFactory(ModelMetadata metadata, ModelBindingExecutionContext context);
18 /// An implementation of <see cref="ModelValidatorProvider"/> which providers validators
19 /// for attributes which derive from <see cref="ValidationAttribute"/>. It also provides
20 /// a validator for types which implement <see cref="IValidatableObject"/>. To support
21 /// client side validation, you can either register adapters through the static methods
22 /// on this class, or by having your validation attributes implement
23 /// <see cref="IClientValidatable"/>. The logic to support IClientValidatable
24 /// is implemented in <see cref="DataAnnotationsModelValidator"/>.
26 public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider {
27 private static bool _addImplicitRequiredAttributeForValueTypes = true;
28 private static ReaderWriterLockSlim _adaptersLock = new ReaderWriterLockSlim();
30 // Factories for validation attributes
32 internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
33 (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute);
35 internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>() {
37 typeof(RangeAttribute),
38 (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute)
41 typeof(RegularExpressionAttribute),
42 (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute)
45 typeof(RequiredAttribute),
46 (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute)
49 typeof(StringLengthAttribute),
50 (metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute)
54 // Factories for IValidatableObject models
56 internal static DataAnnotationsValidatableObjectAdapterFactory DefaultValidatableFactory =
57 (metadata, context) => new ValidatableObjectAdapter(metadata, context);
59 internal static Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory> ValidatableFactories = new Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory>();
61 public static bool AddImplicitRequiredAttributeForValueTypes {
63 return _addImplicitRequiredAttributeForValueTypes;
66 _addImplicitRequiredAttributeForValueTypes = value;
70 protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ModelBindingExecutionContext context, IEnumerable<Attribute> attributes) {
71 _adaptersLock.EnterReadLock();
74 List<ModelValidator> results = new List<ModelValidator>();
76 // Add an implied [Required] attribute for any non-nullable value type,
77 // unless they've configured us not to do that.
78 if (AddImplicitRequiredAttributeForValueTypes &&
79 metadata.IsRequired &&
80 !attributes.Any(a => a is RequiredAttribute)) {
81 attributes = attributes.Concat(new[] { new RequiredAttribute() });
84 // Produce a validator for each validation attribute we find
85 foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>()) {
86 DataAnnotationsModelValidationFactory factory;
87 if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory)) {
88 factory = DefaultAttributeFactory;
90 results.Add(factory(metadata, context, attribute));
93 // Produce a validator if the type supports IValidatableObject
94 if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType)) {
95 DataAnnotationsValidatableObjectAdapterFactory factory;
96 if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory)) {
97 factory = DefaultValidatableFactory;
99 results.Add(factory(metadata, context));
105 _adaptersLock.ExitReadLock();
109 #region Validation attribute adapter registration
111 public static void RegisterAdapter(Type attributeType, Type adapterType) {
112 ValidateAttributeType(attributeType);
113 ValidateAttributeAdapterType(adapterType);
114 ConstructorInfo constructor = GetAttributeAdapterConstructor(attributeType, adapterType);
116 _adaptersLock.EnterWriteLock();
119 AttributeFactories[attributeType] = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute });
122 _adaptersLock.ExitWriteLock();
126 [SuppressMessage("Microsoft.Usage", "CA2301:EmbeddableTypesInContainersRule", MessageId = "AttributeFactories", Justification = "The types that go into this dictionary are specifically ValidationAttribute derived types.")]
127 public static void RegisterAdapterFactory(Type attributeType, DataAnnotationsModelValidationFactory factory)
129 ValidateAttributeType(attributeType);
130 ValidateAttributeFactory(factory);
132 _adaptersLock.EnterWriteLock();
135 AttributeFactories[attributeType] = factory;
138 _adaptersLock.ExitWriteLock();
142 public static void RegisterDefaultAdapter(Type adapterType) {
143 ValidateAttributeAdapterType(adapterType);
144 ConstructorInfo constructor = GetAttributeAdapterConstructor(typeof(ValidationAttribute), adapterType);
146 DefaultAttributeFactory = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute });
149 public static void RegisterDefaultAdapterFactory(DataAnnotationsModelValidationFactory factory) {
150 ValidateAttributeFactory(factory);
152 DefaultAttributeFactory = factory;
157 private static ConstructorInfo GetAttributeAdapterConstructor(Type attributeType, Type adapterType) {
158 ConstructorInfo constructor = adapterType.GetConstructor(new[] { typeof(ModelMetadata), typeof(ModelBindingExecutionContext), attributeType });
159 if (constructor == null) {
160 throw new ArgumentException(
162 CultureInfo.CurrentCulture,
163 SR.GetString(SR.DataAnnotationsModelValidatorProvider_ConstructorRequirements),
164 adapterType.FullName,
165 typeof(ModelMetadata).FullName,
166 typeof(ModelBindingExecutionContext).FullName,
167 attributeType.FullName
176 private static void ValidateAttributeAdapterType(Type adapterType) {
177 if (adapterType == null) {
178 throw new ArgumentNullException("adapterType");
180 if (!typeof(ModelValidator).IsAssignableFrom(adapterType)) {
181 throw new ArgumentException(
183 CultureInfo.CurrentCulture,
184 SR.GetString(SR.Common_TypeMustDriveFromType),
185 adapterType.FullName,
186 typeof(ModelValidator).FullName
193 private static void ValidateAttributeType(Type attributeType) {
194 if (attributeType == null) {
195 throw new ArgumentNullException("attributeType");
197 if (!typeof(ValidationAttribute).IsAssignableFrom(attributeType)) {
198 throw new ArgumentException(
200 CultureInfo.CurrentCulture,
201 SR.GetString(SR.Common_TypeMustDriveFromType),
202 attributeType.FullName,
203 typeof(ValidationAttribute).FullName
209 private static void ValidateAttributeFactory(DataAnnotationsModelValidationFactory factory) {
210 if (factory == null) {
211 throw new ArgumentNullException("factory");
217 #region IValidatableObject adapter registration
220 /// Registers an adapter type for the given <see cref="modelType"/>, which must
221 /// implement <see cref="IValidatableObject"/>. The adapter type must derive from
222 /// <see cref="ModelValidator"/> and it must contain a public constructor
223 /// which takes two parameters of types <see cref="ModelMetadata"/> and
224 /// <see cref="ModelBindingExecutionContext"/>.
226 public static void RegisterValidatableObjectAdapter(Type modelType, Type adapterType) {
227 ValidateValidatableModelType(modelType);
228 ValidateValidatableAdapterType(adapterType);
229 ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType);
231 _adaptersLock.EnterWriteLock();
234 ValidatableFactories[modelType] = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context });
237 _adaptersLock.ExitWriteLock();
242 /// Registers an adapter factory for the given <see cref="modelType"/>, which must
243 /// implement <see cref="IValidatableObject"/>.
245 [SuppressMessage("Microsoft.Usage", "CA2301:EmbeddableTypesInContainersRule", MessageId = "ValidatableFactories", Justification = "The types that go into this dictionary are specifically those which implement IValidatableObject.")]
246 public static void RegisterValidatableObjectAdapterFactory(Type modelType, DataAnnotationsValidatableObjectAdapterFactory factory)
248 ValidateValidatableModelType(modelType);
249 ValidateValidatableFactory(factory);
251 _adaptersLock.EnterWriteLock();
254 ValidatableFactories[modelType] = factory;
257 _adaptersLock.ExitWriteLock();
262 /// Registers the default adapter type for objects which implement
263 /// <see cref="IValidatableObject"/>. The adapter type must derive from
264 /// <see cref="ModelValidator"/> and it must contain a public constructor
265 /// which takes two parameters of types <see cref="ModelMetadata"/> and
266 /// <see cref="ModelBindingExecutionContext"/>.
268 public static void RegisterDefaultValidatableObjectAdapter(Type adapterType) {
269 ValidateValidatableAdapterType(adapterType);
270 ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType);
272 DefaultValidatableFactory = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context });
276 /// Registers the default adapter factory for objects which implement
277 /// <see cref="IValidatableObject"/>.
279 public static void RegisterDefaultValidatableObjectAdapterFactory(DataAnnotationsValidatableObjectAdapterFactory factory) {
280 ValidateValidatableFactory(factory);
282 DefaultValidatableFactory = factory;
287 private static ConstructorInfo GetValidatableAdapterConstructor(Type adapterType) {
288 ConstructorInfo constructor = adapterType.GetConstructor(new[] { typeof(ModelMetadata), typeof(ModelBindingExecutionContext) });
289 if (constructor == null) {
290 throw new ArgumentException(
292 CultureInfo.CurrentCulture,
293 SR.GetString(SR.DataAnnotationsModelValidatorProvider_ValidatableConstructorRequirements),
294 adapterType.FullName,
295 typeof(ModelMetadata).FullName,
296 typeof(ModelBindingExecutionContext).FullName
305 private static void ValidateValidatableAdapterType(Type adapterType) {
306 if (adapterType == null) {
307 throw new ArgumentNullException("adapterType");
309 if (!typeof(ModelValidator).IsAssignableFrom(adapterType)) {
310 throw new ArgumentException(
312 CultureInfo.CurrentCulture,
313 SR.GetString(SR.Common_TypeMustDriveFromType),
314 adapterType.FullName,
315 typeof(ModelValidator).FullName
321 private static void ValidateValidatableModelType(Type modelType) {
322 if (modelType == null) {
323 throw new ArgumentNullException("modelType");
325 if (!typeof(IValidatableObject).IsAssignableFrom(modelType)) {
326 throw new ArgumentException(
328 CultureInfo.CurrentCulture,
329 SR.GetString(SR.Common_TypeMustDriveFromType),
331 typeof(IValidatableObject).FullName
338 private static void ValidateValidatableFactory(DataAnnotationsValidatableObjectAdapterFactory factory) {
339 if (factory == null) {
340 throw new ArgumentNullException("factory");