namespace System.Web.DynamicData { using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.Linq; using System.Web; using System.Web.Resources; using System.Web.UI; using System.Web.UI.WebControls; /// /// Validator that enforces model validation. It can be used either at the field level or the entity level /// [ToolboxBitmap(typeof(DynamicValidator), "DynamicValidator.bmp")] public class DynamicValidator : BaseValidator { private IDynamicDataSource _dataSource; private Exception _exception; private Dictionary _ignoredModelValidationAttributes; /// /// The name of the column to be validated, or null for entity level validation /// [Browsable(false)] [Themeable(false)] public string ColumnName { get { return (Column == null) ? String.Empty : Column.Name; } } private IDynamicDataSource DynamicDataSource { get { if (_dataSource == null) { // get data source for the parent container. _dataSource = this.FindDataSourceControl(); // get the data source for the targeted data bound control. if (_dataSource == null) { Control c = NamingContainer.FindControl(ControlToValidate); if (c == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.DynamicValidator_ControlNotFound, ControlToValidate, ID)); } _dataSource = c.FindDataSourceControl(); } } return _dataSource; } } /// /// The column to be validated, or null for entity level validation /// [Browsable(false)] [Themeable(false)] public MetaColumn Column { get; set; } /// /// The validation exception that occurred, if any /// protected virtual Exception ValidationException { get { return _exception; } set { _exception = value; } } /// /// Overridden from base /// protected override bool ControlPropertiesValid() { bool hasDataSource = DynamicDataSource != null; // We can't call the base when there is no Column, because the control will be something like // a GridView, which doesn't have a validation property and would cause the base to fail. if (String.IsNullOrEmpty(ColumnName)) { return hasDataSource; } return base.ControlPropertiesValid(); } /// /// Overridden from base /// protected override bool EvaluateIsValid() { // Check if there were some model exceptions (e.g. OnProductNameChanging throwing) Exception e = ValidationException; if (e != null) { ErrorMessage = HttpUtility.HtmlEncode(e.Message); return false; } if (Column == null) return true; string controlValue = GetControlValidationValue(ControlToValidate); if (controlValue == null) { // can return null if ControlToValidate is empty, or if the control is not written correctly to return a value // this does not mean that the value was null return true; } if (Column is MetaForeignKeyColumn || Column is MetaChildrenColumn) { // do not perform conversion or validation on relationship columns as controlValue is the serialized form // of a foreign key which would be useless to a validation attribute return true; } // Check if any of our validators want to fail the value controlValue = (string)Column.ConvertEditedValue(controlValue); object value; if (!TryConvertControlValue(controlValue, Column.ColumnType, out value)) { ErrorMessage = HttpUtility.HtmlEncode(DynamicDataResources.DynamicValidator_CannotConvertValue); return false; } return ValueIsValid(value); } internal static bool TryConvertControlValue(string controlValue, Type columnType, out object value) { try { if (controlValue == null) { value = null; } else if (columnType == typeof(string)) { value = controlValue; } else if (controlValue.Length != 0) { value = Misc.ChangeType(controlValue, columnType); } else { value = null; } return true; } catch (Exception) { value = null; return false; } } private bool ValueIsValid(object value) { // Go through all the model validation attribute to make sure they're valid foreach (var attrib in Column.Attributes.Cast().OfType()) { // Ignore it if it's found in the ignore list if (_ignoredModelValidationAttributes != null && _ignoredModelValidationAttributes.ContainsKey(attrib.GetType())) { continue; } //DynamicValidator can not pass in a ValidationContext as it does //not have an easy way to get the data object row. Hence we will //not support attributes that require Validation Context (Ex : CompareAttribute). if (attrib.RequiresValidationContext) { continue; } if (!attrib.IsValid(value)) { ErrorMessage = HttpUtility.HtmlEncode(attrib.FormatErrorMessage(Column.DisplayName)); return false; } } return true; } internal void SetIgnoredModelValidationAttributes(Dictionary ignoredModelValidationAttributes) { _ignoredModelValidationAttributes = ignoredModelValidationAttributes; } private void OnException(object sender, DynamicValidatorEventArgs e) { ValidateException(e.Exception); } /// /// Overridden from base /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")] protected override void OnInit(EventArgs e) { base.OnInit(e); // Don't do anything in Design mode if (DesignMode) return; IDynamicDataSource dataSource = DynamicDataSource; if (dataSource != null) { // Register for datasource exception so that we're called if an error occurs // during an update/insert/delete dataSource.Exception += new EventHandler(OnException); } } /// /// Called when an exception happens. Typically, this sets the ValidationException /// /// The exception protected virtual void ValidateException(Exception exception) { if (exception == null) { return; } ValidationException = null; // IDynamicValidatorExceptions are used by LinqDataSource to wrap exceptions caused by problems // with setting model properties (columns), such as exceptions thrown from the OnXYZChanging // methods IDynamicValidatorException e = exception as IDynamicValidatorException; if (e != null) { HandleDynamicValidatorException(e); } else { // It's not a column specific exception. e.g. it could be coming from // OnValidate (DLinq), or could be caused by a database error. // We only want to use it if it's a ValidationException, otherwise we // could end up displaying sensitive database errors to the end user if (Column == null && exception is ValidationException) { if (exception.InnerException == null) { ValidationException = exception; } else { ValidationException = exception.InnerException; } } } } private void HandleDynamicValidatorException(IDynamicValidatorException e) { if (Column == null) { // IDynamicValidatorException only applies to column exceptions return; } List columnNames = GetValidationColumnNames(Column); foreach (string name in columnNames) { // see if the exception wraps any child exceptions relevant to this column Exception inner; if (e.InnerExceptions.TryGetValue(name, out inner)) { // Stop as soon as we find the first exception. ValidationException = inner; return; } } } /// /// Get the names of all the columns that can throw an exception that will affect the setting of the /// value of the given column. /// private static List GetValidationColumnNames(MetaColumn column) { List columnNames = new List(); columnNames.Add(column.Name); // add it first so that it gets checked first var fkColumn = column as MetaForeignKeyColumn; if (fkColumn != null) { columnNames.AddRange(fkColumn.ForeignKeyNames); } return columnNames; } } }