//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.UI.WebControls { using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Globalization; /// /// Represents a parameter to a DataSourceControl. /// Parameters can be session variables, web request parameters, or of custom types. /// [ DefaultProperty("DefaultValue"), ] public class Parameter : ICloneable, IStateManager { private ParameterCollection _owner; private bool _tracking; private StateBag _viewState; /// /// Creates an instance of the Parameter class. /// public Parameter() { } /// /// Creates an instance of the Parameter class with the specified parameter name. /// public Parameter(string name) { Name = name; } /// /// Creates an instance of the Parameter class with the specified parameter name and db type. /// public Parameter(string name, DbType dbType) { Name = name; DbType = dbType; } /// /// Creates an instance of the Parameter class with the specified parameter name, db type, and default value. /// public Parameter(string name, DbType dbType, string defaultValue) { Name = name; DbType = dbType; DefaultValue = defaultValue; } /// /// Creates an instance of the Parameter class with the specified parameter name and type. /// public Parameter(string name, TypeCode type) { Name = name; Type = type; } /// /// Creates an instance of the Parameter class with the specified parameter name, type, and default value. /// public Parameter(string name, TypeCode type, string defaultValue) { Name = name; Type = type; DefaultValue = defaultValue; } /// /// Used to clone a parameter. /// protected Parameter(Parameter original) { DefaultValue = original.DefaultValue; Direction = original.Direction; Name = original.Name; ConvertEmptyStringToNull = original.ConvertEmptyStringToNull; Size = original.Size; Type = original.Type; DbType = original.DbType; } /// /// Indicates whether the Parameter is tracking view state. /// protected bool IsTrackingViewState { get { return _tracking; } } /// /// Gets/sets the db type of the parameter's value. /// When DbType is DbType.Object, the Type property will be used instead /// [ DefaultValue(DbType.Object), WebCategory("Parameter"), WebSysDescription(SR.Parameter_DbType), ] public DbType DbType { get { object o = ViewState["DbType"]; if (o == null) return DbType.Object; return (DbType)o; } set { if (value < DbType.AnsiString || value > DbType.DateTimeOffset) { throw new ArgumentOutOfRangeException("value"); } if (DbType != value) { ViewState["DbType"] = value; OnParameterChanged(); } } } /// /// The default value to use in GetValue() if it cannot obtain a value. /// [ DefaultValue(null), WebCategory("Parameter"), WebSysDescription(SR.Parameter_DefaultValue), ] public string DefaultValue { get { object o = ViewState["DefaultValue"]; return (o as string); } set { if (DefaultValue != value) { ViewState["DefaultValue"] = value; OnParameterChanged(); } } } /// /// Gets/sets the direction of the parameter. /// [ DefaultValue(ParameterDirection.Input), WebCategory("Parameter"), WebSysDescription(SR.Parameter_Direction), ] public ParameterDirection Direction { get { object o = ViewState["Direction"]; if (o == null) return ParameterDirection.Input; return (ParameterDirection)o; } set { if (Direction != value) { ViewState["Direction"] = value; OnParameterChanged(); } } } /// /// Gets/sets the name of the parameter. /// [ DefaultValue(""), WebCategory("Parameter"), WebSysDescription(SR.Parameter_Name), ] public string Name { get { object o = ViewState["Name"]; if (o == null) return String.Empty; return (string)o; } set { if (Name != value) { ViewState["Name"] = value; OnParameterChanged(); } } } /// /// Returns the value of parameter after converting it to the proper type. /// [ Browsable(false), ] internal object ParameterValue { get { return GetValue(ViewState["ParameterValue"], false); } } public DbType GetDatabaseType() { DbType dbType = DbType; if (dbType == DbType.Object) { return ConvertTypeCodeToDbType(Type); } if (Type != TypeCode.Empty) { throw new InvalidOperationException(SR.GetString(SR.Parameter_TypeNotSupported, Name)); } return dbType; } internal object GetValue(object value, bool ignoreNullableTypeChanges) { DbType dbType = DbType; if (dbType == DbType.Object) { return GetValue(value, DefaultValue, Type, ConvertEmptyStringToNull, ignoreNullableTypeChanges); } if (Type != TypeCode.Empty) { throw new InvalidOperationException(SR.GetString(SR.Parameter_TypeNotSupported, Name)); } return GetValue(value, DefaultValue, dbType, ConvertEmptyStringToNull, ignoreNullableTypeChanges); } internal static object GetValue(object value, string defaultValue, DbType dbType, bool convertEmptyStringToNull, bool ignoreNullableTypeChanges) { // use the TypeCode conversion logic for Whidbey types. if ((dbType != DbType.DateTimeOffset) && (dbType != DbType.Time) && (dbType != DbType.Guid)) { TypeCode type = ConvertDbTypeToTypeCode(dbType); return GetValue(value, defaultValue, type, convertEmptyStringToNull, ignoreNullableTypeChanges); } value = HandleNullValue(value, defaultValue, convertEmptyStringToNull); if (value == null) { return null; } // For ObjectDataSource we special-case Nullable and do nothing because these // types will get converted when we actually call the method. if (ignoreNullableTypeChanges && IsNullableType(value.GetType())) { return value; } if (dbType == DbType.DateTimeOffset) { if (value is DateTimeOffset) { return value; } return DateTimeOffset.Parse(value.ToString(), CultureInfo.CurrentCulture); } else if (dbType == DbType.Time) { if (value is TimeSpan) { return value; } return TimeSpan.Parse(value.ToString(), CultureInfo.CurrentCulture); } else if (dbType == DbType.Guid) { if (value is Guid) { return value; } return new Guid(value.ToString()); } Debug.Fail("Should never reach this point."); return null; } internal static object GetValue(object value, string defaultValue, TypeCode type, bool convertEmptyStringToNull, bool ignoreNullableTypeChanges) { // Convert.ChangeType() throws if you attempt to convert to DBNull, so we have to special case it. if (type == TypeCode.DBNull) { return DBNull.Value; } value = HandleNullValue(value, defaultValue, convertEmptyStringToNull); if (value == null) { return null; } if (type == TypeCode.Object || type == TypeCode.Empty) { return value; } // For ObjectDataSource we special-case Nullable and do nothing because these // types will get converted when we actually call the method. if (ignoreNullableTypeChanges && IsNullableType(value.GetType())) { return value; } return value = Convert.ChangeType(value, type, CultureInfo.CurrentCulture);; } private static object HandleNullValue(object value, string defaultValue, bool convertEmptyStringToNull) { // Get the value and convert it to the default value if it is null if (convertEmptyStringToNull) { string stringValue = value as string; if ((stringValue != null) && (stringValue.Length == 0)) { value = null; } } if (value == null) { // Attempt to use the default value, but if it is null too, just return null immediately if (convertEmptyStringToNull && String.IsNullOrEmpty(defaultValue)) { defaultValue = null; } if (defaultValue == null) { return null; } value = defaultValue; } return value; } private static bool IsNullableType(Type type) { return type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>)); } /// /// Gets/sets the size of the parameter. /// [ DefaultValue(0), WebCategory("Parameter"), WebSysDescription(SR.Parameter_Size), ] public int Size { get { object o = ViewState["Size"]; if (o == null) return 0; return (int)o; } set { if (Size != value) { ViewState["Size"] = value; OnParameterChanged(); } } } /// /// Gets/sets the type of the parameter's value. /// [ DefaultValue(TypeCode.Empty), WebCategory("Parameter"), WebSysDescription(SR.Parameter_Type), ] public TypeCode Type { get { object o = ViewState["Type"]; if (o == null) return TypeCode.Empty; return (TypeCode)o; } set { if (value < TypeCode.Empty || value > TypeCode.String) { throw new ArgumentOutOfRangeException("value"); } if (Type != value) { ViewState["Type"] = value; OnParameterChanged(); } } } /// /// Gets/sets whether an empty string should be treated as a null value. If this property is set to true /// and the value is an empty string, the default value will be used. /// [ DefaultValue(true), WebCategory("Parameter"), WebSysDescription(SR.Parameter_ConvertEmptyStringToNull), ] public bool ConvertEmptyStringToNull { get { object o = ViewState["ConvertEmptyStringToNull"]; if (o == null) return true; return (bool)o; } set { if (ConvertEmptyStringToNull != value) { ViewState["ConvertEmptyStringToNull"] = value; OnParameterChanged(); } } } /// /// Indicates a dictionary of state information that allows you to save and restore /// the state of a Parameter across multiple requests for the same page. /// [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ] protected StateBag ViewState { get { if (_viewState == null) { _viewState = new StateBag(); if (_tracking) _viewState.TrackViewState(); } return _viewState; } } /// /// Creates a new Parameter that is a copy of this Parameter. /// protected virtual Parameter Clone() { return new Parameter(this); } public static TypeCode ConvertDbTypeToTypeCode(DbType dbType) { switch (dbType) { case DbType.AnsiString: case DbType.AnsiStringFixedLength: case DbType.String: case DbType.StringFixedLength: return TypeCode.String; case DbType.Boolean: return TypeCode.Boolean; case DbType.Byte: return TypeCode.Byte; case DbType.VarNumeric: // ??? case DbType.Currency: case DbType.Decimal: return TypeCode.Decimal; case DbType.Date: case DbType.DateTime: case DbType.DateTime2: // new Katmai type case DbType.Time: // new Katmai type - no TypeCode for TimeSpan return TypeCode.DateTime; case DbType.Double: return TypeCode.Double; case DbType.Int16: return TypeCode.Int16; case DbType.Int32: return TypeCode.Int32; case DbType.Int64: return TypeCode.Int64; case DbType.SByte: return TypeCode.SByte; case DbType.Single: return TypeCode.Single; case DbType.UInt16: return TypeCode.UInt16; case DbType.UInt32: return TypeCode.UInt32; case DbType.UInt64: return TypeCode.UInt64; case DbType.Guid: // ??? case DbType.Binary: case DbType.Object: case DbType.DateTimeOffset: // new Katmai type - no TypeCode for DateTimeOffset default: return TypeCode.Object; } } public static DbType ConvertTypeCodeToDbType(TypeCode typeCode) { // no TypeCode equivalent for TimeSpan or DateTimeOffset switch (typeCode) { case TypeCode.Boolean: return DbType.Boolean; case TypeCode.Byte: return DbType.Byte; case TypeCode.Char: return DbType.StringFixedLength; // ??? case TypeCode.DateTime: // Used for Date, DateTime and DateTime2 DbTypes return DbType.DateTime; case TypeCode.Decimal: return DbType.Decimal; case TypeCode.Double: return DbType.Double; case TypeCode.Int16: return DbType.Int16; case TypeCode.Int32: return DbType.Int32; case TypeCode.Int64: return DbType.Int64; case TypeCode.SByte: return DbType.SByte; case TypeCode.Single: return DbType.Single; case TypeCode.String: return DbType.String; case TypeCode.UInt16: return DbType.UInt16; case TypeCode.UInt32: return DbType.UInt32; case TypeCode.UInt64: return DbType.UInt64; case TypeCode.DBNull: case TypeCode.Empty: case TypeCode.Object: default: return DbType.Object; } } /// /// Evaluates the parameter and returns the new value. /// The control parameter is used to access the page's framework. /// By default it returns the null, implying that the DefaultValue will /// be the value. /// protected internal virtual object Evaluate(HttpContext context, Control control) { return null; } /// /// Loads view state. /// protected virtual void LoadViewState(object savedState) { if (savedState != null) { ViewState.LoadViewState(savedState); } } /// /// Raises the ParameterChanged event. This notifies a listener that it should re-evaluate the value. /// protected void OnParameterChanged() { if (_owner != null) { _owner.CallOnParametersChanged(); } } /// /// Saves view state. /// protected virtual object SaveViewState() { return (_viewState != null) ? _viewState.SaveViewState() : null; } /// /// Tells the Parameter to record its entire state into view state. /// protected internal virtual void SetDirty() { ViewState.SetDirty(true); } /// /// Tells the Parameter the collection it belongs to /// internal void SetOwner(ParameterCollection owner) { _owner = owner; } /// /// Converts the Parameter to a string value. /// public override string ToString() { return this.Name; } /// /// Tells the Parameter to start tracking property changes. /// protected virtual void TrackViewState() { _tracking = true; if (_viewState != null) { _viewState.TrackViewState(); } } /// /// Updates the value of parameter. /// If the value changed, this will raise the ParametersChanged event of the ParameterCollection it belongs to. /// The control parameter is used to access the page's framework. /// internal void UpdateValue(HttpContext context, Control control) { object oldValue = ViewState["ParameterValue"]; object newValue = Evaluate(context, control); ViewState["ParameterValue"] = newValue; // If you have chains of dependency, like one control with a control parameter on another, and then a third with a control // parameter on the second, the order in which the evaluations take place is non-deterministic and may create incorrect // evaluation of parameters because all our evaluation happens during LoadComplete. The correct solution is to call DataBind // on the third control when the second control's selected value changes. Hacky, but we don't support specifying dependency // chains on data sources. if ((newValue == null && oldValue != null) || (newValue != null && !newValue.Equals(oldValue))) { OnParameterChanged(); } } #region Implementation of ICloneable /// object ICloneable.Clone() { return Clone(); } #endregion #region Implementation of IStateManager /// bool IStateManager.IsTrackingViewState { get { return IsTrackingViewState; } } /// void IStateManager.LoadViewState(object savedState) { LoadViewState(savedState); } /// object IStateManager.SaveViewState() { return SaveViewState(); } /// void IStateManager.TrackViewState() { TrackViewState(); } #endregion } }