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;
14 using System.Web.DynamicData.Util;
17 /// Validator that enforces model validation. It can be used either at the field level or the entity level
19 [ToolboxBitmap(typeof(DynamicValidator), "DynamicValidator.bmp")]
20 public class DynamicValidator : BaseValidator {
22 private IDynamicDataSource _dataSource;
23 private Exception _exception;
24 private Dictionary<Type, bool> _ignoredModelValidationAttributes;
27 /// The name of the column to be validated, or null for entity level validation
31 public string ColumnName {
33 return (Column == null) ? String.Empty : Column.Name;
37 private IDynamicDataSource DynamicDataSource {
39 if (_dataSource == null) {
40 // get data source for the parent container.
41 _dataSource = this.FindDataSourceControl();
42 // get the data source for the targeted data bound control.
43 if (_dataSource == null) {
44 Control c = NamingContainer.FindControl(ControlToValidate);
46 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
47 DynamicDataResources.DynamicValidator_ControlNotFound, ControlToValidate, ID));
50 _dataSource = c.FindDataSourceControl();
58 /// The column to be validated, or null for entity level validation
62 public MetaColumn Column { get; set; }
65 /// The validation exception that occurred, if any
67 protected virtual Exception ValidationException {
77 /// Overridden from base
79 protected override bool ControlPropertiesValid() {
80 bool hasDataSource = DynamicDataSource != null;
82 // We can't call the base when there is no Column, because the control will be something like
83 // a GridView, which doesn't have a validation property and would cause the base to fail.
84 if (String.IsNullOrEmpty(ColumnName)) {
88 return base.ControlPropertiesValid();
92 /// Overridden from base
94 protected override bool EvaluateIsValid() {
95 // Check if there were some model exceptions (e.g. OnProductNameChanging throwing)
96 Exception e = ValidationException;
98 ErrorMessage = HttpUtility.HtmlEncode(e.Message);
106 string controlValue = GetControlValidationValue(ControlToValidate);
107 if (controlValue == null) {
108 // can return null if ControlToValidate is empty, or if the control is not written correctly to return a value
109 // this does not mean that the value was null
113 if (Column is MetaForeignKeyColumn || Column is MetaChildrenColumn) {
114 // do not perform conversion or validation on relationship columns as controlValue is the serialized form
115 // of a foreign key which would be useless to a validation attribute
119 // Check if any of our validators want to fail the value
120 controlValue = (string)Column.ConvertEditedValue(controlValue);
123 if (!TryConvertControlValue(controlValue, Column.ColumnType, out value)) {
124 ErrorMessage = HttpUtility.HtmlEncode(DynamicDataResources.DynamicValidator_CannotConvertValue);
128 return ValueIsValid(value);
131 internal static bool TryConvertControlValue(string controlValue, Type columnType, out object value) {
133 if (controlValue == null) {
135 } else if (columnType == typeof(string)) {
136 value = controlValue;
137 } else if (controlValue.Length != 0) {
138 value = Misc.ChangeType(controlValue, columnType);
143 } catch (Exception) {
149 private bool ValueIsValid(object value) {
150 // Go through all the model validation attribute to make sure they're valid
151 foreach (var attrib in Column.Attributes.Cast<Attribute>().OfType<ValidationAttribute>()) {
153 // Ignore it if it's found in the ignore list
154 if (_ignoredModelValidationAttributes != null &&
155 _ignoredModelValidationAttributes.ContainsKey(attrib.GetType())) {
159 //DynamicValidator can not pass in a ValidationContext as it does
160 //not have an easy way to get the data object row. Hence we will
161 //not support attributes that require Validation Context (Ex : CompareAttribute).
162 if (attrib.RequiresValidationContext) {
166 if (!attrib.IsValid(value)) {
167 ErrorMessage = HttpUtility.HtmlEncode(StringLocalizerUtil.GetLocalizedString(attrib, Column.DisplayName));
175 internal void SetIgnoredModelValidationAttributes(Dictionary<Type, bool> ignoredModelValidationAttributes) {
176 _ignoredModelValidationAttributes = ignoredModelValidationAttributes;
179 private void OnException(object sender, DynamicValidatorEventArgs e) {
180 ValidateException(e.Exception);
184 /// Overridden from base
186 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")]
187 protected override void OnInit(EventArgs e) {
190 // Don't do anything in Design mode
194 IDynamicDataSource dataSource = DynamicDataSource;
195 if (dataSource != null) {
196 // Register for datasource exception so that we're called if an error occurs
197 // during an update/insert/delete
198 dataSource.Exception += new EventHandler<DynamicValidatorEventArgs>(OnException);
203 /// Called when an exception happens. Typically, this sets the ValidationException
205 /// <param name="exception">The exception</param>
206 protected virtual void ValidateException(Exception exception) {
207 if (exception == null) {
211 ValidationException = null;
213 // IDynamicValidatorExceptions are used by LinqDataSource to wrap exceptions caused by problems
214 // with setting model properties (columns), such as exceptions thrown from the OnXYZChanging
216 IDynamicValidatorException e = exception as IDynamicValidatorException;
218 HandleDynamicValidatorException(e);
220 // It's not a column specific exception. e.g. it could be coming from
221 // OnValidate (DLinq), or could be caused by a database error.
222 // We only want to use it if it's a ValidationException, otherwise we
223 // could end up displaying sensitive database errors to the end user
224 if (Column == null && exception is ValidationException) {
225 if (exception.InnerException == null) {
226 ValidationException = exception;
228 ValidationException = exception.InnerException;
234 private void HandleDynamicValidatorException(IDynamicValidatorException e) {
235 if (Column == null) {
236 // IDynamicValidatorException only applies to column exceptions
240 List<string> columnNames = GetValidationColumnNames(Column);
242 foreach (string name in columnNames) {
243 // see if the exception wraps any child exceptions relevant to this column
245 if (e.InnerExceptions.TryGetValue(name, out inner)) {
246 // Stop as soon as we find the first exception.
247 ValidationException = inner;
254 /// Get the names of all the columns that can throw an exception that will affect the setting of the
255 /// value of the given column.
257 private static List<string> GetValidationColumnNames(MetaColumn column) {
258 List<string> columnNames = new List<string>();
259 columnNames.Add(column.Name); // add it first so that it gets checked first
260 var fkColumn = column as MetaForeignKeyColumn;
261 if (fkColumn != null) {
262 columnNames.AddRange(fkColumn.ForeignKeyNames);