2 // ValidationAttribute.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Marek Habersack <mhabersack@novell.com>
8 // Copyright (C) 2008-2010 Novell Inc. http://novell.com
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.ComponentModel;
33 using System.Reflection;
35 namespace System.ComponentModel.DataAnnotations
37 public abstract class ValidationAttribute : Attribute
39 const string DEFAULT_ERROR_MESSAGE = "The field {0} is invalid.";
41 object nestedCallLock = new object ();
44 string errorMessageResourceName;
45 string errorMessageString;
46 Type errorMessageResourceType;
49 string fallbackErrorMessage;
50 Func <string> errorMessageAccessor;
52 protected ValidationAttribute ()
56 protected ValidationAttribute (Func<string> errorMessageAccessor)
58 this.errorMessageAccessor = errorMessageAccessor;
61 protected ValidationAttribute (string errorMessage)
63 fallbackErrorMessage = errorMessage;
66 public virtual string FormatErrorMessage (string name)
68 string format = ErrorMessageString;
69 if (String.IsNullOrEmpty (format))
72 return String.Format (ErrorMessageString, name);
75 public string ErrorMessage {
76 get { return errorMessage; }
79 if (errorMessage != null)
80 errorMessageAccessor = null;
83 public string ErrorMessageResourceName { get; set; }
84 public Type ErrorMessageResourceType { get; set; }
86 public string ErrorMessage {
87 get { return errorMessage; }
91 if (errorMessage != null)
92 throw new InvalidOperationException ("This property can be set only once.");
94 if (String.IsNullOrEmpty (value))
95 throw new ArgumentException ("Value cannot be null or empty.", "value");
97 if (errorMessageResourceName != null || errorMessageResourceType != null)
98 throw new InvalidOperationException ("This property cannot be set because the attribute is already in the resource mode.");
100 errorMessage = value;
104 public string ErrorMessageResourceName {
105 get { return errorMessageResourceName; }
108 if (errorMessageResourceName != null)
109 throw new InvalidOperationException ("This property can be set only once.");
111 if (String.IsNullOrEmpty (value))
112 throw new ArgumentException ("Value cannot be null or empty.", "value");
114 errorMessageResourceName = value;
115 if (errorMessageResourceType != null)
116 errorMessageString = GetStringFromResourceAccessor ();
120 public Type ErrorMessageResourceType {
121 get { return errorMessageResourceType; }
123 errorMessageResourceType = value;
124 if (!String.IsNullOrEmpty (errorMessageResourceName))
125 errorMessageString = GetStringFromResourceAccessor ();
129 protected string ErrorMessageString {
130 get { return GetStringFromResourceAccessor (); }
133 NotImplementedException NestedNIEX ()
135 return new NotImplementedException ("IsValid(object value) has not been implemented by this class. The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context).");
139 // This is the weirdest (to be gentle) idea ever... The IsValid (object) overload
140 // throws the NIEX when it is called from the default IsValid (object,
141 // ValidationContext) overload, but not when directly. And the reverse situation is
142 // true as well. That means, the calls detect the "nested" calls and that we need to
143 // protect the nestedCall flag... ugh
145 public virtual bool IsValid (object value)
147 lock (nestedCallLock) {
152 return IsValid (value, null) == ValidationResult.Success;
159 protected virtual ValidationResult IsValid (object value, ValidationContext validationContext)
161 lock (nestedCallLock) {
167 if (!IsValid (value)) {
169 if (validationContext == null)
170 throw new NullReferenceException (".NET emulation.");
171 string memberName = validationContext.MemberName;
172 return new ValidationResult (FormatErrorMessage (validationContext.DisplayName), memberName != null ? new string[] { memberName } : new string[] {});
179 return ValidationResult.Success;
182 public abstract bool IsValid (object value);
186 public ValidationResult GetValidationResult (object value, ValidationContext validationContext)
188 if (validationContext == null)
189 throw new ArgumentNullException ("validationContext");
191 ValidationResult ret = IsValid (value, validationContext);
192 if (ret != null && String.IsNullOrEmpty (ret.ErrorMessage))
193 ret.ErrorMessage = FormatErrorMessage (validationContext.DisplayName);
198 string GetStringFromResourceAccessor ()
200 string resourceName = ErrorMessageResourceName;
201 Type resourceType = ErrorMessageResourceType;
202 string errorMessage = ErrorMessage;
204 if (resourceName != null && errorMessage != null)
205 throw new InvalidOperationException ("Either ErrorMessage or ErrorMessageResourceName must be set, but not both.");
207 if (resourceType == null ^ resourceName == null)
208 throw new InvalidOperationException ("Both ErrorMessageResourceType and ErrorMessageResourceName must be set on this attribute.");
212 if (resourceType != null) {
213 PropertyInfo pi = resourceType.GetProperty (resourceName, BindingFlags.Public | BindingFlags.Static);
214 if (pi == null || !pi.CanRead)
215 throw new InvalidOperationException (
216 String.Format ("Resource type '{0}' does not have an accessible static property named '{1}'.",
217 resourceType, resourceName)
220 if (pi.PropertyType != typeof (string))
221 throw new InvalidOperationException (
222 String.Format ("The property '{0}' on resource type '{1}' is not a string type.",
223 resourceName, resourceType)
226 return pi.GetValue (null, null) as string;
229 if (errorMessage == null) {
230 if (errorMessageAccessor != null)
231 return errorMessageAccessor ();
233 if (fallbackErrorMessage != null)
234 return fallbackErrorMessage;
236 return DEFAULT_ERROR_MESSAGE;
242 public void Validate (object value, ValidationContext validationContext)
244 if (validationContext == null)
245 throw new ArgumentNullException ("validationContext");
247 ValidationResult result = IsValid (value, validationContext);
248 if (result != null) {
249 string message = result.ErrorMessage;
251 message = FormatErrorMessage (validationContext.DisplayName);
253 throw new ValidationException (message, this, value);
257 public void Validate (object value, string name)
259 if (!IsValid (value))
260 throw new ValidationException (FormatErrorMessage (name), this, value);