99using System . Data . Objects ;
1010using System . Data . Entity ;
1111using System . Data . Entity . Infrastructure ;
12+ using System . Text . RegularExpressions ;
1213
1314namespace DataTablesParser
1415{
@@ -37,6 +38,7 @@ public class DataTablesParser<T> where T : class
3738 private const string INDIVIDUAL_DATA_KEY_PREFIX = "mDataProp_" ;
3839 private const string INDIVIDUAL_SEARCH_KEY_PREFIX = "sSearch_" ;
3940 private const string INDIVIDUAL_SEARCHABLE_KEY_PREFIX = "bSearchable_" ;
41+ private const string INDIVIDUAL_SORTABLE_KEY_PREFIX = "bSortable_" ;
4042 private const string INDIVIDUAL_SORT_KEY_PREFIX = "iSortCol_" ;
4143 private const string INDIVIDUAL_SORT_DIRECTION_KEY_PREFIX = "sSortDir_" ;
4244 private const string DISPLAY_START = "iDisplayStart" ;
@@ -48,13 +50,51 @@ public class DataTablesParser<T> where T : class
4850 private readonly HttpRequestBase _httpRequest ;
4951 private readonly Type _type ;
5052 private PropertyInfo [ ] _properties ;
53+ private IDictionary < int , PropertyMapping > _propertyMap ;
54+
55+ //TODO: We may be able to handle other numeric property types if they are translatable
56+ private Type [ ] _translatable =
57+ {
58+ typeof ( string ) ,
59+ typeof ( int ) ,
60+ typeof ( Nullable < int > ) ,
61+ typeof ( decimal ) ,
62+ typeof ( Nullable < decimal > ) ,
63+ typeof ( float ) ,
64+ typeof ( Nullable < float > ) ,
65+ typeof ( DateTime ) ,
66+ typeof ( Nullable < DateTime > )
67+
68+ } ;
5169
5270 public DataTablesParser ( HttpRequestBase httpRequest , IQueryable < T > queriable )
5371 {
5472 _queriable = queriable ;
5573 _httpRequest = httpRequest ;
5674 _type = typeof ( T ) ;
5775 _properties = _type . GetProperties ( ) ;
76+
77+ //user regex instead of throwing exception or using tryparse
78+ var integerTest = new Regex ( @"^\d+$" ) ;
79+
80+ //Is this readable? Well if you can read all this expression stuff then this is nothing~!
81+ //This associates class properties with relevant datatable configuration options
82+ _propertyMap = ( from key in _httpRequest . Params . AllKeys . Where ( k => k . StartsWith ( INDIVIDUAL_DATA_KEY_PREFIX ) )
83+ join prop in _properties on _httpRequest [ key ] equals prop . Name
84+ let extractIndex = key . Replace ( INDIVIDUAL_DATA_KEY_PREFIX , string . Empty ) . Trim ( )
85+ let searchable = _httpRequest [ INDIVIDUAL_SEARCHABLE_KEY_PREFIX + extractIndex ] == null ? true : _httpRequest [ INDIVIDUAL_SEARCHABLE_KEY_PREFIX + extractIndex ] . Trim ( ) == "true"
86+ let sortable = _httpRequest [ INDIVIDUAL_SORTABLE_KEY_PREFIX + extractIndex ] == null ? true : _httpRequest [ INDIVIDUAL_SORTABLE_KEY_PREFIX + extractIndex ] . Trim ( ) == "true"
87+ where integerTest . IsMatch ( extractIndex )
88+ select new
89+ {
90+ index = int . Parse ( extractIndex ) ,
91+ map = new PropertyMapping
92+ {
93+ Property = prop ,
94+ Searchable = searchable ,
95+ Sortable = sortable
96+ }
97+ } ) . Distinct ( ) . ToDictionary ( k => k . index , v => v . map ) ;
5898 }
5999
60100 public DataTablesParser ( HttpRequest httpRequest , IQueryable < T > queriable )
@@ -91,22 +131,23 @@ public FormatedList<T> Parse()
91131 //This needs to be an expression or else it won't limit results
92132 Func < T , bool > GenericFind = delegate ( T item )
93133 {
94- bool bFound = false ;
134+ bool found = false ;
95135 var sSearch = _httpRequest [ "sSearch" ] ;
96136
97137 if ( string . IsNullOrWhiteSpace ( sSearch ) )
98138 {
99139 return true ;
100140 }
101141
102- foreach ( PropertyInfo property in _properties )
142+ foreach ( var map in _propertyMap )
103143 {
104- if ( Convert . ToString ( property . GetValue ( item , null ) ) . ToLower ( ) . Contains ( ( sSearch ) . ToLower ( ) ) )
144+
145+ if ( map . Value . Searchable && Convert . ToString ( map . Value . Property . GetValue ( item , null ) ) . ToLower ( ) . Contains ( ( sSearch ) . ToLower ( ) ) )
105146 {
106- bFound = true ;
147+ found = true ;
107148 }
108149 }
109- return bFound ;
150+ return found ;
110151
111152 } ;
112153
@@ -163,65 +204,60 @@ public async Task<FormatedList<T>> ParseAsync()
163204
164205 private void ApplySort ( )
165206 {
166- var thenBy = false ;
207+ var sorted = false ;
167208 var paramExpr = Expression . Parameter ( typeof ( T ) , "val" ) ;
168- // enumerate the keys for any sortations
209+
210+ // Enumerate the keys sort keys in the order we received them
169211 foreach ( string key in _httpRequest . Params . AllKeys . Where ( x => x . StartsWith ( INDIVIDUAL_SORT_KEY_PREFIX ) ) )
170212 {
171213 // column number to sort (same as the array)
172214 int sortcolumn = int . Parse ( _httpRequest [ key ] ) ;
173215
174- // ignore malformatted values
175- if ( sortcolumn < 0 || sortcolumn >= _properties . Length )
176- break ;
216+ // ignore invalid columns
217+ if ( ! _propertyMap . ContainsKey ( sortcolumn ) || ! _propertyMap [ sortcolumn ] . Sortable )
218+ continue ;
177219
178220 // get the direction of the sort
179221 string sortdir = _httpRequest [ INDIVIDUAL_SORT_DIRECTION_KEY_PREFIX + key . Replace ( INDIVIDUAL_SORT_KEY_PREFIX , string . Empty ) ] ;
180222
181- // form the sortation per property via a property expression
182-
183- //var expression = Expression.Convert(Expression.Property(paramExpr, _properties[sortcolumn].Name),typeof(object));
184- var expression1 = Expression . Property ( paramExpr , _properties [ sortcolumn ] . Name ) ;
185- var propType = _properties [ sortcolumn ] . PropertyType ;
186- var delegateType = Expression . GetFuncType ( typeof ( T ) , propType ) ;
187- var propertyExpr = Expression . Lambda ( delegateType , expression1 , paramExpr ) ;
188- //var propertyExpr = Expression.Lambda<Func<T, dynamic>>(Expression.Property(paramExpr, _properties[sortcolumn].Name), paramExpr);
189-
223+
224+ var sortProperty = _propertyMap [ sortcolumn ] . Property ;
225+ var expression1 = Expression . Property ( paramExpr , sortProperty ) ;
226+ var propType = sortProperty . PropertyType ;
227+ var delegateType = Expression . GetFuncType ( typeof ( T ) , propType ) ;
228+ var propertyExpr = Expression . Lambda ( delegateType , expression1 , paramExpr ) ;
190229
191230 // apply the sort (default is ascending if not specified)
192- if ( string . IsNullOrEmpty ( sortdir ) || sortdir . Equals ( ASCENDING_SORT , StringComparison . OrdinalIgnoreCase ) )
193- //_queriable = _queriable.OrderBy<T, dynamic>(propertyExpr);
194-
195- _queriable = typeof ( Queryable ) . GetMethods ( ) . Single (
196- method => method . Name == ( thenBy ? "ThenBy" : "OrderBy" )
197- && method . IsGenericMethodDefinition
198- && method . GetGenericArguments ( ) . Length == 2
199- && method . GetParameters ( ) . Length == 2 )
200- . MakeGenericMethod ( typeof ( T ) , propType )
201- . Invoke ( null , new object [ ] { _queriable , propertyExpr } ) as IOrderedQueryable < T > ;
202-
203- else
204- //_queriable = _queriable.OrderByDescending<T, dynamic>(propertyExpr);
231+ string methodName ;
232+ if ( string . IsNullOrEmpty ( sortdir ) || sortdir . Equals ( ASCENDING_SORT , StringComparison . OrdinalIgnoreCase ) )
233+ {
234+ methodName = sorted ? "ThenBy" : "OrderBy" ;
235+ }
236+ else
237+ {
238+ methodName = sorted ? "ThenByDescending" : "OrderByDescending" ;
239+ }
205240
206241 _queriable = typeof ( Queryable ) . GetMethods ( ) . Single (
207- method => method . Name == ( thenBy ? "ThenByDescending" : "OrderByDescending" )
208- && method . IsGenericMethodDefinition
209- && method . GetGenericArguments ( ) . Length == 2
210- && method . GetParameters ( ) . Length == 2 )
211- . MakeGenericMethod ( typeof ( T ) , propType )
212- . Invoke ( null , new object [ ] { _queriable , propertyExpr } ) as IOrderedQueryable < T > ;
213-
214- thenBy = true ;
242+ method => method . Name == methodName
243+ && method . IsGenericMethodDefinition
244+ && method . GetGenericArguments ( ) . Length == 2
245+ && method . GetParameters ( ) . Length == 2 )
246+ . MakeGenericMethod ( typeof ( T ) , propType )
247+ . Invoke ( null , new object [ ] { _queriable , propertyExpr } ) as IOrderedQueryable < T > ;
248+
249+ sorted = true ;
215250 }
216251
217252 //Linq to entities needs a sort to implement skip
218- if ( ! thenBy && ! ( _queriable is IOrderedQueryable < T > ) )
253+ //Not sure if we care about the queriables that come in sorted? IOrderedQueryable does not seem to be a reliable test
254+ if ( ! sorted )
219255 {
220256 var firstProp = Expression . Property ( paramExpr , _properties [ 0 ] . Name ) ;
221257 var propType = _properties [ 0 ] . PropertyType ;
222258 var delegateType = Expression . GetFuncType ( typeof ( T ) , propType ) ;
223259 var propertyExpr = Expression . Lambda ( delegateType , firstProp , paramExpr ) ;
224-
260+
225261 _queriable = typeof ( Queryable ) . GetMethods ( ) . Single (
226262 method => method . Name == "OrderBy"
227263 && method . IsGenericMethodDefinition
@@ -232,31 +268,6 @@ private void ApplySort()
232268
233269 }
234270
235-
236-
237-
238-
239- }
240-
241-
242-
243- /// <summary>
244- /// Expression that returns a list of string values, which correspond to the values
245- /// of each property in the list type
246- /// </summary>
247- /// <remarks>This implementation does not allow indexers</remarks>
248- private Expression < Func < T , List < string > > > SelectProperties
249- {
250- get
251- {
252- //
253- return value => _properties . Select
254- (
255- // empty string is the default property value
256- prop => ( prop . GetValue ( value , new object [ 0 ] ) ?? string . Empty ) . ToString ( )
257- )
258- . ToList ( ) ;
259- }
260271 }
261272
262273 /// <summary>
@@ -314,62 +325,44 @@ private Expression<Func<T, bool>> ApplyGenericSearch
314325 // invariant expressions
315326 var searchExpression = Expression . Constant ( search . ToLower ( ) ) ;
316327 var paramExpression = Expression . Parameter ( typeof ( T ) , "val" ) ;
317-
328+ var conversionLength = Expression . Constant ( 10 , typeof ( Nullable < int > ) ) ;
329+ var conversionDecimals = Expression . Constant ( 16 , typeof ( Nullable < int > ) ) ;
318330 List < MethodCallExpression > searchProps = new List < MethodCallExpression > ( ) ;
319331
320- var dataNumbers = new Dictionary < int , string > ( ) ;
321-
322- foreach ( string key in _httpRequest . Params . AllKeys . Where ( x => x . StartsWith ( INDIVIDUAL_DATA_KEY_PREFIX ) ) )
332+ foreach ( var propMap in _propertyMap )
323333 {
324- // parse the property number
325- var property = - 1 ;
326-
327- var propertyString = key . Replace ( INDIVIDUAL_DATA_KEY_PREFIX , string . Empty ) ;
334+ var property = propMap . Value . Property ;
328335
329- if ( ( ! int . TryParse ( propertyString , out property ) )
330- || property >= _properties . Length || string . IsNullOrEmpty ( key ) )
331- break ; // ignore if the option is invalid
332-
333- dataNumbers . Add ( property , _httpRequest [ key ] ) ;
334- }
335-
336- foreach ( var prop in _properties )
337- {
338- var searchable = INDIVIDUAL_SEARCHABLE_KEY_PREFIX
339- + dataNumbers . Where ( d => d . Value == prop . Name ) . Select ( d => d . Key ) . FirstOrDefault ( ) ;
336+ if ( ! property . CanWrite || ! propMap . Value . Searchable || ! _translatable . Any ( t => t == property . PropertyType ) )
337+ continue ;
340338
341- bool isSearchable ;
339+ Expression stringProp = null ; //The result must be a TSQL translatable string expression
342340
343- bool . TryParse ( _httpRequest [ searchable ] , out isSearchable ) ;
341+ var propExp = Expression . Property ( paramExpression , property ) ;
344342
345- if ( ! prop . CanWrite || ! isSearchable ) { continue ; }
346-
347- Expression stringProp = null ;
348-
349- var propExp = Expression . Property ( paramExpression , prop ) ;
350-
351- if ( prop . PropertyType == typeof ( double ) || prop . PropertyType == typeof ( int ) )
343+ //TODO: find some genius way to categorize numeric properties including their nullable<> variants
344+ if ( new Type [ ] { typeof ( int ) , typeof ( Nullable < int > ) , typeof ( double ) , typeof ( Nullable < double > ) , typeof ( float ) , typeof ( Nullable < float > ) } . Contains ( property . PropertyType ) )
352345 {
353346 var toDoubleCall = Expression . Convert ( propExp , typeof ( Nullable < double > ) ) ;
354347
355- var doubleConvert = typeof ( SqlFunctions ) . GetMethod ( "StringConvert" , new Type [ ] { typeof ( Nullable < double > ) } ) ;
348+ var doubleConvert = typeof ( SqlFunctions ) . GetMethod ( "StringConvert" , new Type [ ] { typeof ( Nullable < double > ) , typeof ( Nullable < int > ) , typeof ( Nullable < int > ) } ) ;
356349
357- stringProp = Expression . Call ( doubleConvert , toDoubleCall ) ;
350+ stringProp = Expression . Call ( doubleConvert , toDoubleCall , conversionLength , conversionDecimals ) ;
358351
359352 }
360353
361-
362- if ( prop . PropertyType == typeof ( decimal ) )
354+ if ( property . PropertyType == typeof ( decimal ) || property . PropertyType == typeof ( Nullable < decimal > ) )
363355 {
364- var toDecimalCall = Expression . Convert ( propExp , typeof ( Nullable < decimal > ) ) ;
356+ var toDoubleCall = Expression . Convert ( propExp , typeof ( Nullable < decimal > ) ) ;
365357
366- var decimalConvert = typeof ( SqlFunctions ) . GetMethod ( "StringConvert" , new Type [ ] { typeof ( Nullable < decimal > ) } ) ;
358+ var doubleConvert = typeof ( SqlFunctions ) . GetMethod ( "StringConvert" , new Type [ ] { typeof ( Nullable < decimal > ) , typeof ( Nullable < int > ) , typeof ( Nullable < int > ) } ) ;
367359
368- stringProp = Expression . Call ( decimalConvert , toDecimalCall ) ;
369- }
360+ stringProp = Expression . Call ( doubleConvert , toDoubleCall , conversionLength , conversionDecimals ) ;
370361
362+ }
371363
372- if ( prop . PropertyType == typeof ( DateTime ) || prop . PropertyType == typeof ( Nullable < DateTime > ) )
364+ //TODO: Either remove this or provide a way to customize
365+ else if ( property . PropertyType == typeof ( DateTime ) || property . PropertyType == typeof ( Nullable < DateTime > ) )
373366 {
374367
375368 var date = Expression . Convert ( propExp , typeof ( Nullable < DateTime > ) ) ;
@@ -395,7 +388,7 @@ private Expression<Func<T, bool>> ApplyGenericSearch
395388
396389 }
397390
398- if ( prop . PropertyType == typeof ( string ) )
391+ else if ( property . PropertyType == typeof ( string ) )
399392 {
400393 stringProp = propExp ;
401394 }
@@ -420,6 +413,13 @@ private Expression<Func<T, bool>> ApplyGenericSearch
420413 return Expression . Lambda < Func < T , bool > > ( compoundExpression , paramExpression ) ;
421414 }
422415 }
416+
417+ private class PropertyMapping
418+ {
419+ public PropertyInfo Property { get ; set ; }
420+ public bool Sortable { get ; set ; }
421+ public bool Searchable { get ; set ; }
422+ }
423423 }
424424
425425 public class FormatedList < T >
0 commit comments