e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
375 lines
14 KiB
C#
375 lines
14 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="DataBinder.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.UI {
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Web.UI.WebControls;
|
|
using System.Web.Util;
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para> Provides design-time support for RAD designers to
|
|
/// generate and parse <see topic='cpconDatabindingExpressionSyntax'/> . This class cannot be inherited.</para>
|
|
/// </devdoc>
|
|
public sealed class DataBinder {
|
|
private static readonly char[] expressionPartSeparator = new char[] { '.' };
|
|
private static readonly char[] indexExprStartChars = new char[] { '[', '(' };
|
|
private static readonly char[] indexExprEndChars = new char[] { ']', ')' };
|
|
private static readonly ConcurrentDictionary<Type, PropertyDescriptorCollection> propertyCache = new ConcurrentDictionary<Type, PropertyDescriptorCollection>();
|
|
// By default the new caching behavior will be on.
|
|
private static bool enableCaching = true;
|
|
|
|
// Global overrride switch to enable old behavior.
|
|
public static bool EnableCaching {
|
|
get {
|
|
return enableCaching;
|
|
}
|
|
set {
|
|
enableCaching = value;
|
|
if (!value) {
|
|
// Clear the cache when caching is turned off.
|
|
propertyCache.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <internalonly/>
|
|
//
|
|
public DataBinder() {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Evaluates data binding expressions at runtime. While
|
|
/// this method is automatically called when you create data bindings in a RAD
|
|
/// designer, you can also use it declaratively if you want to simplify the casting
|
|
/// to a text string to be displayed on a browser. To do so, you must place the
|
|
/// <%# and %> tags, which are also used in standard ASP.NET data binding, around the data binding expression.</para>
|
|
/// <para>This method is particularly useful when data binding against controls that
|
|
/// are in a templated list.</para>
|
|
/// <note type="caution">
|
|
/// Since this method is called at runtime, it can cause performance
|
|
/// to noticeably slow compared to standard ASP.NET databinding syntax.
|
|
/// Use this method judiciously.
|
|
/// </note>
|
|
/// </devdoc>
|
|
public static object Eval(object container, string expression) {
|
|
if (expression == null) {
|
|
throw new ArgumentNullException("expression");
|
|
}
|
|
|
|
expression = expression.Trim();
|
|
|
|
if (expression.Length == 0) {
|
|
throw new ArgumentNullException("expression");
|
|
}
|
|
|
|
if (container == null) {
|
|
return null;
|
|
}
|
|
|
|
string[] expressionParts = expression.Split(expressionPartSeparator);
|
|
|
|
return DataBinder.Eval(container, expressionParts);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
private static object Eval(object container, string[] expressionParts) {
|
|
Debug.Assert((expressionParts != null) && (expressionParts.Length != 0),
|
|
"invalid expressionParts parameter");
|
|
|
|
object prop;
|
|
int i;
|
|
|
|
for (prop = container, i = 0; (i < expressionParts.Length) && (prop != null); i++) {
|
|
string expr = expressionParts[i];
|
|
bool indexedExpr = expr.IndexOfAny(indexExprStartChars) >= 0;
|
|
|
|
if (indexedExpr == false) {
|
|
prop = DataBinder.GetPropertyValue(prop, expr);
|
|
}
|
|
else {
|
|
prop = DataBinder.GetIndexedPropertyValue(prop, expr);
|
|
}
|
|
}
|
|
|
|
return prop;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para> Evaluates data binding expressions at runtime and
|
|
/// formats the output as text to be displayed in the requesting browser. While this
|
|
/// method is automatically called when you create data bindings in a RAD designer,
|
|
/// you can also use it declaratively if you want to simplify the casting to a text
|
|
/// string to be displayed on a browser. To do so, you must place the <%# and %> tags, which are also used in standard ASP.NET data binding, around
|
|
/// the data binding expression.</para>
|
|
/// <para>This method is particularly useful when data binding against controls that
|
|
/// are in a templated list.</para>
|
|
/// <note type="caution">
|
|
/// Since this method is called at
|
|
/// runtime, it can cause performance to noticeably slow compared to standard ASP.NET
|
|
/// databinding syntax. Use this method judiciously, particularly when string
|
|
/// formatting is not required.
|
|
/// </note>
|
|
/// </devdoc>
|
|
public static string Eval(object container, string expression, string format) {
|
|
object value = DataBinder.Eval(container, expression);
|
|
|
|
if ((value == null) || (value == System.DBNull.Value)) {
|
|
return String.Empty;
|
|
}
|
|
else {
|
|
if (String.IsNullOrEmpty(format)) {
|
|
return value.ToString();
|
|
}
|
|
else {
|
|
return String.Format(format, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static PropertyDescriptorCollection GetPropertiesFromCache(object container) {
|
|
// We don't cache if the object implements ICustomTypeDescriptor.
|
|
if (EnableCaching && !(container is ICustomTypeDescriptor)) {
|
|
PropertyDescriptorCollection properties = null;
|
|
Type containerType = container.GetType();
|
|
if (!propertyCache.TryGetValue(containerType, out properties)) {
|
|
properties = TypeDescriptor.GetProperties(containerType);
|
|
propertyCache.TryAdd(containerType, properties);
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
return TypeDescriptor.GetProperties(container);
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
public static object GetPropertyValue(object container, string propName) {
|
|
if (container == null) {
|
|
throw new ArgumentNullException("container");
|
|
}
|
|
if (String.IsNullOrEmpty(propName)) {
|
|
throw new ArgumentNullException("propName");
|
|
}
|
|
|
|
object prop = null;
|
|
|
|
|
|
// get a PropertyDescriptor using case-insensitive lookup
|
|
PropertyDescriptor pd = GetPropertiesFromCache(container).Find(propName, true);
|
|
if (pd != null) {
|
|
prop = pd.GetValue(container);
|
|
}
|
|
else {
|
|
throw new HttpException(SR.GetString(SR.DataBinder_Prop_Not_Found, container.GetType().FullName, propName));
|
|
}
|
|
|
|
return prop;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
public static string GetPropertyValue(object container, string propName, string format) {
|
|
object value = DataBinder.GetPropertyValue(container, propName);
|
|
|
|
if ((value == null) || (value == System.DBNull.Value)) {
|
|
return string.Empty;
|
|
}
|
|
else {
|
|
if (String.IsNullOrEmpty(format)) {
|
|
return value.ToString();
|
|
}
|
|
else {
|
|
return string.Format(format, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
public static object GetIndexedPropertyValue(object container, string expr) {
|
|
if (container == null) {
|
|
throw new ArgumentNullException("container");
|
|
}
|
|
if (String.IsNullOrEmpty(expr)) {
|
|
throw new ArgumentNullException("expr");
|
|
}
|
|
|
|
object prop = null;
|
|
bool intIndex = false;
|
|
|
|
int indexExprStart = expr.IndexOfAny(indexExprStartChars);
|
|
int indexExprEnd = expr.IndexOfAny(indexExprEndChars, indexExprStart + 1);
|
|
|
|
if ((indexExprStart < 0) || (indexExprEnd < 0) ||
|
|
(indexExprEnd == indexExprStart + 1)) {
|
|
throw new ArgumentException(SR.GetString(SR.DataBinder_Invalid_Indexed_Expr, expr));
|
|
}
|
|
|
|
string propName = null;
|
|
object indexValue = null;
|
|
string index = expr.Substring(indexExprStart + 1, indexExprEnd - indexExprStart - 1).Trim();
|
|
|
|
if (indexExprStart != 0)
|
|
propName = expr.Substring(0, indexExprStart);
|
|
|
|
if (index.Length != 0) {
|
|
if (((index[0] == '"') && (index[index.Length - 1] == '"')) ||
|
|
((index[0] == '\'') && (index[index.Length - 1] == '\''))) {
|
|
indexValue = index.Substring(1, index.Length - 2);
|
|
}
|
|
else {
|
|
if (Char.IsDigit(index[0])) {
|
|
// treat it as a number
|
|
int parsedIndex;
|
|
intIndex = Int32.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedIndex);
|
|
if (intIndex) {
|
|
indexValue = parsedIndex;
|
|
}
|
|
else {
|
|
indexValue = index;
|
|
}
|
|
}
|
|
else {
|
|
// treat as a string
|
|
indexValue = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (indexValue == null) {
|
|
throw new ArgumentException(SR.GetString(SR.DataBinder_Invalid_Indexed_Expr, expr));
|
|
}
|
|
|
|
object collectionProp = null;
|
|
if ((propName != null) && (propName.Length != 0)) {
|
|
collectionProp = DataBinder.GetPropertyValue(container, propName);
|
|
}
|
|
else {
|
|
collectionProp = container;
|
|
}
|
|
|
|
if (collectionProp != null) {
|
|
Array arrayProp = collectionProp as Array;
|
|
if (arrayProp != null && intIndex) {
|
|
prop = arrayProp.GetValue((int)indexValue);
|
|
}
|
|
else if ((collectionProp is IList) && intIndex) {
|
|
prop = ((IList)collectionProp)[(int)indexValue];
|
|
}
|
|
else {
|
|
PropertyInfo propInfo =
|
|
collectionProp.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance, null, null, new Type[] { indexValue.GetType() }, null);
|
|
if (propInfo != null) {
|
|
prop = propInfo.GetValue(collectionProp, new object[] { indexValue });
|
|
}
|
|
else {
|
|
throw new ArgumentException(SR.GetString(SR.DataBinder_No_Indexed_Accessor, collectionProp.GetType().FullName));
|
|
}
|
|
}
|
|
}
|
|
|
|
return prop;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
public static string GetIndexedPropertyValue(object container, string propName, string format) {
|
|
object value = DataBinder.GetIndexedPropertyValue(container, propName);
|
|
|
|
if ((value == null) || (value == System.DBNull.Value)) {
|
|
return String.Empty;
|
|
}
|
|
else {
|
|
if (String.IsNullOrEmpty(format)) {
|
|
return value.ToString();
|
|
}
|
|
else {
|
|
return string.Format(format, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
public static object GetDataItem(object container) {
|
|
bool foundDataItem;
|
|
return DataBinder.GetDataItem(container, out foundDataItem);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
public static object GetDataItem(object container, out bool foundDataItem) {
|
|
//
|
|
if (container == null) {
|
|
foundDataItem = false;
|
|
return null;
|
|
}
|
|
|
|
// If object implements IDataItemContainer, get value directly from interface
|
|
IDataItemContainer dataItemContainer = container as IDataItemContainer;
|
|
if (dataItemContainer != null) {
|
|
foundDataItem = true;
|
|
return dataItemContainer.DataItem;
|
|
}
|
|
|
|
// Otherwise, look for a property named "DataItem"
|
|
string dataItemPropertyName = "DataItem";
|
|
|
|
// Check whether the property exists
|
|
PropertyInfo propInfo = container.GetType().GetProperty(dataItemPropertyName,
|
|
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
|
|
|
// If not, return null
|
|
//
|
|
if (propInfo == null) {
|
|
foundDataItem = false;
|
|
return null;
|
|
}
|
|
|
|
// If so, get the value
|
|
foundDataItem = true;
|
|
return propInfo.GetValue(container, null);
|
|
}
|
|
|
|
// Returns true for types that can be automatically databound in controls such as
|
|
// GridView and DetailsView. Bindable types are simple types, such as primitives, strings, enums
|
|
// and nullable primitives.
|
|
public static bool IsBindableType(Type type) {
|
|
//Note that for supporting AutoGenerateEnums property, our default Column Generators for GridView / DetailsView
|
|
//are not calling the DataBinder.IsBindableType but rather calling the below method.
|
|
//So any new types to be considered as Bindable types should be added in the below method rather than here directly.
|
|
return DataBoundControlHelper.IsBindableType(type, enableEnums: true);
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Returns true if the value is null, DBNull, or INullableValue.HasValue is false.
|
|
/// </devdoc>
|
|
internal static bool IsNull(object value) {
|
|
if (value == null || Convert.IsDBNull(value)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|