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;
34 using System.Globalization;
36 namespace System.ComponentModel.DataAnnotations
38 public abstract class ValidationAttribute : Attribute
40 const string DEFAULT_ERROR_MESSAGE = "The field {0} is invalid.";
42 object nestedCallLock = new object ();
45 string errorMessageResourceName;
46 string errorMessageString;
47 Type errorMessageResourceType;
50 string fallbackErrorMessage;
51 Func <string> errorMessageAccessor;
53 protected ValidationAttribute ()
57 protected ValidationAttribute (Func<string> errorMessageAccessor)
59 this.errorMessageAccessor = errorMessageAccessor;
62 protected ValidationAttribute (string errorMessage)
64 fallbackErrorMessage = errorMessage;
67 public virtual string FormatErrorMessage (string name)
69 string format = ErrorMessageString;
70 if (String.IsNullOrEmpty (format))
73 return String.Format (ErrorMessageString, name);
76 public string ErrorMessage {
77 get { return errorMessage; }
80 if (errorMessage != null)
81 errorMessageAccessor = null;
84 public string ErrorMessageResourceName { get; set; }
85 public Type ErrorMessageResourceType { get; set; }
87 public string ErrorMessage {
88 get { return errorMessage; }
92 if (errorMessage != null)
93 throw new InvalidOperationException ("This property can be set only once.");
95 if (String.IsNullOrEmpty (value))
96 throw new ArgumentException ("Value cannot be null or empty.", "value");
98 if (errorMessageResourceName != null || errorMessageResourceType != null)
99 throw new InvalidOperationException ("This property cannot be set because the attribute is already in the resource mode.");
101 errorMessage = value;
105 public string ErrorMessageResourceName {
106 get { return errorMessageResourceName; }
109 if (errorMessageResourceName != null)
110 throw new InvalidOperationException ("This property can be set only once.");
112 if (String.IsNullOrEmpty (value))
113 throw new ArgumentException ("Value cannot be null or empty.", "value");
115 errorMessageResourceName = value;
116 if (errorMessageResourceType != null)
117 errorMessageString = GetStringFromResourceAccessor ();
121 public Type ErrorMessageResourceType {
122 get { return errorMessageResourceType; }
124 errorMessageResourceType = value;
125 if (!String.IsNullOrEmpty (errorMessageResourceName))
126 errorMessageString = GetStringFromResourceAccessor ();
130 protected string ErrorMessageString {
131 get { return GetStringFromResourceAccessor (); }
135 // See: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute.requiresvalidationcontext.aspx
136 public virtual bool RequiresValidationContext { get { return false; } }
140 NotImplementedException NestedNIEX ()
142 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).");
146 // This is the weirdest (to be gentle) idea ever... The IsValid (object) overload
147 // throws the NIEX when it is called from the default IsValid (object,
148 // ValidationContext) overload, but not when directly. And the reverse situation is
149 // true as well. That means, the calls detect the "nested" calls and that we need to
150 // protect the nestedCall flag... ugh
152 public virtual bool IsValid (object value)
154 lock (nestedCallLock) {
159 return IsValid (value, null) == ValidationResult.Success;
166 protected virtual ValidationResult IsValid (object value, ValidationContext validationContext)
168 lock (nestedCallLock) {
174 if (!IsValid (value)) {
176 if (validationContext == null)
177 throw new NullReferenceException (".NET emulation.");
178 string memberName = validationContext.MemberName;
179 return new ValidationResult (FormatErrorMessage (validationContext.DisplayName), memberName != null ? new string[] { memberName } : new string[] {});
186 return ValidationResult.Success;
189 public abstract bool IsValid (object value);
193 public ValidationResult GetValidationResult (object value, ValidationContext validationContext)
195 if (validationContext == null)
196 throw new ArgumentNullException ("validationContext");
198 ValidationResult ret = IsValid (value, validationContext);
199 if (ret != null && String.IsNullOrEmpty (ret.ErrorMessage))
200 ret.ErrorMessage = FormatErrorMessage (validationContext.DisplayName);
205 string GetStringFromResourceAccessor ()
207 string resourceName = ErrorMessageResourceName;
208 Type resourceType = ErrorMessageResourceType;
209 string errorMessage = ErrorMessage;
211 if (resourceName != null && errorMessage != null)
212 throw new InvalidOperationException ("Either ErrorMessage or ErrorMessageResourceName must be set, but not both.");
214 if (resourceType == null ^ resourceName == null)
215 throw new InvalidOperationException ("Both ErrorMessageResourceType and ErrorMessageResourceName must be set on this attribute.");
219 if (resourceType != null) {
220 PropertyInfo pi = resourceType.GetProperty (resourceName, BindingFlags.Public | BindingFlags.Static);
221 if (pi == null || !pi.CanRead)
222 throw new InvalidOperationException (
223 String.Format ("Resource type '{0}' does not have an accessible static property named '{1}'.",
224 resourceType, resourceName)
227 if (pi.PropertyType != typeof (string))
228 throw new InvalidOperationException (
229 String.Format ("The property '{0}' on resource type '{1}' is not a string type.",
230 resourceName, resourceType)
233 return pi.GetValue (null, null) as string;
236 if (errorMessage == null) {
237 if (errorMessageAccessor != null)
238 return errorMessageAccessor ();
240 if (fallbackErrorMessage != null)
241 return fallbackErrorMessage;
243 return DEFAULT_ERROR_MESSAGE;
249 public void Validate (object value, ValidationContext validationContext)
251 if (validationContext == null)
252 throw new ArgumentNullException ("validationContext");
254 ValidationResult result = IsValid (value, validationContext);
255 if (result != null) {
256 string message = result.ErrorMessage;
258 message = FormatErrorMessage (validationContext.DisplayName);
260 throw new ValidationException (message, this, value);
264 public void Validate (object value, string name)
266 if (!IsValid (value))
267 throw new ValidationException (FormatErrorMessage (name), this, value);