2015-04-07 09:35:12 +01:00
//------------------------------------------------------------------------------
// <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 ;
2016-02-22 11:00:01 -05:00
using System.Runtime.CompilerServices ;
using System.Threading ;
using System.Threading.Tasks ;
2015-04-07 09:35:12 +01:00
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 ;
2016-02-22 11:00:01 -05:00
private Task _viewOperationTask = null ;
2015-04-07 09:35:12 +01:00
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 ( ) ;
}
2016-02-22 11:00:01 -05:00
private static bool IsAutoPagingRequired ( MethodInfo selectMethod , bool isReturningQueryable , bool isAsyncSelect ) {
2015-04-07 09:35:12 +01:00
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 ;
}
}
2016-02-22 11:00:01 -05:00
bool pagingParamsFound ;
if ( isAsyncSelect ) {
pagingParamsFound = maximumRowsFound & & startRowIndexFound ;
}
else {
pagingParamsFound = maximumRowsFound & & startRowIndexFound & & totalRowCountFound ;
}
2015-04-07 09:35:12 +01:00
2016-02-22 11:00:01 -05:00
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 ) ) ;
}
2015-04-07 09:35:12 +01:00
}
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 ;
}
}
}
2016-02-22 11:00:01 -05:00
if ( ! isReturningQueryable & & ! sortExpressionFound ) {
2015-04-07 09:35:12 +01:00
throw new InvalidOperationException ( SR . GetString ( SR . ModelDataSourceView_InvalidSortingParameters ) ) ;
}
return ! sortExpressionFound ;
2016-02-22 11:00:01 -05:00
}
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 ;
2015-04-07 09:35:12 +01:00
}
/// <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 ) {
2016-02-22 11:00:01 -05:00
return EvaluateSelectMethodParameters ( arguments , null /*method*/ , false /*isAsyncSelect*/ , out selectResultProcessingOptions ) ;
}
private ModelDataSourceMethod EvaluateSelectMethodParameters ( DataSourceSelectArguments arguments , ModelDataSourceMethod method , bool isAsyncSelect , out DataSourceSelectResultProcessingOptions selectResultProcessingOptions ) {
2015-04-07 09:35:12 +01:00
IOrderedDictionary mergedParameters = MergeSelectParameters ( arguments ) ;
// Resolve the method
2016-02-22 11:00:01 -05:00
method = method ? ? FindMethod ( SelectMethod ) ;
2015-04-07 09:35:12 +01:00
Type selectMethodReturnType = method . MethodInfo . ReturnType ;
2016-02-22 11:00:01 -05:00
if ( isAsyncSelect ) {
selectMethodReturnType = ExtractAsyncSelectReturnType ( selectMethodReturnType ) ;
}
2015-04-07 09:35:12 +01:00
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 ) ;
2016-02-22 11:00:01 -05:00
if ( isAsyncSelect & & isReturningQueryable ) {
// async select method does not support returning IQueryable<>.
throw new InvalidOperationException ( SR . GetString ( SR . ModelDataSourceView_InvalidAsyncSelectReturnType , modelType ) ) ;
}
2015-04-07 09:35:12 +01:00
bool autoPage = false ;
bool autoSort = false ;
if ( arguments . StartRowIndex > = 0 & & arguments . MaximumRows > 0 ) {
2016-02-22 11:00:01 -05:00
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 ) ) ;
}
}
2015-04-07 09:35:12 +01:00
}
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 ;
}
2016-02-22 11:00:01 -05:00
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 ) ) ;
}
2015-04-07 09:35:12 +01:00
/// <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 ) {
2016-02-22 11:00:01 -05:00
return CreateSelectResult ( result , false /*isAsyncSelect*/ ) ;
}
private IEnumerable CreateSelectResult ( object result , bool isAsyncSelect ) {
2015-04-07 09:35:12 +01:00
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!!!
2016-02-22 11:00:01 -05:00
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 ) ) ;
2015-04-07 09:35:12 +01:00
}
2016-02-22 11:00:01 -05:00
method . Parameters [ cancellationTokenParameterName ] = cancellationToken ;
2015-04-07 09:35:12 +01:00
}
}
/// <summary>
/// Invokes the Delete method and gets the result.
/// </summary>
protected virtual object GetDeleteMethodResult ( IDictionary keys , IDictionary oldValues ) {
2016-02-22 11:00:01 -05:00
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 ) ;
2015-04-07 09:35:12 +01:00
return result . ReturnValue ;
}
protected virtual ModelDataSourceMethod EvaluateDeleteMethodParameters ( IDictionary keys , IDictionary oldValues ) {
2016-02-22 11:00:01 -05:00
return EvaluateDeleteMethodParameters ( keys , oldValues , null /*method*/ ) ;
}
private ModelDataSourceMethod EvaluateDeleteMethodParameters ( IDictionary keys , IDictionary oldValues , ModelDataSourceMethod method ) {
2015-04-07 09:35:12 +01:00
if ( ! CanDelete ) {
throw new NotSupportedException ( SR . GetString ( SR . ModelDataSourceView_DeleteNotSupported ) ) ;
}
IDictionary caseInsensitiveOldValues = new OrderedDictionary ( StringComparer . OrdinalIgnoreCase ) ;
MergeDictionaries ( keys , caseInsensitiveOldValues ) ;
MergeDictionaries ( oldValues , caseInsensitiveOldValues ) ;
2016-02-22 11:00:01 -05:00
method = method ? ? FindMethod ( DeleteMethod ) ;
2015-04-07 09:35:12 +01:00
EvaluateMethodParameters ( DataSourceOperation . Delete , method , caseInsensitiveOldValues ) ;
return method ;
}
/// <summary>
/// Invokes the Insert method and gets the result.
/// </summary>
protected virtual object GetInsertMethodResult ( IDictionary values ) {
2016-02-22 11:00:01 -05:00
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 ) ;
2015-04-07 09:35:12 +01:00
return result . ReturnValue ;
}
protected virtual ModelDataSourceMethod EvaluateInsertMethodParameters ( IDictionary values ) {
2016-02-22 11:00:01 -05:00
return EvaluateInsertMethodParameters ( values , null /*method*/ ) ;
}
private ModelDataSourceMethod EvaluateInsertMethodParameters ( IDictionary values , ModelDataSourceMethod method ) {
2015-04-07 09:35:12 +01:00
if ( ! CanInsert ) {
throw new NotSupportedException ( SR . GetString ( SR . ModelDataSourceView_InsertNotSupported ) ) ;
}
IDictionary caseInsensitiveNewValues = new OrderedDictionary ( StringComparer . OrdinalIgnoreCase ) ;
MergeDictionaries ( values , caseInsensitiveNewValues ) ;
2016-02-22 11:00:01 -05:00
method = method ? ? FindMethod ( InsertMethod ) ;
2015-04-07 09:35:12 +01:00
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 ) {
2016-02-22 11:00:01 -05:00
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 ) ;
2015-04-07 09:35:12 +01:00
return result . ReturnValue ;
}
protected virtual ModelDataSourceMethod EvaluateUpdateMethodParameters ( IDictionary keys , IDictionary values , IDictionary oldValues ) {
2016-02-22 11:00:01 -05:00
return EvaluateUpdateMethodParameters ( keys , values , oldValues , null /*method*/ ) ;
}
private ModelDataSourceMethod EvaluateUpdateMethodParameters ( IDictionary keys , IDictionary values , IDictionary oldValues , ModelDataSourceMethod method ) {
2015-04-07 09:35:12 +01:00
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 ) ;
2016-02-22 11:00:01 -05:00
method = method ? ? FindMethod ( UpdateMethod ) ;
2015-04-07 09:35:12 +01:00
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 ;
}
2016-02-22 11:00:01 -05:00
// 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 ) ;
}
2015-04-07 09:35:12 +01:00
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 ) ;
}
2016-02-22 11:00:01 -05:00
// 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 ) ;
}
2015-04-07 09:35:12 +01:00
//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 ;
2016-02-22 11:00:01 -05:00
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 ;
}
}
2015-04-07 09:35:12 +01:00
//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 ) ;
}
2016-02-22 11:00:01 -05:00
var methodParameters = actionMethod . GetParameters ( ) ;
ParameterInfo lastParameter = null ;
if ( methodParameters . Length > 0 ) {
lastParameter = methodParameters [ methodParameters . Length - 1 ] ;
}
foreach ( ParameterInfo parameterInfo in methodParameters ) {
2015-04-07 09:35:12 +01:00
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 ;
}
}
2016-02-22 11:00:01 -05:00
// 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 ;
}
2015-04-07 09:35:12 +01:00
if ( ! isPageLoadComplete ) {
ValidateParameterValue ( parameterInfo , value , actionMethod ) ;
}
}
modelDataSourceMethod . Parameters . Add ( parameterInfo . Name , value ) ;
}
2016-02-22 11:00:01 -05:00
if ( previousDataControl ! = null ) {
modelBindingExecutionContext . PublishService < Control > ( previousDataControl ) ;
}
2015-04-07 09:35:12 +01:00
}
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 ) {
2016-02-22 11:00:01 -05:00
return InvokeMethod ( method , false /*isAsyncMethod*/ ) ;
}
private ModelDataMethodResult InvokeMethod ( ModelDataSourceMethod method , bool isAsyncMethod ) {
2015-04-07 09:35:12 +01:00
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 ;
2016-02-22 11:00:01 -05:00
// 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 ) ;
}
2015-04-07 09:35:12 +01:00
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
}
}