using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Web.DynamicData.Util;
using System.Web.Resources;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace System.Web.DynamicData {
///
/// The base class for all field template user controls
///
public class FieldTemplateUserControl : UserControl, IBindableControl, IFieldTemplate {
private static RequiredAttribute s_defaultRequiredAttribute = new RequiredAttribute();
private Dictionary _ignoredModelValidationAttributes;
private object _fieldValue;
private DefaultValueMapping _defaultValueMapping;
private bool _pageDataItemSet;
private object _pageDataItem;
public FieldTemplateUserControl() {
}
internal FieldTemplateUserControl(DefaultValueMapping defaultValueMapping) {
_defaultValueMapping = defaultValueMapping;
}
///
/// The host that provides context to this field template
///
[Browsable(false)]
public IFieldTemplateHost Host { get; private set; }
///
/// The formatting options that need to be applied to this field template
///
[Browsable(false)]
public IFieldFormattingOptions FormattingOptions { get; private set; }
///
/// The MetaColumn that this field template is working with
///
[Browsable(false)]
public MetaColumn Column {
get {
return Host.Column;
}
}
///
/// The ContainerType in which this
///
[Browsable(false)]
public virtual ContainerType ContainerType {
get {
return Misc.FindContainerType(this);
}
}
///
/// The MetaTable that this field's column belongs to
///
[Browsable(false)]
public MetaTable Table {
get {
return Column.Table;
}
}
///
/// Casts the MetaColumn to a MetaForeignKeyColumn. Throws if it is not an FK column.
///
[Browsable(false)]
public MetaForeignKeyColumn ForeignKeyColumn {
get {
var foreignKeyColumn = Column as MetaForeignKeyColumn;
if (foreignKeyColumn == null) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.FieldTemplateUserControl_ColumnIsNotFK, Column.Name));
}
return foreignKeyColumn;
}
}
///
/// Casts the MetaColumn to a MetaChildrenColumn. Throws if it is not an Children column.
///
[Browsable(false)]
public MetaChildrenColumn ChildrenColumn {
get {
var childrenColumn = Column as MetaChildrenColumn;
if (childrenColumn == null) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.FieldTemplateUserControl_ColumnIsNotChildren, Column.Name));
}
return childrenColumn;
}
}
///
/// The mode (readonly, edit, insert) that the field template should use
///
[Browsable(false)]
public DataBoundControlMode Mode {
get {
return Host.Mode;
}
}
///
/// The collection of metadata attributes that apply to this column
///
[Browsable(false)]
public System.ComponentModel.AttributeCollection MetadataAttributes {
get {
return Column.Attributes;
}
}
///
/// Returns the data control that handles the field inside the field template
///
[Browsable(false)]
public virtual Control DataControl {
get {
return null;
}
}
///
/// The current data object. Equivalent to Page.GetDataItem()
///
[Browsable(false)]
public virtual object Row {
get {
// The DataItem is normally null in insert mode, we're going to surface the DictionaryCustomTypeDescriptor if there is a
// a default value was specified for this column.
if (Mode == DataBoundControlMode.Insert && DefaultValueMapping != null && DefaultValueMapping.Contains(Column)) {
return DefaultValueMapping.Instance;
}
// Used for unit testing. We can't use null since thats a valid value.
if (_pageDataItemSet) {
return _pageDataItem;
}
return Page.GetDataItem();
}
internal set {
// Only set in unit tests.
_pageDataItem = value;
_pageDataItemSet = true;
}
}
///
/// The value of the Column in the current Row
///
[Browsable(false)]
public virtual object FieldValue {
get {
// If a field value was explicitly set, use it instead of the usual logic.
if (_fieldValue != null)
return _fieldValue;
return GetColumnValue(Column);
}
set {
_fieldValue = value;
}
}
///
/// Get the value of a specific column in the current row
///
///
///
protected virtual object GetColumnValue(MetaColumn column) {
object row = Row;
if (row != null) {
return DataBinder.GetPropertyValue(row, column.Name);
}
// Fallback on old behavior
if (Mode == DataBoundControlMode.Insert) {
return column.DefaultValue;
}
return null;
}
///
/// Return the field value as a formatted string
///
[Browsable(false)]
public virtual string FieldValueString {
get {
// Get the string and preprocess it
return FormatFieldValue(FieldValue);
}
}
///
/// Similar to FieldValueString, but the string is to be used when the field is in edit mode
///
[Browsable(false)]
public virtual string FieldValueEditString {
get {
return FormattingOptions.FormatEditValue(FieldValue);
}
}
///
/// Only applies to FK columns. Returns a URL that links to the page that displays the details
/// of the foreign key entity. e.g. In the Product table's Category column, this produces a link
/// that goes to the details of the category that the product is in
///
protected string ForeignKeyPath {
get {
return ForeignKeyColumn.GetForeignKeyPath(PageAction.Details, Row);
}
}
internal DefaultValueMapping DefaultValueMapping {
get {
if (_defaultValueMapping == null) {
// Ensure this only gets accessed in insert mode
Debug.Assert(Mode == DataBoundControlMode.Insert);
_defaultValueMapping = MetaTableHelper.GetDefaultValueMapping(this, Context.ToWrapper());
}
return _defaultValueMapping;
}
}
///
/// Same as ForeignKeyPath, except that it allows the path part of the URL to be overriden. This is
/// used when using pages that don't live under DynamicData/CustomPages.
///
/// The path override
///
protected string BuildForeignKeyPath(string path) {
// If a path was passed in, resolved it relative to the containing page
if (!String.IsNullOrEmpty(path)) {
path = ResolveParentRelativePath(path);
}
return ForeignKeyColumn.GetForeignKeyPath(PageAction.Details, Row, path);
}
///
/// Only applies to Children columns. Returns a URL that links to the page that displays the list
/// of children entities. e.g. In the Category table's Products column, this produces a link
/// that goes to the list of Products that are in this Category.
///
protected string ChildrenPath {
get {
return ChildrenColumn.GetChildrenPath(PageAction.List, Row);
}
}
///
/// Same as ChildrenPath, except that it allows the path part of the URL to be overriden. This is
/// used when using pages that don't live under DynamicData/CustomPages.
///
/// The path override
///
protected string BuildChildrenPath(string path) {
// If a path was passed in, resolved it relative to the containing page
if (!String.IsNullOrEmpty(path)) {
path = ResolveParentRelativePath(path);
}
return ChildrenColumn.GetChildrenPath(PageAction.List, Row, path);
}
// Resolve a relative path based on the containing page
private string ResolveParentRelativePath(string path) {
if (path == null || TemplateControl == null)
return path;
Control parentControl = TemplateControl.Parent;
if (parentControl == null)
return path;
return parentControl.ResolveUrl(path);
}
///
/// Return the field template for another column
///
protected FieldTemplateUserControl FindOtherFieldTemplate(string columnName) {
return Parent.FindFieldTemplate(columnName) as FieldTemplateUserControl;
}
///
/// Only applies to FK columns. Populate the list control with all the values from the parent table
///
/// The control to be populated
protected void PopulateListControl(ListControl listControl) {
Type enumType;
if (Column is MetaForeignKeyColumn) {
Misc.FillListItemCollection(ForeignKeyColumn.ParentTable, listControl.Items);
} else if (Column.IsEnumType(out enumType)) {
Debug.Assert(enumType != null);
FillEnumListControl(listControl, enumType);
}
}
private void FillEnumListControl(ListControl list, Type enumType) {
foreach (DictionaryEntry entry in Misc.GetEnumNamesAndValues(enumType)) {
list.Items.Add(new ListItem((string)entry.Key, (string)entry.Value));
}
}
///
/// Gets a string representation of the column's value so that it can be matched with
/// values populated in a dropdown. This currently works for FK and Enum columns only.
/// The method returns null for other column types.
///
///
protected string GetSelectedValueString() {
Type enumType;
if (Column is MetaForeignKeyColumn) {
return ForeignKeyColumn.GetForeignKeyString(Row);
} else if(Column.IsEnumType(out enumType)) {
return Misc.GetUnderlyingTypeValueString(enumType, FieldValue);
}
return null;
}
///
/// Only applies to FK columns. This is used when saving the value of a foreign key, typically selected
/// from a drop down.
///
/// The dictionary that contains all the new values
/// The value to be saved. Typically, this comes from DropDownList.SelectedValue
protected virtual void ExtractForeignKey(IDictionary dictionary, string selectedValue) {
ForeignKeyColumn.ExtractForeignKey(dictionary, selectedValue);
}
///
/// Apply potential HTML encoding and formatting to a string that needs to be displayed
///
/// The value that should be formatted
/// the formatted value
public virtual string FormatFieldValue(object fieldValue) {
return FormattingOptions.FormatValue(fieldValue);
}
///
/// Return either the input value or null based on ConvertEmptyStringToNull and NullDisplayText
///
/// The input value
/// The converted value
protected virtual object ConvertEditedValue(string value) {
return FormattingOptions.ConvertEditedValue(value);
}
///
/// Set up a validator for dynamic data use. It sets the ValidationGroup on all validators,
/// and also performs additional logic for some specific validator types. e.g. for a RangeValidator
/// it sets the range values if they exist on the model.
///
/// The validator to be set up
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly",
Justification = "We really want Set Up as two words")]
protected virtual void SetUpValidator(BaseValidator validator) {
SetUpValidator(validator, Column);
}
///
/// Set up a validator for dynamic data use. It sets the ValidationGroup on all validators,
/// and also performs additional logic for some specific validator types. e.g. for a RangeValidator
/// it sets the range values if they exist on the model.
///
/// The validator to be set up
/// The column for which the validator is getting set
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly",
Justification = "We really want Set Up as two words")]
protected virtual void SetUpValidator(BaseValidator validator, MetaColumn column) {
// Set the validation group to match the dynamic control
validator.ValidationGroup = Host.ValidationGroup;
if (validator is DynamicValidator) {
SetUpDynamicValidator((DynamicValidator)validator, column);
}
else if (validator is RequiredFieldValidator) {
SetUpRequiredFieldValidator((RequiredFieldValidator)validator, column);
}
else if (validator is CompareValidator) {
SetUpCompareValidator((CompareValidator)validator, column);
}
else if (validator is RangeValidator) {
SetUpRangeValidator((RangeValidator)validator, column);
}
else if (validator is RegularExpressionValidator) {
SetUpRegexValidator((RegularExpressionValidator)validator, column);
}
validator.ToolTip = validator.ErrorMessage;
validator.Text = "*";
}
private void SetUpDynamicValidator(DynamicValidator validator, MetaColumn column) {
validator.Column = column;
// Tell the DynamicValidator which validation attributes it should ignore (because
// they're already handled by server side ASP.NET validator controls)
validator.SetIgnoredModelValidationAttributes(_ignoredModelValidationAttributes);
}
private void SetUpRequiredFieldValidator(RequiredFieldValidator validator, MetaColumn column) {
var requiredAttribute = column.Metadata.RequiredAttribute;
if (requiredAttribute!= null && requiredAttribute.AllowEmptyStrings) {
// Dev10 Bug 749744
// If somone explicitly set AllowEmptyStrings = true then we assume that they want to
// allow empty strings to go into a database even if the column is marked as required.
// Since ASP.NET validators always get an empty string, this essential turns of
// required field validation.
IgnoreModelValidationAttribute(typeof(RequiredAttribute));
} else if (column.IsRequired) {
validator.Enabled = true;
// Make sure the attribute doesn't get validated a second time by the DynamicValidator
IgnoreModelValidationAttribute(typeof(RequiredAttribute));
if (String.IsNullOrEmpty(validator.ErrorMessage)) {
string columnErrorMessage = column.RequiredErrorMessage;
if (String.IsNullOrEmpty(columnErrorMessage)) {
// generate default error message
validator.ErrorMessage = HttpUtility.HtmlEncode(s_defaultRequiredAttribute.FormatErrorMessage(column.DisplayName));
} else {
validator.ErrorMessage = HttpUtility.HtmlEncode(columnErrorMessage);
}
}
}
}
private void SetUpCompareValidator(CompareValidator validator, MetaColumn column) {
validator.Operator = ValidationCompareOperator.DataTypeCheck;
ValidationDataType? dataType = null;
string errorMessage = null;
if (column.ColumnType == typeof(DateTime)) {
dataType = ValidationDataType.Date;
errorMessage = String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.FieldTemplateUserControl_CompareValidationError_Date,
column.DisplayName);
} else if (column.IsInteger && column.ColumnType != typeof(long)) {
// long is unsupported because it's larger than int
dataType = ValidationDataType.Integer;
errorMessage = String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.FieldTemplateUserControl_CompareValidationError_Integer,
column.DisplayName);
} else if (column.ColumnType == typeof(decimal)) {
//
dataType = ValidationDataType.Double;
errorMessage = String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.FieldTemplateUserControl_CompareValidationError_Decimal,
column.DisplayName);
} else if (column.IsFloatingPoint) {
dataType = ValidationDataType.Double;
errorMessage = String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.FieldTemplateUserControl_CompareValidationError_Decimal,
column.DisplayName);
}
if (dataType != null) {
Debug.Assert(errorMessage != null);
validator.Enabled = true;
validator.Type = dataType.Value;
if (String.IsNullOrEmpty(validator.ErrorMessage)) {
validator.ErrorMessage = HttpUtility.HtmlEncode(errorMessage);
}
} else {
// If we don't recognize the type, turn off the validator
validator.Enabled = false;
}
}
private void SetUpRangeValidator(RangeValidator validator, MetaColumn column) {
// Nothing to do if no range was specified
var rangeAttribute = column.Attributes.OfType().FirstOrDefault();
if (rangeAttribute == null)
return;
// Make sure the attribute doesn't get validated a second time by the DynamicValidator
IgnoreModelValidationAttribute(rangeAttribute.GetType());
validator.Enabled = true;
Func