|
//------------------------------------------------------------------------------
// <copyright file="ModelDataSourceView.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.UI.WebControls {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Compilation;
using System.Web.ModelBinding;
using System.Web.UI;
using System.Web.Util;
/// <summary>
/// Represents a single view of a ModelDataSource.
/// </summary>
public class ModelDataSourceView : DataSourceView, IStateManager {
// Having the immediate caller of MethodInfo.Invoke be a dynamic method gives us two security advantages:
// - It forces the callee to be a public method on a public type.
// - It forces a CAS transparency check on the callee.
private delegate object MethodInvokerDispatcher(MethodInfo methodInfo, object instance, object[] args);
private static readonly MethodInvokerDispatcher _methodInvokerDispatcher = ((Expression<MethodInvokerDispatcher>)((methodInfo, instance, args) => methodInfo.Invoke(instance, args))).Compile();
private ModelDataSource _owner;
private MethodParametersDictionary _selectParameters;
private bool _tracking;
private string _modelTypeName;
private string _deleteMethod;
private string _insertMethod;
private string _selectMethod;
private string _updateMethod;
private string _dataKeyName;
private Task _viewOperationTask = null;
private const string TotalRowCountParameterName = "totalRowCount";
private const string MaximumRowsParameterName = "maximumRows";
private const string StartRowIndexParameterName = "startRowIndex";
private const string SortParameterName = "sortByExpression";
private static readonly object EventCallingDataMethods = new object();
/// <summary>
/// Creates a new ModelDataSourceView.
/// </summary>
public ModelDataSourceView(ModelDataSource owner)
: base(owner, ModelDataSource.DefaultViewName) {
if (owner == null) {
throw new ArgumentNullException("owner");
}
_owner = owner;
if (owner.DataControl.Page != null) {
owner.DataControl.Page.LoadComplete += OnPageLoadComplete;
}
}
/// <summary>
/// Indicates that the view can delete rows.
/// </summary>
public override bool CanDelete {
get {
return (DeleteMethod.Length != 0);
}
}
/// <summary>
/// Indicates that the view can add new rows.
/// </summary>
public override bool CanInsert {
get {
return (InsertMethod.Length != 0);
}
}
/// <summary>
/// Indicates that the view can do server paging.
/// We allow paging by default.
/// </summary>
public override bool CanPage {
get {
return true;
}
}
/// <summary>
/// Indicates that the view can sort rows.
/// We allow sorting by default.
/// </summary>
public override bool CanSort {
get {
return true;
}
}
/// <summary>
/// We allow retrieving total row count by default.
/// </summary>
public override bool CanRetrieveTotalRowCount {
get {
return true;
}
}
/// <summary>
/// Indicates that the view can update rows.
/// </summary>
public override bool CanUpdate {
get {
return (UpdateMethod.Length != 0);
}
}
//All the property setters below are internal for unit tests.
/// <summary>
/// The Data Type Name for the Data Bound Control.
/// </summary>
public string ModelTypeName {
get {
return _modelTypeName ?? String.Empty;
}
internal set {
if (_modelTypeName != value) {
_modelTypeName = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <summary>
/// Name of the method to execute when Delete() is called.
/// </summary>
public string DeleteMethod {
get {
return _deleteMethod ?? String.Empty;
}
internal set {
_deleteMethod = value;
}
}
/// <summary>
/// Name of the method to execute when Insert() is called.
/// </summary>
public string InsertMethod {
get {
return _insertMethod ?? String.Empty;
}
internal set {
_insertMethod = value;
}
}
/// <summary>
/// Name of the method to execute when Select() is called.
/// </summary>
public string SelectMethod {
get {
return _selectMethod ?? String.Empty;
}
internal set {
if (_selectMethod != value) {
_selectMethod = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <summary>
/// Name of the method to execute when Update() is called.
/// </summary>
public string UpdateMethod {
get {
return _updateMethod ?? String.Empty;
}
internal set {
_updateMethod = value;
}
}
/// <summary>
/// First of the DataKeyNames array of the data bound control if applicable (FormView/ListView/GridView/DetailsView) and present.
/// </summary>
public string DataKeyName {
get {
return _dataKeyName ?? String.Empty;
}
internal set {
if (_dataKeyName != value) {
_dataKeyName = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
public event CallingDataMethodsEventHandler CallingDataMethods {
add {
Events.AddHandler(EventCallingDataMethods, value);
}
remove {
Events.RemoveHandler(EventCallingDataMethods, value);
}
}
public void UpdateProperties(string modelTypeName, string selectMethod, string updateMethod, string insertMethod, string deleteMethod, string dataKeyName) {
ModelTypeName = modelTypeName;
SelectMethod = selectMethod;
UpdateMethod = updateMethod;
InsertMethod = insertMethod;
DeleteMethod = deleteMethod;
DataKeyName = dataKeyName;
}
protected virtual void OnCallingDataMethods(CallingDataMethodsEventArgs e) {
CallingDataMethodsEventHandler handler = Events[EventCallingDataMethods] as CallingDataMethodsEventHandler;
if (handler != null) {
handler(_owner.DataControl, e);
}
}
private void OnPageLoadComplete(object sender, EventArgs e) {
EvaluateSelectParameters();
}
private static bool IsAutoPagingRequired(MethodInfo selectMethod, bool isReturningQueryable, bool isAsyncSelect) {
bool maximumRowsFound = false;
bool totalRowCountFound = false;
bool startRowIndexFound = false;
foreach (ParameterInfo parameter in selectMethod.GetParameters()) {
string parameterName = parameter.Name;
if (String.Equals(StartRowIndexParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) {
if (parameter.ParameterType.IsAssignableFrom(typeof(int))) {
startRowIndexFound = true;
}
continue;
}
if (String.Equals(MaximumRowsParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) {
if (parameter.ParameterType.IsAssignableFrom(typeof(int))) {
maximumRowsFound = true;
}
continue;
}
if (String.Equals(TotalRowCountParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) {
if (parameter.IsOut && typeof(int).IsAssignableFrom(parameter.ParameterType.GetElementType())) {
totalRowCountFound = true;
}
continue;
}
}
bool pagingParamsFound;
if (isAsyncSelect) {
pagingParamsFound = maximumRowsFound && startRowIndexFound;
}
else {
pagingParamsFound = maximumRowsFound && startRowIndexFound && totalRowCountFound;
}
bool canDoPaging = isReturningQueryable || pagingParamsFound;
if (!canDoPaging) {
if (isAsyncSelect) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_InvalidAsyncPagingParameters));
}
else {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_InvalidPagingParameters));
}
}
return !pagingParamsFound;
}
private static bool IsAutoSortingRequired(MethodInfo selectMethod, bool isReturningQueryable) {
bool sortExpressionFound = false;
foreach (ParameterInfo parameter in selectMethod.GetParameters()) {
string parameterName = parameter.Name;
if (String.Equals(SortParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) {
if (parameter.ParameterType.IsAssignableFrom(typeof(string))) {
sortExpressionFound = true;
}
}
}
if (!isReturningQueryable && !sortExpressionFound) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_InvalidSortingParameters));
}
return !sortExpressionFound;
}
private object GetPropertyValueByName(object o, string name) {
var propInfo = o.GetType().GetProperty(name);
object value = propInfo.GetValue(o, null);
return value;
}
private void ValidateAsyncModelBindingRequirements() {
if (!(_owner.DataControl.Page.IsAsync && SynchronizationContextUtil.CurrentMode != SynchronizationContextMode.Legacy)) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_UseAsyncMethodMustBeUsingAsyncPage));
}
}
private bool RequireAsyncModelBinding(string methodName, out ModelDataSourceMethod method) {
if (!AppSettings.EnableAsyncModelBinding) {
method = null;
return false;
}
method = FindMethod(methodName);
if (null == method) {
return false;
}
MethodInfo methodInfo = method.MethodInfo;
bool returnTypeIsTask = typeof(Task).IsAssignableFrom(methodInfo.ReturnType);
return returnTypeIsTask;
}
/// <summary>
/// Invokes the select method and gets the result. Also handles auto paging and sorting when required.
/// </summary>
/// <param name="arguments">The DataSourceSelectArguments for the select operation.
/// When applicable, this method sets the TotalRowCount out parameter in the arguments.
/// </param>
/// <returns>The return value from the select method.</returns>
protected virtual object GetSelectMethodResult(DataSourceSelectArguments arguments) {
if (SelectMethod.Length == 0) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_SelectNotSupported));
}
DataSourceSelectResultProcessingOptions options = null;
ModelDataSourceMethod method = EvaluateSelectMethodParameters(arguments, out options);
ModelDataMethodResult result = InvokeMethod(method);
return ProcessSelectMethodResult(arguments, options, result);
}
/// <summary>
/// Evaluates the select method parameters and also determines the options for processing select result like auto paging and sorting behavior.
/// </summary>
/// <param name="arguments">The DataSourceSelectArguments for the select operation.</param>
/// <param name="selectResultProcessingOptions">The <see cref="System.Web.UI.WebControls.DataSourceSelectResultProcessingOptions"/> to use
/// for processing the select result once select operation is complete. These options are determined in this method and later used
/// by the method <see cref="System.Web.UI.WebControls.ModelDataSourceView.ProcessSelectMethodResult"/>.
/// </param>
/// <returns>A <see cref="System.Web.UI.WebControls.ModelDataSourceMethod"/> with the information required to invoke the select method.</returns>
protected virtual ModelDataSourceMethod EvaluateSelectMethodParameters(DataSourceSelectArguments arguments, out DataSourceSelectResultProcessingOptions selectResultProcessingOptions) {
return EvaluateSelectMethodParameters(arguments, null/*method*/, false /*isAsyncSelect*/, out selectResultProcessingOptions);
}
private ModelDataSourceMethod EvaluateSelectMethodParameters(DataSourceSelectArguments arguments, ModelDataSourceMethod method, bool isAsyncSelect, out DataSourceSelectResultProcessingOptions selectResultProcessingOptions) {
IOrderedDictionary mergedParameters = MergeSelectParameters(arguments);
// Resolve the method
method = method ?? FindMethod(SelectMethod);
Type selectMethodReturnType = method.MethodInfo.ReturnType;
if (isAsyncSelect) {
selectMethodReturnType = ExtractAsyncSelectReturnType(selectMethodReturnType);
}
Type modelType = ModelType;
if (modelType == null) {
//When ModelType is not specified but SelectMethod returns IQueryable<T>, we treat T as model type for auto paging and sorting.
//If the return type is something like CustomType<U,T> : IQueryable<T>, we should use T for paging and sorting, hence
//we walk over the return type's generic arguments for a proper match.
foreach (Type typeParameter in selectMethodReturnType.GetGenericArguments()) {
if (typeof(IQueryable<>).MakeGenericType(typeParameter).IsAssignableFrom(selectMethodReturnType)) {
modelType = typeParameter;
}
}
}
Type queryableModelType = (modelType != null) ? typeof(IQueryable<>).MakeGenericType(modelType) : null;
//We do auto paging or auto sorting when the select method is returning an IQueryable and does not have parameters for paging or sorting.
bool isReturningQueryable = queryableModelType != null && queryableModelType.IsAssignableFrom(selectMethodReturnType);
if (isAsyncSelect && isReturningQueryable) {
// async select method does not support returning IQueryable<>.
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_InvalidAsyncSelectReturnType, modelType));
}
bool autoPage = false;
bool autoSort = false;
if (arguments.StartRowIndex >= 0 && arguments.MaximumRows > 0) {
autoPage = IsAutoPagingRequired(method.MethodInfo, isReturningQueryable, isAsyncSelect);
if (isAsyncSelect) {
Debug.Assert(!autoPage, "auto-paging should not be true when using async select method");
// custom paging is not supported if the return type is not SelectResult
if (typeof(SelectResult) != selectMethodReturnType) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_MustUseSelectResultAsReturnType));
}
}
}
if (!String.IsNullOrEmpty(arguments.SortExpression)) {
autoSort = IsAutoSortingRequired(method.MethodInfo, isReturningQueryable);
}
selectResultProcessingOptions = new DataSourceSelectResultProcessingOptions() { ModelType = modelType, AutoPage = autoPage, AutoSort = autoSort };
EvaluateMethodParameters(DataSourceOperation.Select, method, mergedParameters);
return method;
}
private Type ExtractAsyncSelectReturnType(Type t) {
// Async select return type is expected to be Task<T>.
// This method is trying to return type T.
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Task<>)) {
Type[] typeArguments = t.GetGenericArguments();
if (typeArguments.Length == 1) {
return typeArguments[0];
}
}
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_InvalidAsyncSelectReturnType, ModelType));
}
/// <summary>
/// This method performs operations on the select method result like auto paging and sorting if applicable.
/// </summary>
/// <param name="arguments">The DataSourceSelectArguments for the select operation.</param>
/// <param name="selectResultProcessingOptions">The <see cref="System.Web.UI.WebControls.DataSourceSelectResultProcessingOptions"/> to use for processing the select result.
/// These options are determined in an earlier call to <see cref="System.Web.UI.WebControls.ModelDataSourceView.EvaluateSelectMethodParameters"/>.
/// </param>
/// <param name="result">The result after operations like auto paging/sorting are done.</param>
/// <returns></returns>
protected virtual object ProcessSelectMethodResult(DataSourceSelectArguments arguments, DataSourceSelectResultProcessingOptions selectResultProcessingOptions, ModelDataMethodResult result) {
// If the return value is null, there is no more processing to be done
if (result.ReturnValue == null) {
return null;
}
bool autoPage = selectResultProcessingOptions.AutoPage;
bool autoSort = selectResultProcessingOptions.AutoSort;
Type modelType = selectResultProcessingOptions.ModelType;
string sortExpression = arguments.SortExpression;
if (autoPage) {
MethodInfo countHelperMethod = typeof(QueryableHelpers).GetMethod("CountHelper").MakeGenericMethod(modelType);
arguments.TotalRowCount = (int)countHelperMethod.Invoke(null, new object[] { result.ReturnValue });
//Bug 180907: We would like to auto sort on DataKeyName when paging is enabled and result is not already sorted by user to overcome a limitation in EF.
MethodInfo isOrderingMethodFoundMethod = typeof(QueryableHelpers).GetMethod("IsOrderingMethodFound").MakeGenericMethod(modelType);
bool isOrderingMethodFound = (bool)isOrderingMethodFoundMethod.Invoke(null, new object[] { result.ReturnValue });
if (!isOrderingMethodFound) {
if (String.IsNullOrEmpty(sortExpression) && !String.IsNullOrEmpty(DataKeyName)) {
autoSort = true;
selectResultProcessingOptions.AutoSort = true;
sortExpression = DataKeyName;
}
}
}
else if (arguments.StartRowIndex >= 0 && arguments.MaximumRows > 0) {
//When paging is handled by developer, we need to set the TotalRowCount parameter from the select method out parameter.
arguments.TotalRowCount = (int)result.OutputParameters[TotalRowCountParameterName];
}
if (autoPage || autoSort) {
MethodInfo sortPageHelperMethod = typeof(QueryableHelpers).GetMethod("SortandPageHelper").MakeGenericMethod(modelType);
object returnValue = sortPageHelperMethod.Invoke(null, new object[] { result.ReturnValue,
autoPage ? (int?)arguments.StartRowIndex : null,
autoPage ? (int?)arguments.MaximumRows : null,
autoSort ? sortExpression : null });
return returnValue;
}
return result.ReturnValue;
}
private static IOrderedDictionary MergeSelectParameters(DataSourceSelectArguments arguments) {
bool shouldPage = arguments.StartRowIndex >= 0 && arguments.MaximumRows > 0;
bool shouldSort = !String.IsNullOrEmpty(arguments.SortExpression);
// Copy the parameters into a case insensitive dictionary
IOrderedDictionary mergedParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
// Add the sort expression as a parameter if necessary
if (shouldSort) {
mergedParameters[SortParameterName] = arguments.SortExpression;
}
// Add the paging arguments as parameters if necessary
if (shouldPage) {
// Create a new dictionary with the paging information and merge it in (so we get type conversions)
IDictionary pagingParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
pagingParameters[MaximumRowsParameterName] = arguments.MaximumRows;
pagingParameters[StartRowIndexParameterName] = arguments.StartRowIndex;
pagingParameters[TotalRowCountParameterName] = 0;
MergeDictionaries(pagingParameters, mergedParameters);
}
return mergedParameters;
}
/// <summary>
/// Returns the incoming result after wrapping it into IEnumerable if it's not already one.
/// Also ensures that the result is properly typed when ModelTypeName property is set.
/// </summary>
/// <param name="result">The return value of select method.</param>
/// <returns>Returns the value wrapping it into IEnumerable if necessary.
/// If ItemType is set and the return value is not of proper type, throws an InvalidOperationException.
/// </returns>
protected virtual IEnumerable CreateSelectResult(object result) {
return CreateSelectResult(result, false/*isAsyncSelect*/);
}
private IEnumerable CreateSelectResult(object result, bool isAsyncSelect) {
if (result == null) {
return null;
}
Type modelType = ModelType;
//If it is IEnumerable<ModelType> we return as is.
Type enumerableModelType = (modelType != null) ? typeof(IEnumerable<>).MakeGenericType(modelType) : typeof(IEnumerable);
if (enumerableModelType.IsInstanceOfType(result)) {
return (IEnumerable)result;
}
else {
if (modelType == null || modelType.IsInstanceOfType(result)) {
//If it is a <ModelType> we wrap it in an array and return.
return new object[1] { result };
}
else {
//Sorry only the above return types are allowed!!!
if (isAsyncSelect) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_InvalidAsyncSelectReturnType, modelType));
}
else {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_InvalidSelectReturnType, modelType));
}
}
}
}
private static bool IsCancellationRequired(MethodInfo method, out string parameterName) {
parameterName = null;
bool cancellationTokenFound = false;
var lastParameter = method.GetParameters().LastOrDefault();
if (lastParameter != null && lastParameter.ParameterType == typeof(CancellationToken)) {
cancellationTokenFound = true;
parameterName = lastParameter.Name;
}
return cancellationTokenFound;
}
private void SetCancellationTokenIfRequired(ModelDataSourceMethod method, bool isAsyncMethod, CancellationToken? cancellationToken) {
string cancellationTokenParameterName;
if (isAsyncMethod && IsCancellationRequired(method.MethodInfo, out cancellationTokenParameterName)) {
if (null == cancellationToken) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_CancellationTokenIsNotSupported));
}
method.Parameters[cancellationTokenParameterName] = cancellationToken;
}
}
/// <summary>
/// Invokes the Delete method and gets the result.
/// </summary>
protected virtual object GetDeleteMethodResult(IDictionary keys, IDictionary oldValues) {
return GetDeleteMethodResult(keys, oldValues, null/*method*/, false/*isAsyncMethod*/, null/*cancellationToken*/);
}
private object GetDeleteMethodResult(IDictionary keys, IDictionary oldValues, ModelDataSourceMethod method, bool isAsyncMethod, CancellationToken? cancellationToken) {
method = method == null ? EvaluateDeleteMethodParameters(keys, oldValues) : EvaluateDeleteMethodParameters(keys, oldValues, method);
SetCancellationTokenIfRequired(method, isAsyncMethod, cancellationToken);
ModelDataMethodResult result = InvokeMethod(method, isAsyncMethod);
return result.ReturnValue;
}
protected virtual ModelDataSourceMethod EvaluateDeleteMethodParameters(IDictionary keys, IDictionary oldValues) {
return EvaluateDeleteMethodParameters(keys, oldValues, null/*method*/);
}
private ModelDataSourceMethod EvaluateDeleteMethodParameters(IDictionary keys, IDictionary oldValues, ModelDataSourceMethod method) {
if (!CanDelete) {
throw new NotSupportedException(SR.GetString(SR.ModelDataSourceView_DeleteNotSupported));
}
IDictionary caseInsensitiveOldValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
MergeDictionaries(keys, caseInsensitiveOldValues);
MergeDictionaries(oldValues, caseInsensitiveOldValues);
method = method ?? FindMethod(DeleteMethod);
EvaluateMethodParameters(DataSourceOperation.Delete, method, caseInsensitiveOldValues);
return method;
}
/// <summary>
/// Invokes the Insert method and gets the result.
/// </summary>
protected virtual object GetInsertMethodResult(IDictionary values) {
return GetInsertMethodResult(values, null/*method*/, false/*isAsyncMethod&*/, null/*cancellationToken*/);
}
private object GetInsertMethodResult(IDictionary values, ModelDataSourceMethod method, bool isAsyncMethod, CancellationToken? cancellationToken) {
method = method == null ? EvaluateInsertMethodParameters(values) : EvaluateInsertMethodParameters(values, method);
SetCancellationTokenIfRequired(method, isAsyncMethod, cancellationToken);
ModelDataMethodResult result = InvokeMethod(method, isAsyncMethod);
return result.ReturnValue;
}
protected virtual ModelDataSourceMethod EvaluateInsertMethodParameters(IDictionary values) {
return EvaluateInsertMethodParameters(values, null/*method*/);
}
private ModelDataSourceMethod EvaluateInsertMethodParameters(IDictionary values, ModelDataSourceMethod method) {
if (!CanInsert) {
throw new NotSupportedException(SR.GetString(SR.ModelDataSourceView_InsertNotSupported));
}
IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
MergeDictionaries(values, caseInsensitiveNewValues);
method = method ?? FindMethod(InsertMethod);
EvaluateMethodParameters(DataSourceOperation.Insert, method, caseInsensitiveNewValues);
return method;
}
/// <summary>
/// Invokes the Update method and gets the result.
/// </summary>
protected virtual object GetUpdateMethodResult(IDictionary keys, IDictionary values, IDictionary oldValues) {
return GetUpdateMethodResult(keys, values, oldValues, null/*method*/, false/*isAsyncMethod*/, null/*cancellationToken*/);
}
private object GetUpdateMethodResult(IDictionary keys, IDictionary values, IDictionary oldValues, ModelDataSourceMethod method, bool isAsyncMethod, CancellationToken? cancellationToken) {
method = method == null ? EvaluateUpdateMethodParameters(keys, values, oldValues) : EvaluateUpdateMethodParameters(keys, values, oldValues, method);
SetCancellationTokenIfRequired(method, isAsyncMethod, cancellationToken);
ModelDataMethodResult result = InvokeMethod(method, isAsyncMethod);
return result.ReturnValue;
}
protected virtual ModelDataSourceMethod EvaluateUpdateMethodParameters(IDictionary keys, IDictionary values, IDictionary oldValues) {
return EvaluateUpdateMethodParameters(keys, values, oldValues, null/*method*/);
}
private ModelDataSourceMethod EvaluateUpdateMethodParameters(IDictionary keys, IDictionary values, IDictionary oldValues, ModelDataSourceMethod method) {
if (!CanUpdate) {
throw new NotSupportedException(SR.GetString(SR.ModelDataSourceView_UpdateNotSupported));
}
IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
// 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.
MergeDictionaries(oldValues, caseInsensitiveNewValues);
MergeDictionaries(keys, caseInsensitiveNewValues);
MergeDictionaries(values, caseInsensitiveNewValues);
method = method ?? FindMethod(UpdateMethod);
EvaluateMethodParameters(DataSourceOperation.Update, method, caseInsensitiveNewValues);
return method;
}
/// <summary>
/// This method is used by ExecuteInsert/Update/Delete methods to return the result if it's an integer or return a default value.
/// </summary>
/// <param name="result">The return value from one of the above operations.</param>
/// <returns>Returns the result as is if it's integer. Otherwise returns -1.</returns>
private static int GetIntegerReturnValue(object result) {
return (result is int) ? (int)result : -1 ;
}
// We cannot cache this value since users can change SelectMethod by using UpdateProperties
// method.
internal bool IsSelectMethodAsync {
get {
ModelDataSourceMethod method;
return RequireAsyncModelBinding(SelectMethod, out method);
}
}
public override void Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback) {
ModelDataSourceMethod method;
if (RequireAsyncModelBinding(SelectMethod, out method)) {
// We have to remember the method to make sure the method we later would use in the async function
// is the method we validate here in case the method later might be changed by UpdateProperties.
SelectAsync(arguments, callback, method);
}
else {
base.Select(arguments, callback);
}
}
private void SelectAsync(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback, ModelDataSourceMethod method) {
Func<object, Task> func = GetSelectAsyncFunc(arguments, callback, method);
var syncContext = _owner.DataControl.Page.Context.SyncContext as AspNetSynchronizationContext;
if (null == syncContext) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_UseAsyncMethodMustBeUsingAsyncPage));
}
// The first edition of the async model binding feature was implemented by registering async binding
// function as PageAsyncTask. We, however, decided not to do that because postponing data binding
// to page async point changed the order of page events and caused many problems.
// See the comment on SynchronizationHelper.QueueAsynchronousAsync for more details regarding to the PostAsync
// function.
syncContext.PostAsync(func, null);
}
private Func<object, Task> GetSelectAsyncFunc(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback, ModelDataSourceMethod method) {
Func<object, Task> func = async _ => {
ValidateAsyncModelBindingRequirements();
CancellationTokenSource cancellationTokenSource = _owner.DataControl.Page.CreateCancellationTokenFromAsyncTimeout();
CancellationToken cancellationToken = cancellationTokenSource.Token;
DataSourceSelectResultProcessingOptions selectResultProcessingOptions = null;
ModelDataSourceMethod modelMethod = EvaluateSelectMethodParameters(arguments, method, true/*isAsyncSelect*/, out selectResultProcessingOptions);
SetCancellationTokenIfRequired(modelMethod, true/*isAsyncMethod*/, cancellationToken);
ModelDataMethodResult result = InvokeMethod(modelMethod);
IEnumerable finalResult = null;
if (result.ReturnValue != null) {
await (Task)result.ReturnValue;
var returnValue = GetPropertyValueByName(result.ReturnValue, "Result");
if (null == returnValue) {
// do nothing
}
// Users needs to use SelectResult as return type to use
// custom paging.
else if (returnValue is SelectResult) {
var viewOperationTask = _viewOperationTask;
if (viewOperationTask != null) {
await viewOperationTask;
}
var selectResult = (SelectResult)returnValue;
arguments.TotalRowCount = selectResult.TotalRowCount;
finalResult = CreateSelectResult(selectResult.Results, true/*isAsyncSelect*/);
}
else {
// The returnValue does not have to run through ProcessSelectMethodResult() as we
// don't support auto-paging or auto-sorting when using async select.
finalResult = CreateSelectResult(returnValue, true/*isAsyncSelect*/);
}
}
callback(finalResult);
if (cancellationToken.IsCancellationRequested) {
throw new TimeoutException(SR.GetString(SR.Async_task_timed_out));
}
};
return func;
}
public override void Insert(IDictionary values, DataSourceViewOperationCallback callback) {
if (callback == null) {
throw new ArgumentNullException("callback");
}
ModelDataSourceMethod method;
if (RequireAsyncModelBinding(InsertMethod, out method)) {
ViewOperationAsync((cancellationToken) => (Task)GetInsertMethodResult(values, method, true/*isAsyncMethod*/, cancellationToken), callback);
}
else {
base.Insert(values, callback);
}
}
public override void Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback) {
if (callback == null) {
throw new ArgumentNullException("callback");
}
ModelDataSourceMethod method;
if (RequireAsyncModelBinding(UpdateMethod, out method)) {
ViewOperationAsync((cancellationToken) => (Task)GetUpdateMethodResult(keys, values, oldValues, method, true/*isAsyncMethod*/, cancellationToken), callback);
}
else {
base.Update(keys, values, oldValues, callback);
}
}
public override void Delete(IDictionary keys, IDictionary oldValues, DataSourceViewOperationCallback callback) {
if (callback == null) {
throw new ArgumentNullException("callback");
}
ModelDataSourceMethod method;
if (RequireAsyncModelBinding(DeleteMethod, out method)) {
ViewOperationAsync((cancellationToken) => (Task)GetDeleteMethodResult(keys, oldValues, method, true/*isAsyncMethod*/, cancellationToken), callback);
}
else {
base.Delete(keys, oldValues, callback);
}
}
private void ViewOperationAsync(Func<CancellationToken, Task> asyncViewOperation, DataSourceViewOperationCallback callback) {
ValidateAsyncModelBindingRequirements();
Func<object, Task> func = async _ => {
CancellationTokenSource cancellationTokenSource = _owner.DataControl.Page.CreateCancellationTokenFromAsyncTimeout();
CancellationToken cancellationToken = cancellationTokenSource.Token;
var viewOperationTask = _viewOperationTask;
if (viewOperationTask != null) {
await viewOperationTask;
}
var operationTask = asyncViewOperation(cancellationToken);
_viewOperationTask = operationTask;
var operationTaskInt = operationTask as Task<int>;
var operationThrew = false;
var affectedRecords = -1;
try {
if (null != operationTask) {
await operationTask;
if (operationTaskInt != null) {
affectedRecords = operationTaskInt.Result;
}
}
}
catch (Exception ex) {
operationThrew = true;
if (!callback(affectedRecords, ex)) {
// Nobody handled the operation error so re-throw
throw;
}
}
finally {
_viewOperationTask = null;
// Data Method is done executing, turn off the TryUpdateModel
_owner.DataControl.Page.SetActiveValueProvider(null);
if (!operationThrew) {
if (_owner.DataControl.Page.ModelState.IsValid) {
OnDataSourceViewChanged(EventArgs.Empty);
}
// Success
callback(affectedRecords, null);
}
}
if (cancellationToken.IsCancellationRequested) {
throw new TimeoutException(SR.GetString(SR.Async_task_timed_out));
}
};
var syncContext = _owner.DataControl.Page.Context.SyncContext as AspNetSynchronizationContext;
if (null == syncContext) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_UseAsyncMethodMustBeUsingAsyncPage));
}
syncContext.PostAsync(func, null);
}
protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues) {
object result = GetDeleteMethodResult(keys, oldValues);
OnDataSourceViewChanged(EventArgs.Empty);
return GetIntegerReturnValue(result);
}
protected override int ExecuteInsert(IDictionary values) {
object result = GetInsertMethodResult(values);
Debug.Assert(_owner.DataControl.Page != null);
//We do not want to databind when ModelState is invaild so that user entered values are not cleared.
if (_owner.DataControl.Page.ModelState.IsValid) {
OnDataSourceViewChanged(EventArgs.Empty);
}
return GetIntegerReturnValue(result);
}
protected internal override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) {
object result = GetSelectMethodResult(arguments);
return CreateSelectResult(result);
}
protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) {
object result = GetUpdateMethodResult(keys, values, oldValues);
Debug.Assert(_owner.DataControl.Page != null);
//We do not want to databind when ModelState is invaild so that user entered values are not cleared.
if (_owner.DataControl.Page.ModelState.IsValid) {
OnDataSourceViewChanged(EventArgs.Empty);
}
return GetIntegerReturnValue(result);
}
//For unit testing.
internal IEnumerable Select(DataSourceSelectArguments arguments) {
return ExecuteSelect(arguments);
}
//For unit testing.
internal int Update(IDictionary keys, IDictionary values, IDictionary oldValues) {
return ExecuteUpdate(keys, values, oldValues);
}
// For unit testing.
// Return the select async func that we use in the SelectAsync method.
internal Func<object, Task> SelectAsyncInternal(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback, ModelDataSourceMethod method) {
return GetSelectAsyncFunc(arguments, callback, method);
}
//Evaluates the select method parameters using the custom value provides. This is done after page load so that
//we raise the DataSourceChanged event if the values of parameters change.
private void EvaluateSelectParameters() {
if (!String.IsNullOrEmpty(SelectMethod)) {
ModelDataSourceMethod method = FindMethod(SelectMethod);
EvaluateMethodParameters(DataSourceOperation.Select, method, controlValues: null, isPageLoadComplete: true);
}
}
/// <summary>
/// Evaluates the method parameters using model binding.
/// </summary>
/// <param name="dataSourceOperation">The datasource operation for which parameters are being evaluated.</param>
/// <param name="modelDataSourceMethod">The ModelDataSourceMethod object for which the Parameter collection is being evaluated. The MethodInfo property should already be set on this object.</param>
/// <param name="controlValues">The values from the data bound control.</param>
protected virtual void EvaluateMethodParameters(DataSourceOperation dataSourceOperation, ModelDataSourceMethod modelDataSourceMethod, IDictionary controlValues) {
EvaluateMethodParameters(dataSourceOperation, modelDataSourceMethod, controlValues, isPageLoadComplete: false);
}
/// <summary>
/// Evaluates the method parameters using model binding.
/// </summary>
/// <param name="dataSourceOperation">The datasource operation for which parameters are being evaluated.</param>
/// <param name="modelDataSourceMethod">The ModelDataSourceMethod object for which the Parameter collection is being evaluated. The MethodInfo property should already be set on this object.</param>
/// <param name="controlValues">The values from the data bound control.</param>
/// <param name="isPageLoadComplete">This must be set to true only when this method is called in Page's LoadComplete event handler
/// to evaluate the select method parameters that use custom value providers so that we can identify any changes
/// to those and mark the data-bound control for data binding if necessary.</param>
protected virtual void EvaluateMethodParameters(DataSourceOperation dataSourceOperation, ModelDataSourceMethod modelDataSourceMethod, IDictionary controlValues, bool isPageLoadComplete) {
Debug.Assert(_owner.DataControl.Page != null);
Debug.Assert(_owner.DataControl.TemplateControl != null);
MethodInfo actionMethod = modelDataSourceMethod.MethodInfo;
IModelBinder binder = ModelBinders.Binders.DefaultBinder;
IValueProvider dataBoundControlValueProvider = GetValueProviderFromDictionary(controlValues);
ModelBindingExecutionContext modelBindingExecutionContext = _owner.DataControl.Page.ModelBindingExecutionContext;
Control previousDataControl = null;
if (BinaryCompatibility.Current.TargetsAtLeastFramework46) {
// DevDiv 1087698: a child control overwrites its parent controls's modelBindingExecutionContext,
// which may cause a problem for the parent control to find control by controlId.
Control dataControl = modelBindingExecutionContext.TryGetService<Control>();
if (dataControl != _owner.DataControl) {
previousDataControl = dataControl;
}
}
//This is used by ControlValueProvider later.
modelBindingExecutionContext.PublishService<Control>(_owner.DataControl);
//This is done for the TryUpdateModel to work inside a Data Method.
if (dataSourceOperation != DataSourceOperation.Select) {
_owner.DataControl.Page.SetActiveValueProvider(dataBoundControlValueProvider);
}
var methodParameters = actionMethod.GetParameters();
ParameterInfo lastParameter = null;
if (methodParameters.Length > 0) {
lastParameter = methodParameters[methodParameters.Length - 1];
}
foreach (ParameterInfo parameterInfo in methodParameters) {
object value = null;
string modelName = parameterInfo.Name;
if (parameterInfo.ParameterType == typeof(ModelMethodContext)) {
//ModelMethodContext is a special parameter we pass in for enabling developer to call
//TryUpdateModel when Select/Update/Delete/InsertMethods are on a custom class.
value = new ModelMethodContext(_owner.DataControl.Page);
}
//Do not attempt model binding the out parameters
else if (!parameterInfo.IsOut) {
bool validateRequest;
IValueProvider customValueProvider = GetCustomValueProvider(modelBindingExecutionContext, parameterInfo, ref modelName, out validateRequest);
//When we are evaluating the parameter at the time of page load, we do not want to populate the actual ModelState
//because there will be another evaluation at data-binding causing duplicate errors if model validation fails.
ModelStateDictionary modelState = isPageLoadComplete ? new ModelStateDictionary() : _owner.DataControl.Page.ModelState;
ModelBindingContext bindingContext = new ModelBindingContext() {
ModelBinderProviders = ModelBinderProviders.Providers,
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterInfo.ParameterType),
ModelState = modelState,
ModelName = modelName,
ValueProvider = customValueProvider,
ValidateRequest = validateRequest
};
//Select parameters that take custom values providers are tracked by ViewState so that
//we can detect any changes from previous page request and mark the data bound control for data binding if necessary.
if (dataSourceOperation == DataSourceOperation.Select && customValueProvider != null && parameterInfo.ParameterType.IsSerializable) {
if (!SelectParameters.ContainsKey(parameterInfo.Name)) {
SelectParameters.Add(parameterInfo.Name, new MethodParameterValue());
}
if (binder.BindModel(modelBindingExecutionContext, bindingContext)) {
value = bindingContext.Model;
}
SelectParameters[parameterInfo.Name].UpdateValue(value);
}
else {
if (isPageLoadComplete) {
Debug.Assert(dataSourceOperation == DataSourceOperation.Select, "Only Select Operation should have been done immediately after page load");
//When this method is called as part of Page's LoadComplete event handler we do not have values in defaultValueProvider
//(i.e., values from DataBoundControl), so we need not evaluate the parameters values.
continue;
}
if (customValueProvider == null) {
bindingContext.ValueProvider = dataBoundControlValueProvider;
}
if (binder.BindModel(modelBindingExecutionContext, bindingContext)) {
value = bindingContext.Model;
}
}
// We set the CancellationToken after EvaluateMethodParameters().
// We don't want to set a null value to a CancellationToken variable.
if (parameterInfo == lastParameter && typeof(CancellationToken) == parameterInfo.ParameterType && value == null) {
value = CancellationToken.None;
}
if (!isPageLoadComplete) {
ValidateParameterValue(parameterInfo, value, actionMethod);
}
}
modelDataSourceMethod.Parameters.Add(parameterInfo.Name, value);
}
if (previousDataControl != null) {
modelBindingExecutionContext.PublishService<Control>(previousDataControl);
}
}
private static IValueProvider GetValueProviderFromDictionary(IDictionary controlValues) {
Dictionary<string, object> genericDictionary = new Dictionary<string, object>();
if (controlValues != null) {
foreach (DictionaryEntry entry in controlValues) {
Debug.Assert(entry.Key is string, "Some key value is not string");
genericDictionary.Add((string)entry.Key, entry.Value);
}
}
return new DictionaryValueProvider<object>(genericDictionary, CultureInfo.CurrentCulture);
}
private IValueProvider GetCustomValueProvider(ModelBindingExecutionContext modelBindingExecutionContext, ParameterInfo parameterInfo, ref string modelName, out bool validateRequest) {
validateRequest = true;
object[] valueProviderAttributes = parameterInfo.GetCustomAttributes(typeof(IValueProviderSource), false);
if (valueProviderAttributes.Count() > 1) {
throw new NotSupportedException(SR.GetString(SR.ModelDataSourceView_MultipleValueProvidersNotSupported, parameterInfo.Name));
}
if (valueProviderAttributes.Count() > 0) {
IValueProviderSource valueProviderAttribute = (IValueProviderSource)valueProviderAttributes[0];
if (valueProviderAttribute is IModelNameProvider) {
string name = ((IModelNameProvider)valueProviderAttribute).GetModelName();
if (!String.IsNullOrEmpty(name)) {
modelName = name;
}
}
if (valueProviderAttribute is IUnvalidatedValueProviderSource) {
validateRequest = ((IUnvalidatedValueProviderSource)valueProviderAttribute).ValidateInput;
}
return valueProviderAttribute.GetValueProvider(modelBindingExecutionContext);
}
return null;
}
/// <summary>
/// Finds the method to be executed. Raises the CallingDataMethods event to see if developer opted in for custom model method look-up instead of page/usercontrol code behind.
/// Uses the TemplateControl type as a fallback.
/// </summary>
/// <param name="methodName">Name of the data method.</param>
/// <returns>
/// A ModelDataSourceMethod with the Instance and MethodInfo set.
/// The Parameters collection on ModelDataSourceMethod is still empty after this method.
/// </returns>
protected virtual ModelDataSourceMethod FindMethod(string methodName) {
CallingDataMethodsEventArgs e = new CallingDataMethodsEventArgs();
OnCallingDataMethods(e);
Type type;
BindingFlags flags;
object instance;
if (e.DataMethodsType != null) {
if (e.DataMethodsObject != null) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_MultipleModelMethodSources, methodName));
}
flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
instance = null;
type = e.DataMethodsType;
}
else if (e.DataMethodsObject != null) {
flags = BindingFlags.Public | BindingFlags.Instance;
instance = e.DataMethodsObject;
type = instance.GetType();
}
else {
//The compiled page code is a child class of code behind class where usually static methods are defined.
//We will not get those methods unless we use FlattenHierarchy.
flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy;
instance = _owner.DataControl.TemplateControl;
type = instance.GetType();
}
MethodInfo[] allMethods = type.GetMethods(flags);
MethodInfo[] actionMethods = Array.FindAll(allMethods, methodInfo => methodInfo.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase));
if (actionMethods.Length != 1) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_DataMethodNotFound, methodName, type));
}
ValidateMethodIsCallable(actionMethods[0]);
return new ModelDataSourceMethod(instance: instance, methodInfo: actionMethods[0]);
}
private void ValidateMethodIsCallable(MethodInfo methodInfo) {
// we can't call methods with open generic type parameters
if (methodInfo.ContainsGenericParameters) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_CannotCallOpenGenericMethods, methodInfo, methodInfo.ReflectedType.FullName));
}
// we can't call methods with ref parameters
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
foreach (ParameterInfo parameterInfo in parameterInfos) {
if (parameterInfo.ParameterType.IsByRef && !parameterInfo.Name.Equals(TotalRowCountParameterName, StringComparison.OrdinalIgnoreCase)) {
throw new InvalidOperationException(SR.GetString(SR.ModelDataSourceView_CannotCallMethodsWithOutOrRefParameters,
methodInfo, methodInfo.ReflectedType.FullName, parameterInfo));
}
}
}
/// <summary>
/// Extracts the values of all output (out and ref) parameters given a list of parameters and their respective values.
/// </summary>
private OrderedDictionary GetOutputParameters(ParameterInfo[] parameters, object[] values) {
OrderedDictionary outputParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < parameters.Length; i++) {
ParameterInfo parameter = parameters[i];
if (parameter.ParameterType.IsByRef) {
outputParameters[parameter.Name] = values[i];
}
}
return outputParameters;
}
/// <summary>
/// Invokes the data method in a secure fashion.
/// </summary>
/// <param name="method">
/// The ModelDataSouceMethod object specifying the Instance on which the method should be invoked (null for static methods),
/// the MethodInfo of the method to be invoked and the Parameters for invoking the method.
/// All the above properties should be populated before this method is called.
/// </param>
/// <returns>
/// A ModelDataSouceResult object containing the ReturnValue of the method and any out parameters.
/// </returns>
protected virtual ModelDataMethodResult InvokeMethod(ModelDataSourceMethod method) {
return InvokeMethod(method, false/*isAsyncMethod*/);
}
private ModelDataMethodResult InvokeMethod(ModelDataSourceMethod method, bool isAsyncMethod) {
object returnValue = null;
object[] parameterValues = null;
if (method.Parameters != null && method.Parameters.Count > 0) {
parameterValues = new object[method.Parameters.Count];
for (int i = 0; i < method.Parameters.Count; i++) {
parameterValues[i] = method.Parameters[i];
}
}
returnValue = _methodInvokerDispatcher(method.MethodInfo, method.Instance, parameterValues);
OrderedDictionary outputParameters = GetOutputParameters(method.MethodInfo.GetParameters(), parameterValues);
method.Instance = null;
// Data Method is done executing, turn off the TryUpdateModel
// Do not turn off the TryUpdateModel at this point when the method is async
if (!isAsyncMethod) {
_owner.DataControl.Page.SetActiveValueProvider(null);
}
return new ModelDataMethodResult(returnValue, outputParameters);
}
protected virtual bool IsTrackingViewState() {
return _tracking;
}
protected virtual void LoadViewState(object savedState) {
if (savedState != null) {
((IStateManager)SelectParameters).LoadViewState(savedState);
}
}
protected virtual object SaveViewState() {
return _selectParameters != null ? ((IStateManager)_selectParameters).SaveViewState() : null;
}
protected virtual void TrackViewState() {
_tracking = true;
if (_selectParameters != null) {
((IStateManager)_selectParameters).TrackViewState();
}
}
private void ValidateParameterValue(ParameterInfo parameterInfo, object value, MethodInfo methodInfo) {
if (value == null && !TypeHelpers.TypeAllowsNullValue(parameterInfo.ParameterType)) {
// tried to pass a null value for a non-nullable parameter type
string message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.ModelDataSourceView_ParameterCannotBeNull),
parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
throw new InvalidOperationException(message);
}
if (value != null && !parameterInfo.ParameterType.IsInstanceOfType(value)) {
// value was supplied but is not of the proper type
string message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.ModelDataSourceView_ParameterValueHasWrongType),
parameterInfo.Name, methodInfo, methodInfo.DeclaringType, value.GetType(), parameterInfo.ParameterType);
throw new InvalidOperationException(message);
}
}
/// <summary>
/// Merges new values in the source dictionary with old values in the destination dictionary.
/// </summary>
private static void MergeDictionaries(IDictionary source, IDictionary destination) {
Debug.Assert(destination != null);
if (source != null) {
foreach (DictionaryEntry de in source) {
object value = de.Value;
// If the reference collection contains this parameter, we will convert its type to match it
string parameterName = (string)de.Key;
destination[parameterName] = value;
}
}
}
private Type ModelType {
get {
string modelTypeName = ModelTypeName;
if (String.IsNullOrEmpty(modelTypeName)) {
return null;
}
// Load the data object type using BuildManager
return BuildManager.GetType(modelTypeName, true, true);
}
}
private MethodParametersDictionary SelectParameters {
get {
if (_selectParameters == null) {
_selectParameters = new MethodParametersDictionary();
_selectParameters.ParametersChanged += OnSelectParametersChanged;
if (_tracking) {
((IStateManager)_selectParameters).TrackViewState();
}
}
return _selectParameters;
}
}
private void OnSelectParametersChanged(object sender, EventArgs e) {
OnDataSourceViewChanged(EventArgs.Empty);
}
#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
}
}
|