Moving BSTR conv to native code in SecureStringToBSTR.
[mono.git] / mcs / class / referencesource / System.Web / ModelBinding / DataAnnotationsModelValidatorProvider.cs
1 namespace System.Web.ModelBinding {
2     using System;
3 using System.Collections.Generic;
4 using System.ComponentModel.DataAnnotations;
5 using System.Diagnostics.CodeAnalysis;
6 using System.Globalization;
7 using System.Linq;
8 using System.Reflection;
9 using System.Threading;
10
11     // A factory for validators based on ValidationAttribute
12     public delegate ModelValidator DataAnnotationsModelValidationFactory(ModelMetadata metadata, ModelBindingExecutionContext context, ValidationAttribute attribute);
13
14     // A factory for validators based on IValidatableObject
15     public delegate ModelValidator DataAnnotationsValidatableObjectAdapterFactory(ModelMetadata metadata, ModelBindingExecutionContext context);
16
17     /// <summary>
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"/>.
25     /// </summary>
26     public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider {
27         private static bool _addImplicitRequiredAttributeForValueTypes = true;
28         private static ReaderWriterLockSlim _adaptersLock = new ReaderWriterLockSlim();
29
30         // Factories for validation attributes
31
32         internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
33             (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute);
34
35         internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>() {
36             {
37                 typeof(RangeAttribute),
38                 (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute)
39             },
40             {
41                 typeof(RegularExpressionAttribute),
42                 (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute)
43             },
44             {
45                 typeof(RequiredAttribute),
46                 (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute)
47             },
48             {
49                 typeof(StringLengthAttribute),
50                 (metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute)
51             },
52         };
53
54         // Factories for IValidatableObject models
55
56         internal static DataAnnotationsValidatableObjectAdapterFactory DefaultValidatableFactory =
57             (metadata, context) => new ValidatableObjectAdapter(metadata, context);
58
59         internal static Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory> ValidatableFactories = new Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory>();
60
61         public static bool AddImplicitRequiredAttributeForValueTypes {
62             get {
63                 return _addImplicitRequiredAttributeForValueTypes;
64             }
65             set {
66                 _addImplicitRequiredAttributeForValueTypes = value;
67             }
68         }
69
70         protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ModelBindingExecutionContext context, IEnumerable<Attribute> attributes) {
71             _adaptersLock.EnterReadLock();
72
73             try {
74                 List<ModelValidator> results = new List<ModelValidator>();
75
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() });
82                 }
83
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;
89                     }
90                     results.Add(factory(metadata, context, attribute));
91                 }
92
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;
98                     }
99                     results.Add(factory(metadata, context));
100                 }
101
102                 return results;
103             }
104             finally {
105                 _adaptersLock.ExitReadLock();
106             }
107         }
108
109         #region Validation attribute adapter registration
110
111         public static void RegisterAdapter(Type attributeType, Type adapterType) {
112             ValidateAttributeType(attributeType);
113             ValidateAttributeAdapterType(adapterType);
114             ConstructorInfo constructor = GetAttributeAdapterConstructor(attributeType, adapterType);
115
116             _adaptersLock.EnterWriteLock();
117
118             try {
119                 AttributeFactories[attributeType] = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute });
120             }
121             finally {
122                 _adaptersLock.ExitWriteLock();
123             }
124         }
125
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)
128         {
129             ValidateAttributeType(attributeType);
130             ValidateAttributeFactory(factory);
131
132             _adaptersLock.EnterWriteLock();
133
134             try {
135                 AttributeFactories[attributeType] = factory;
136             }
137             finally {
138                 _adaptersLock.ExitWriteLock();
139             }
140         }
141
142         public static void RegisterDefaultAdapter(Type adapterType) {
143             ValidateAttributeAdapterType(adapterType);
144             ConstructorInfo constructor = GetAttributeAdapterConstructor(typeof(ValidationAttribute), adapterType);
145
146             DefaultAttributeFactory = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute });
147         }
148
149         public static void RegisterDefaultAdapterFactory(DataAnnotationsModelValidationFactory factory) {
150             ValidateAttributeFactory(factory);
151
152             DefaultAttributeFactory = factory;
153         }
154
155         // Helpers 
156
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(
161                     String.Format(
162                         CultureInfo.CurrentCulture,
163                         SR.GetString(SR.DataAnnotationsModelValidatorProvider_ConstructorRequirements),
164                         adapterType.FullName,
165                         typeof(ModelMetadata).FullName,
166                         typeof(ModelBindingExecutionContext).FullName,
167                         attributeType.FullName
168                     ),
169                     "adapterType"
170                 );
171             }
172
173             return constructor;
174         }
175
176         private static void ValidateAttributeAdapterType(Type adapterType) {
177             if (adapterType == null) {
178                 throw new ArgumentNullException("adapterType");
179             }
180             if (!typeof(ModelValidator).IsAssignableFrom(adapterType)) {
181                 throw new ArgumentException(
182                     String.Format(
183                         CultureInfo.CurrentCulture,
184                         SR.GetString(SR.Common_TypeMustDriveFromType),
185                         adapterType.FullName,
186                         typeof(ModelValidator).FullName
187                     ),
188                     "adapterType"
189                 );
190             }
191         }
192
193         private static void ValidateAttributeType(Type attributeType) {
194             if (attributeType == null) {
195                 throw new ArgumentNullException("attributeType");
196             }
197             if (!typeof(ValidationAttribute).IsAssignableFrom(attributeType)) {
198                 throw new ArgumentException(
199                     String.Format(
200                         CultureInfo.CurrentCulture,
201                         SR.GetString(SR.Common_TypeMustDriveFromType),
202                         attributeType.FullName,
203                         typeof(ValidationAttribute).FullName
204                     ),
205                     "attributeType");
206             }
207         }
208
209         private static void ValidateAttributeFactory(DataAnnotationsModelValidationFactory factory) {
210             if (factory == null) {
211                 throw new ArgumentNullException("factory");
212             }
213         }
214
215         #endregion
216
217         #region IValidatableObject adapter registration
218
219         /// <summary>
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"/>.
225         /// </summary>
226         public static void RegisterValidatableObjectAdapter(Type modelType, Type adapterType) {
227             ValidateValidatableModelType(modelType);
228             ValidateValidatableAdapterType(adapterType);
229             ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType);
230
231             _adaptersLock.EnterWriteLock();
232
233             try {
234                 ValidatableFactories[modelType] = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context });
235             }
236             finally {
237                 _adaptersLock.ExitWriteLock();
238             }
239         }
240
241         /// <summary>
242         /// Registers an adapter factory for the given <see cref="modelType"/>, which must
243         /// implement <see cref="IValidatableObject"/>.
244         /// </summary>
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)
247         {
248             ValidateValidatableModelType(modelType);
249             ValidateValidatableFactory(factory);
250
251             _adaptersLock.EnterWriteLock();
252
253             try {
254                 ValidatableFactories[modelType] = factory;
255             }
256             finally {
257                 _adaptersLock.ExitWriteLock();
258             }
259         }
260
261         /// <summary>
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"/>.
267         /// </summary>
268         public static void RegisterDefaultValidatableObjectAdapter(Type adapterType) {
269             ValidateValidatableAdapterType(adapterType);
270             ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType);
271
272             DefaultValidatableFactory = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context });
273         }
274
275         /// <summary>
276         /// Registers the default adapter factory for objects which implement
277         /// <see cref="IValidatableObject"/>.
278         /// </summary>
279         public static void RegisterDefaultValidatableObjectAdapterFactory(DataAnnotationsValidatableObjectAdapterFactory factory) {
280             ValidateValidatableFactory(factory);
281
282             DefaultValidatableFactory = factory;
283         }
284
285         // Helpers 
286
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(
291                     String.Format(
292                         CultureInfo.CurrentCulture,
293                         SR.GetString(SR.DataAnnotationsModelValidatorProvider_ValidatableConstructorRequirements),
294                         adapterType.FullName,
295                         typeof(ModelMetadata).FullName,
296                         typeof(ModelBindingExecutionContext).FullName
297                     ),
298                     "adapterType"
299                 );
300             }
301
302             return constructor;
303         }
304
305         private static void ValidateValidatableAdapterType(Type adapterType) {
306             if (adapterType == null) {
307                 throw new ArgumentNullException("adapterType");
308             }
309             if (!typeof(ModelValidator).IsAssignableFrom(adapterType)) {
310                 throw new ArgumentException(
311                     String.Format(
312                         CultureInfo.CurrentCulture,
313                         SR.GetString(SR.Common_TypeMustDriveFromType),
314                         adapterType.FullName,
315                         typeof(ModelValidator).FullName
316                     ),
317                     "adapterType");
318             }
319         }
320
321         private static void ValidateValidatableModelType(Type modelType) {
322             if (modelType == null) {
323                 throw new ArgumentNullException("modelType");
324             }
325             if (!typeof(IValidatableObject).IsAssignableFrom(modelType)) {
326                 throw new ArgumentException(
327                     String.Format(
328                         CultureInfo.CurrentCulture,
329                         SR.GetString(SR.Common_TypeMustDriveFromType),
330                         modelType.FullName,
331                         typeof(IValidatableObject).FullName
332                     ),
333                     "modelType"
334                 );
335             }
336         }
337
338         private static void ValidateValidatableFactory(DataAnnotationsValidatableObjectAdapterFactory factory) {
339             if (factory == null) {
340                 throw new ArgumentNullException("factory");
341             }
342         }
343
344         #endregion
345     }
346 }