namespace System.Web.UI.WebControls { using System.Web.UI.WebControls.Expressions; using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Security.Permissions; using System.Web; using System.Web.Resources; using System.Web.UI; using System.Web.UI.WebControls; using System.Diagnostics.CodeAnalysis; using System.ComponentModel; public abstract class QueryableDataSourceView : DataSourceView, IStateManager { //basic query parameters for any Queryable source private ParameterCollection _whereParameters; private ParameterCollection _orderByParameters; private ParameterCollection _orderGroupsByParameters; private ParameterCollection _selectNewParameters; private ParameterCollection _groupByParameters; // CUD operations private ParameterCollection _deleteParameters; private ParameterCollection _updateParameters; private ParameterCollection _insertParameters; private HttpContext _context; private DataSourceControl _owner; private IDynamicQueryable _queryable; private string _groupBy; private string _orderBy; private string _orderGroupsBy; private string _selectNew; private string _where; private bool _autoGenerateOrderByClause; private bool _autoGenerateWhereClause; private bool _autoPage = true; private bool _autoSort = true; private bool _isTracking; protected static readonly object EventSelected = new object(); protected static readonly object EventSelecting = new object(); private static readonly object EventQueryCreated = new object(); // using Hashtable for original values so that ObjectStateFormatter will serialize it properly in ViewState. private Hashtable _originalValues; protected QueryableDataSourceView(DataSourceControl owner, string viewName, HttpContext context) : this(owner, viewName, context, new DynamicQueryableWrapper()) { _context = context; _owner = owner; } internal QueryableDataSourceView(DataSourceControl owner, string viewName, HttpContext context, IDynamicQueryable queryable) : base(owner, viewName) { _context = context; _queryable = queryable; _owner = owner; } public bool AutoGenerateOrderByClause { get { return _autoGenerateOrderByClause; } set { if (_autoGenerateOrderByClause != value) { _autoGenerateOrderByClause = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public bool AutoGenerateWhereClause { get { return _autoGenerateWhereClause; } set { if (_autoGenerateWhereClause != value) { _autoGenerateWhereClause = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public virtual bool AutoPage { get { return _autoPage; } set { if (_autoPage != value) { _autoPage = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public virtual bool AutoSort { get { return _autoSort; } set { if (_autoSort != value) { _autoSort = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public override bool CanDelete { get { return false; } } public override bool CanInsert { get { return false; } } // When AutoPage is false the user should manually page in the Selecting event. public override bool CanPage { get { return true; } } // When AutoPage is false the user must set the total row count in the Selecting event. public override bool CanRetrieveTotalRowCount { get { return true; } } // When AutoSort is false the user should manually sort in the Selecting event. public override bool CanSort { get { return true; } } public override bool CanUpdate { get { return false; } } public virtual ParameterCollection DeleteParameters { get { if (_deleteParameters == null) { _deleteParameters = new ParameterCollection(); } return _deleteParameters; } } protected abstract Type EntityType { get; } public virtual ParameterCollection GroupByParameters { get { if (_groupByParameters == null) { _groupByParameters = new ParameterCollection(); _groupByParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged); if (_isTracking) { DataSourceHelper.TrackViewState(_groupByParameters); } } return _groupByParameters; } } protected bool IsTrackingViewState { get { return _isTracking; } } public virtual ParameterCollection InsertParameters { get { if (_insertParameters == null) { _insertParameters = new ParameterCollection(); } return _insertParameters; } } public virtual ParameterCollection OrderByParameters { get { if (_orderByParameters == null) { _orderByParameters = new ParameterCollection(); _orderByParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged); if (_isTracking) { DataSourceHelper.TrackViewState(_orderByParameters); } } return _orderByParameters; } } public virtual ParameterCollection OrderGroupsByParameters { get { if (_orderGroupsByParameters == null) { _orderGroupsByParameters = new ParameterCollection(); _orderGroupsByParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged); if (_isTracking) { DataSourceHelper.TrackViewState(_orderGroupsByParameters); } } return _orderGroupsByParameters; } } public virtual string OrderBy { get { return _orderBy ?? String.Empty; } set { if (_orderBy != value) { _orderBy = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public virtual string OrderGroupsBy { get { return _orderGroupsBy ?? String.Empty; } set { if (_orderGroupsBy != value) { _orderGroupsBy = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public virtual string GroupBy { get { return _groupBy ?? String.Empty; } set { if (_groupBy != value) { _groupBy = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public virtual ParameterCollection SelectNewParameters { get { if (_selectNewParameters == null) { _selectNewParameters = new ParameterCollection(); _selectNewParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged); if (_isTracking) { DataSourceHelper.TrackViewState(_selectNewParameters); } } return _selectNewParameters; } } [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "SelectNew refers to a projection and 'New' is not used as a suffix in this case")] public virtual string SelectNew { get { return _selectNew ?? String.Empty; } set { if (_selectNew != value) { _selectNew = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public virtual ParameterCollection WhereParameters { get { if (_whereParameters == null) { _whereParameters = new ParameterCollection(); _whereParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged); if (_isTracking) { DataSourceHelper.TrackViewState(_whereParameters); } } return _whereParameters; } } public virtual string Where { get { return _where ?? String.Empty; } set { if (_where != value) { _where = value; OnDataSourceViewChanged(EventArgs.Empty); } } } public virtual ParameterCollection UpdateParameters { get { if (_updateParameters == null) { _updateParameters = new ParameterCollection(); } return _updateParameters; } } [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", Justification = "Derived classes will use this to as the ParametersChanged EventHandler")] protected void OnQueryParametersChanged(object sender, EventArgs e) { RaiseViewChanged(); } [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "An event exists already and it it protected")] public void RaiseViewChanged() { OnDataSourceViewChanged(EventArgs.Empty); } /// /// Gets the result to apply query /// /// protected abstract object GetSource(QueryContext context); protected QueryContext CreateQueryContext(DataSourceSelectArguments arguments) { IDictionary whereParameters = WhereParameters.ToDictionary(_context, _owner); IOrderedDictionary orderByParameters = OrderByParameters.GetValues(_context, _owner).ToCaseInsensitiveDictionary(); IDictionary orderGroupsByParameters = OrderGroupsByParameters.ToDictionary(_context, _owner); IDictionary selectNewParameters = SelectNewParameters.ToDictionary(_context, _owner); IDictionary groupByParameters = GroupByParameters.ToDictionary(_context, _owner); return new QueryContext( whereParameters, orderGroupsByParameters, orderByParameters, groupByParameters, selectNewParameters, arguments); } /// /// Creates a select expression based on parameters and properties /// protected virtual IQueryable BuildQuery(DataSourceSelectArguments arguments) { if (arguments == null) { throw new ArgumentNullException("arguments"); } // Create the query context QueryContext context = CreateQueryContext(arguments); // Clear out old values before selecting new data _originalValues = null; // Get the source of the query(root IQueryable) object result = GetSource(context); if (result != null) { IQueryable source = QueryableDataSourceHelper.AsQueryable(result); // Apply additional filterting return ExecuteQuery(source, context); } return null; } protected virtual IQueryable ExecuteQuery(IQueryable source, QueryContext context) { // Execute Query source = ExecuteQueryExpressions(source, context); // Execute Sorting source = ExecuteSorting(source, context); // Execute Paging source = ExecutePaging(source, context); return source; } protected IQueryable ExecuteQueryExpressions(IQueryable source, QueryContext context) { if (source != null) { QueryCreatedEventArgs queryArgs = new QueryCreatedEventArgs(source); OnQueryCreated(queryArgs); source = queryArgs.Query ?? source; // Support the Dynamic Expression language used by LinqDataSource if (AutoGenerateWhereClause) { if (!String.IsNullOrEmpty(Where)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_WhereAlreadySpecified, _owner.ID)); } source = QueryableDataSourceHelper.CreateWhereExpression(context.WhereParameters, source, _queryable); } else if (!String.IsNullOrEmpty(Where)) { source = _queryable.Where(source, Where, context.WhereParameters.ToEscapedParameterKeys(_owner)); } if (AutoGenerateOrderByClause) { if (!String.IsNullOrEmpty(OrderBy)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_OrderByAlreadySpecified, _owner.ID)); } source = QueryableDataSourceHelper.CreateOrderByExpression(context.OrderByParameters, source, _queryable); } else if (!String.IsNullOrEmpty(OrderBy)) { source = _queryable.OrderBy(source, OrderBy, context.OrderByParameters.ToEscapedParameterKeys(_owner)); } string groupBy = GroupBy; if (String.IsNullOrEmpty(groupBy)) { if (!String.IsNullOrEmpty(OrderGroupsBy)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_OrderGroupsByRequiresGroupBy, _owner.ID)); } } else { source = _queryable.GroupBy(source, groupBy, "it", context.GroupByParameters.ToEscapedParameterKeys(_owner)); if (!String.IsNullOrEmpty(OrderGroupsBy)) { source = _queryable.OrderBy(source, OrderGroupsBy, context.OrderGroupsByParameters.ToEscapedParameterKeys(_owner)); } } if (!String.IsNullOrEmpty(SelectNew)) { source = _queryable.Select(source, SelectNew, context.SelectParameters.ToEscapedParameterKeys(_owner)); } return source; } return source; } protected IQueryable ExecuteSorting(IQueryable source, QueryContext context) { string sortExpression = context.Arguments.SortExpression; if (CanSort && AutoSort && !String.IsNullOrEmpty(sortExpression)) { source = _queryable.OrderBy(source, sortExpression); } return source; } protected IQueryable ExecutePaging(IQueryable source, QueryContext context) { if (CanPage && AutoPage) { if (CanRetrieveTotalRowCount && context.Arguments.RetrieveTotalRowCount) { context.Arguments.TotalRowCount = _queryable.Count(source); } if ((context.Arguments.MaximumRows > 0) && (context.Arguments.StartRowIndex >= 0)) { source = _queryable.Skip(source, context.Arguments.StartRowIndex); source = _queryable.Take(source, context.Arguments.MaximumRows); } } else if (context.Arguments.RetrieveTotalRowCount && (context.Arguments.TotalRowCount == -1)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_PagingNotHandled, _owner.ID)); } return source; } protected virtual void LoadViewState(object savedState) { if (savedState != null) { object[] myState = (object[])savedState; if (myState[0] != null) { ((IStateManager)WhereParameters).LoadViewState(myState[0]); } if (myState[1] != null) { ((IStateManager)OrderByParameters).LoadViewState(myState[1]); } if (myState[2] != null) { ((IStateManager)GroupByParameters).LoadViewState(myState[2]); } if (myState[3] != null) { ((IStateManager)OrderGroupsByParameters).LoadViewState(myState[3]); } if (myState[4] != null) { ((IStateManager)SelectNewParameters).LoadViewState(myState[4]); } if (myState[5] != null) { _originalValues = (Hashtable)myState[5]; } } } protected virtual object SaveViewState() { object[] myState = new object[6]; myState[0] = DataSourceHelper.SaveViewState(_whereParameters); myState[1] = DataSourceHelper.SaveViewState(_orderByParameters); myState[2] = DataSourceHelper.SaveViewState(_groupByParameters); myState[3] = DataSourceHelper.SaveViewState(_orderGroupsByParameters); myState[4] = DataSourceHelper.SaveViewState(_selectNewParameters); if ((_originalValues != null) && (_originalValues.Count > 0)) { myState[5] = _originalValues; } return myState; } protected virtual void TrackViewState() { _isTracking = true; DataSourceHelper.TrackViewState(_whereParameters); DataSourceHelper.TrackViewState(_orderByParameters); DataSourceHelper.TrackViewState(_groupByParameters); DataSourceHelper.TrackViewState(_orderGroupsByParameters); DataSourceHelper.TrackViewState(_selectNewParameters); } protected internal override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) { ClearOriginalValues(); IQueryable source = BuildQuery(arguments); IList results = source.ToList(source.ElementType); // Store original values for concurrency StoreOriginalValues(results); return results; } protected void ClearOriginalValues() { _originalValues = null; } protected virtual IDictionary GetOriginalValues(IDictionary keys) { // Table data is stored in a hashtable with column names for keys and an ArrayList of row data for values. // i.e, Hashtable { ID = ArrayList { 0, 1, 2 }, Name = ArrayList { "A", "B", "C" } } if (_originalValues != null) { // matches list keeps track of row indexes which match. List matches = new List(); foreach (DictionaryEntry entry in keys) { string propertyName = (String)entry.Key; if (_originalValues.ContainsKey(propertyName)) { object propertyValue = entry.Value; // get the row values for the current column. ArrayList values = (ArrayList)_originalValues[propertyName]; for (int i = 0; i < values.Count; i++) { if (matches.Count <= i) { // first column matches.Add(OriginalValueMatches(values[i], propertyValue)); } else if (matches[i] == true) { // subsequent columns matches[i] = OriginalValueMatches(values[i], propertyValue); } } } } int rowIndex = matches.IndexOf(true); // no rows match or too many rows match. if ((rowIndex < 0) || (matches.IndexOf(true, rowIndex + 1) >= 0)) { throw new InvalidOperationException(AtlasWeb.LinqDataSourceView_OriginalValuesNotFound); } // get original values for the matching row. Dictionary rowValues = new Dictionary(_originalValues.Count, StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry entry in _originalValues) { ArrayList value = (ArrayList)entry.Value; rowValues.Add((string)entry.Key, value[rowIndex]); } return rowValues; } return null; } protected virtual void StoreOriginalValues(IList results) { // Derived classes can override this function and call the overload // to determine which columns it should store for optimistic concurrency } protected void StoreOriginalValues(IList results, Func include) { PropertyDescriptorCollection props = TypeDescriptor.GetProperties(EntityType); int numRows = results.Count; int maxColumns = props.Count; _originalValues = new Hashtable(maxColumns, StringComparer.OrdinalIgnoreCase); foreach (PropertyDescriptor p in props) { if (include(p) && p.PropertyType.IsSerializable) { ArrayList values = new ArrayList(numRows); _originalValues[p.Name] = values; foreach (object currentRow in results) { values.Add(p.GetValue(currentRow)); } } } } public int Update(IDictionary keys, IDictionary values, IDictionary oldValues) { return ExecuteUpdate(keys, values, oldValues); } public int Delete(IDictionary keys, IDictionary oldValues) { return ExecuteDelete(keys, oldValues); } public int Insert(IDictionary values) { return ExecuteInsert(values); } protected QueryableDataSourceEditData BuildDeleteObject(IDictionary keys, IDictionary oldValues, IDictionary validationErrors) { QueryableDataSourceEditData editData = new QueryableDataSourceEditData(); Type dataObjectType = EntityType; IDictionary caseInsensitiveOldValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase); IDictionary originalValues = GetOriginalValues(keys); ParameterCollection deleteParameters = DeleteParameters; if (!DataSourceHelper.MergeDictionaries(dataObjectType, deleteParameters, keys, caseInsensitiveOldValues, validationErrors)) { return editData; } if (!DataSourceHelper.MergeDictionaries(dataObjectType, deleteParameters, oldValues, caseInsensitiveOldValues, validationErrors)) { return editData; } if (originalValues != null) { if (!DataSourceHelper.MergeDictionaries(dataObjectType, deleteParameters, originalValues, caseInsensitiveOldValues, validationErrors)) { return editData; } } editData.OriginalDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveOldValues, validationErrors); return editData; } protected QueryableDataSourceEditData BuildInsertObject(IDictionary values, IDictionary validationErrors) { QueryableDataSourceEditData editData = new QueryableDataSourceEditData(); Type dataObjectType = EntityType; IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase); if (!DataSourceHelper.MergeDictionaries(dataObjectType, InsertParameters, InsertParameters.GetValues(_context, _owner), caseInsensitiveNewValues, validationErrors)) { return editData; } if (!DataSourceHelper.MergeDictionaries(dataObjectType, InsertParameters, values, caseInsensitiveNewValues, validationErrors)) { return editData; } editData.NewDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveNewValues, validationErrors); return editData; } protected QueryableDataSourceEditData BuildUpdateObjects(IDictionary keys, IDictionary values, IDictionary oldValues, IDictionary validationErrors) { QueryableDataSourceEditData editData = new QueryableDataSourceEditData(); Type dataObjectType = EntityType; IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase); IDictionary caseInsensitiveOldValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase); IDictionary originalValues = GetOriginalValues(keys); // We start out with the old values, just to pre-populate the list with items // that might not have corresponding new values. For example if a GridView has // a read-only field, there will be an old value, but no new value. The data object // still has to have *some* value for a given field, so we just use the old value. ParameterCollection updateParameters = UpdateParameters; // If we have validation errors bail out while merging bailout if (!DataSourceHelper.MergeDictionaries(dataObjectType, updateParameters, oldValues, caseInsensitiveOldValues, caseInsensitiveNewValues, validationErrors)) { return editData; } if (!DataSourceHelper.MergeDictionaries(dataObjectType, updateParameters, keys, caseInsensitiveOldValues, caseInsensitiveNewValues, validationErrors)) { return editData; } if (originalValues != null) { if (!DataSourceHelper.MergeDictionaries(dataObjectType, updateParameters, originalValues, caseInsensitiveOldValues, caseInsensitiveNewValues, validationErrors)) { return editData; } } if (!DataSourceHelper.MergeDictionaries(dataObjectType, updateParameters, values, caseInsensitiveNewValues, validationErrors)) { return editData; } editData.NewDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveNewValues, validationErrors); if (editData.NewDataObject != null) { editData.OriginalDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveOldValues, validationErrors); } return editData; } protected virtual int DeleteObject(object oldEntity) { return 0; } protected virtual int UpdateObject(object oldEntity, object newEntity) { return 0; } protected virtual int InsertObject(object newEntity) { return 0; } protected abstract void HandleValidationErrors(IDictionary errors, DataSourceOperation operation); protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues) { IDictionary errors = new Dictionary(StringComparer.OrdinalIgnoreCase); QueryableDataSourceEditData editData = BuildDeleteObject(keys, oldValues, errors); if (errors.Any()) { HandleValidationErrors(errors, DataSourceOperation.Delete); } else { return DeleteObject(editData.OriginalDataObject); } return -1; } protected override int ExecuteInsert(IDictionary values) { IDictionary errors = new Dictionary(StringComparer.OrdinalIgnoreCase); QueryableDataSourceEditData editData = BuildInsertObject(values, errors); if (errors.Any()) { HandleValidationErrors(errors, DataSourceOperation.Insert); } else { return InsertObject(editData.NewDataObject); } return -1; } protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) { IDictionary errors = new Dictionary(StringComparer.OrdinalIgnoreCase); QueryableDataSourceEditData editData = BuildUpdateObjects(keys, values, oldValues, errors); if (errors.Any()) { HandleValidationErrors(errors, DataSourceOperation.Update); } else { return UpdateObject(editData.OriginalDataObject, editData.NewDataObject); } return -1; } public event EventHandler QueryCreated { add { Events.AddHandler(EventQueryCreated, value); } remove { Events.RemoveHandler(EventQueryCreated, value); } } [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#")] protected virtual void OnQueryCreated(QueryCreatedEventArgs e) { EventHandler handler = (EventHandler)Events[EventQueryCreated]; if (handler != null) { handler(this, e); } } private bool OriginalValueMatches(object originalValue, object value) { // NOTE: Comparing IEnumerable contents instead of instances to ensure that // timestamp columns (of type byte[]) can be matched appropriately. IEnumerable originalValueEnumerable = originalValue as IEnumerable; IEnumerable valueEnumerable = value as IEnumerable; if ((originalValueEnumerable != null) && (valueEnumerable != null)) { return QueryableDataSourceHelper.EnumerableContentEquals(originalValueEnumerable, valueEnumerable); } return originalValue.Equals(value); } #region IStateManager Members bool IStateManager.IsTrackingViewState { get { return IsTrackingViewState; } } void IStateManager.LoadViewState(object state) { LoadViewState(state); } object IStateManager.SaveViewState() { return SaveViewState(); } void IStateManager.TrackViewState() { TrackViewState(); } #endregion } }