1 namespace System.Web.DynamicData {
3 using System.Collections.Generic;
4 using System.ComponentModel;
5 using System.ComponentModel.DataAnnotations;
6 using System.Diagnostics;
8 using System.Globalization;
11 using System.Web.Resources;
13 using System.Web.UI.WebControls;
16 /// Validator that enforces model validation. It can be used either at the field level or the entity level
18 [ToolboxBitmap(typeof(DynamicValidator), "DynamicValidator.bmp")]
19 public class DynamicValidator : BaseValidator {
21 private IDynamicDataSource _dataSource;
22 private Exception _exception;
23 private Dictionary<Type, bool> _ignoredModelValidationAttributes;
26 /// The name of the column to be validated, or null for entity level validation
30 public string ColumnName {
32 return (Column == null) ? String.Empty : Column.Name;
36 private IDynamicDataSource DynamicDataSource {
38 if (_dataSource == null) {
39 // get data source for the parent container.
40 _dataSource = this.FindDataSourceControl();
41 // get the data source for the targeted data bound control.
42 if (_dataSource == null) {
43 Control c = NamingContainer.FindControl(ControlToValidate);
45 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
46 DynamicDataResources.DynamicValidator_ControlNotFound, ControlToValidate, ID));
49 _dataSource = c.FindDataSourceControl();
57 /// The column to be validated, or null for entity level validation
61 public MetaColumn Column { get; set; }
64 /// The validation exception that occurred, if any
66 protected virtual Exception ValidationException {
76 /// Overridden from base
78 protected override bool ControlPropertiesValid() {
79 bool hasDataSource = DynamicDataSource != null;
81 // We can't call the base when there is no Column, because the control will be something like
82 // a GridView, which doesn't have a validation property and would cause the base to fail.
83 if (String.IsNullOrEmpty(ColumnName)) {
87 return base.ControlPropertiesValid();
91 /// Overridden from base
93 protected override bool EvaluateIsValid() {
94 // Check if there were some model exceptions (e.g. OnProductNameChanging throwing)
95 Exception e = ValidationException;
97 ErrorMessage = HttpUtility.HtmlEncode(e.Message);
105 string controlValue = GetControlValidationValue(ControlToValidate);
106 if (controlValue == null) {
107 // can return null if ControlToValidate is empty, or if the control is not written correctly to return a value
108 // this does not mean that the value was null
112 if (Column is MetaForeignKeyColumn || Column is MetaChildrenColumn) {
113 // do not perform conversion or validation on relationship columns as controlValue is the serialized form
114 // of a foreign key which would be useless to a validation attribute
118 // Check if any of our validators want to fail the value
119 controlValue = (string)Column.ConvertEditedValue(controlValue);
122 if (!TryConvertControlValue(controlValue, Column.ColumnType, out value)) {
123 ErrorMessage = HttpUtility.HtmlEncode(DynamicDataResources.DynamicValidator_CannotConvertValue);
127 return ValueIsValid(value);
130 internal static bool TryConvertControlValue(string controlValue, Type columnType, out object value) {
132 if (controlValue == null) {
134 } else if (columnType == typeof(string)) {
135 value = controlValue;
136 } else if (controlValue.Length != 0) {
137 value = Misc.ChangeType(controlValue, columnType);
142 } catch (Exception) {
148 private bool ValueIsValid(object value) {
149 // Go through all the model validation attribute to make sure they're valid
150 foreach (var attrib in Column.Attributes.Cast<Attribute>().OfType<ValidationAttribute>()) {
152 // Ignore it if it's found in the ignore list
153 if (_ignoredModelValidationAttributes != null &&
154 _ignoredModelValidationAttributes.ContainsKey(attrib.GetType())) {
158 //DynamicValidator can not pass in a ValidationContext as it does
159 //not have an easy way to get the data object row. Hence we will
160 //not support attributes that require Validation Context (Ex : CompareAttribute).
161 if (attrib.RequiresValidationContext) {
165 if (!attrib.IsValid(value)) {
166 ErrorMessage = HttpUtility.HtmlEncode(attrib.FormatErrorMessage(Column.DisplayName));
174 internal void SetIgnoredModelValidationAttributes(Dictionary<Type, bool> ignoredModelValidationAttributes) {
175 _ignoredModelValidationAttributes = ignoredModelValidationAttributes;
178 private void OnException(object sender, DynamicValidatorEventArgs e) {
179 ValidateException(e.Exception);
183 /// Overridden from base
185 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")]
186 protected override void OnInit(EventArgs e) {
189 // Don't do anything in Design mode
193 IDynamicDataSource dataSource = DynamicDataSource;
194 if (dataSource != null) {
195 // Register for datasource exception so that we're called if an error occurs
196 // during an update/insert/delete
197 dataSource.Exception += new EventHandler<DynamicValidatorEventArgs>(OnException);
202 /// Called when an exception happens. Typically, this sets the ValidationException
204 /// <param name="exception">The exception</param>
205 protected virtual void ValidateException(Exception exception) {
206 if (exception == null) {
210 ValidationException = null;
212 // IDynamicValidatorExceptions are used by LinqDataSource to wrap exceptions caused by problems
213 // with setting model properties (columns), such as exceptions thrown from the OnXYZChanging
215 IDynamicValidatorException e = exception as IDynamicValidatorException;
217 HandleDynamicValidatorException(e);
219 // It's not a column specific exception. e.g. it could be coming from
220 // OnValidate (DLinq), or could be caused by a database error.
221 // We only want to use it if it's a ValidationException, otherwise we
222 // could end up displaying sensitive database errors to the end user
223 if (Column == null && exception is ValidationException) {
224 if (exception.InnerException == null) {
225 ValidationException = exception;
227 ValidationException = exception.InnerException;
233 private void HandleDynamicValidatorException(IDynamicValidatorException e) {
234 if (Column == null) {
235 // IDynamicValidatorException only applies to column exceptions
239 List<string> columnNames = GetValidationColumnNames(Column);
241 foreach (string name in columnNames) {
242 // see if the exception wraps any child exceptions relevant to this column
244 if (e.InnerExceptions.TryGetValue(name, out inner)) {
245 // Stop as soon as we find the first exception.
246 ValidationException = inner;
253 /// Get the names of all the columns that can throw an exception that will affect the setting of the
254 /// value of the given column.
256 private static List<string> GetValidationColumnNames(MetaColumn column) {
257 List<string> columnNames = new List<string>();
258 columnNames.Add(column.Name); // add it first so that it gets checked first
259 var fkColumn = column as MetaForeignKeyColumn;
260 if (fkColumn != null) {
261 columnNames.AddRange(fkColumn.ForeignKeyNames);