1


I have been struggling for days over an old (2011) piece of C# code I extracted from a DLL my boss wants me to edit.

The application queries a database with LINQ, server-side processes it and displays the data with a DataTable. From what I gathered, the guy who wrote it had created an ASP.NET Web Forms Site in Visual Studio 2010, with .NET Framework 4.0.

He used the DataTables plugin along with the server-side parser from Zack Owens. In the project I recreated in VS 2017, everything builds well, but at runtime, a bug comes from the little customizations he made to the DataTable parser: among them, the SelectProperties() function has been completely rewritten from this:

private Expression<Func<T, List<string>>> SelectProperties
{
    get
    {
        return value => _properties.Select
        (
            // empty string is the default property value
            prop => (prop.GetValue(value, new object[0]) ?? string.Empty).ToString()
        )
        .ToList();
    }
}

to this:

private Expression<Func<T, List<string>>> SelectProperties
{
    get
    {
        var parameterExpression = Expression.Parameter(typeof(T), "value");

       // (Edited) The bug happens there: type_RD is null because GetMethod is not valid
        var type_RD = typeof(Enumerable).GetMethod(
            "ToList",
            new Type[] {
                typeof(IEnumerable<string>)
            }
        );

        // The programs crashes there (System.Null.Exception)
        var methodFromHandle = (MethodInfo)MethodBase.GetMethodFromHandle(
            type_RD
        .MethodHandle);

        var expressionArray = new Expression[1];

        var methodInfo = (MethodInfo)MethodBase.GetMethodFromHandle(
            typeof(Enumerable).GetMethod("Select", new Type[] {
                typeof(IEnumerable<PropertyInfo>),
                typeof(Func<PropertyInfo, string>)
            })
        .MethodHandle);

        var expressionArray1 = new Expression[] {
            Expression.Field(
                Expression.Constant(
                    this,
                    typeof(DataTableParser<T>)
                ),
                FieldInfo.GetFieldFromHandle(
                    typeof(DataTableParser<T>).GetField("_properties").FieldHandle,
                    typeof(DataTableParser<T>).TypeHandle
                )
            ), null
        };

        var parameterExpression1 = Expression.Parameter(
            typeof(PropertyInfo),
            "prop"
        );

        var methodFromHandle1 = (MethodInfo)MethodBase.GetMethodFromHandle(
            typeof(PropertyInfo).GetMethod(
                "GetValue",
                new Type[] {
                    typeof(object),
                    typeof(object[])
                }
            )
        .MethodHandle);

        var expressionArray2 = new Expression[] {
            Expression.Convert(
                parameterExpression,
                typeof(object)
            ),
            Expression.NewArrayInit(
                typeof(object),
                new Expression[0]
            )
        };

        var methodCallExpression = Expression.Call(
            Expression.Coalesce(
                Expression.Call(
                    parameterExpression1,
                    methodFromHandle1,
                    expressionArray2
                ),
                Expression.Field(
                    null,
                    FieldInfo.GetFieldFromHandle(
                        typeof(string).GetField("Empty").FieldHandle
                    )
                )
            ),
            (MethodInfo)MethodBase.GetMethodFromHandle(
                typeof(object).GetMethod("ToString").MethodHandle
            ),
            new Expression[0]
        );

        expressionArray1[1] = Expression.Lambda<Func<PropertyInfo, string>>(
            methodCallExpression,
            parameterExpression1
        );
        expressionArray[0] = Expression.Call(
            null,
            methodInfo,
            expressionArray1
        );

        // Return Lambda
        return Expression.Lambda<Func<T, List<string>>>(
            Expression.Call(
                null,
                methodFromHandle,
                expressionArray
            ),
            parameterExpression
        );
    }
}

My questions:

  • How to make this SelectProperties function work?
  • What exactly is its purpose, compared to the original one? I didn't get any of the "MethodHandle" bits ...

The only hint I have is that, when I use the original SelectProperties code, the data are in the wrong columns and the sorting causes Error 500 "Exception of type 'Antlr.Runtime.NoViableAltException' was thrown" with some of the columns. Here is the full code of this custom DataTable.cs parser:
Tip: look for the (Edited) tags

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;

// (Edited) Source : Zack Owens commented https://github.com/garvincasimir/csharp-datatables-parser/issues/2#issuecomment-20441424

/* (Edited) Adapting to custom namespace
namespace DataTables
*/
namespace MyApp.Web.WebServices.Utils
{
    /// <summary>
    /// Parses the request values from a query from the DataTables jQuery plugin
    /// </summary>
    /// <typeparam name="T">List data type</typeparam>
    public class DataTableParser<T>
    {
        /*
         * int: iDisplayStart - Display start point
        * int: iDisplayLength - Number of records to display
        * string: string: sSearch - Global search field
        * boolean: bEscapeRegex - Global search is regex or not
        * int: iColumns - Number of columns being displayed (useful for getting individual column search info)
        * string: sSortable_(int) - Indicator for if a column is flagged as sortable or not on the client-side
        * string: sSearchable_(int) - Indicator for if a column is flagged as searchable or not on the client-side
        * string: sSearch_(int) - Individual column filter
        * boolean: bEscapeRegex_(int) - Individual column filter is regex or not
        * int: iSortingCols - Number of columns to sort on
        * int: iSortCol_(int) - Column being sorted on (you will need to decode this number for your database)
        * string: sSortDir_(int) - Direction to be sorted - "desc" or "asc". Note that the prefix for this variable is wrong in 1.5.x, but left for backward compatibility)
        * string: sEcho - Information for DataTables to use for rendering
         */

        private const string INDIVIDUAL_SEARCH_KEY_PREFIX = "sSearch_";
        private const string INDIVIDUAL_SORT_KEY_PREFIX = "iSortCol_";
        private const string INDIVIDUAL_SORT_DIRECTION_KEY_PREFIX = "sSortDir_";
        private const string DISPLAY_START = "iDisplayStart";
        private const string DISPLAY_LENGTH = "iDisplayLength";
        private const string ECHO = "sEcho";
        private const string ASCENDING_SORT = "asc";
        private const string OBJECT_DATA_PREFIX = "mDataProp_";

        private IQueryable<T> _queriable;
        private readonly HttpRequestBase _httpRequest;
        private readonly Type _type;
        private readonly PropertyInfo[] _properties;

        public DataTableParser(HttpRequestBase httpRequest, IQueryable<T> queriable)
        {
            _queriable = queriable;
            _httpRequest = httpRequest;
            _type = typeof(T);
            _properties = _type.GetProperties();
        }

        public DataTableParser(HttpRequest httpRequest, IQueryable<T> queriable)
            : this(new HttpRequestWrapper(httpRequest), queriable) { }

        /// <summary>
        /// Parses the <see cref="HttpRequestBase"/> parameter values for the accepted 
        /// DataTable request values
        /// </summary>
        /// <returns>Formated output for DataTables, which should be serialized to JSON</returns>
        /// <example>
        ///     In an ASP.NET MVC from a controller, you can call the Json method and return this result.
        ///     
        ///     public ActionResult List()
        ///     {
        ///         // change the following line per your data configuration
        ///         IQueriable<User> users = datastore.Linq();
        ///         
        ///         if (Request["sEcho"] != null) // always test to see if the request is from DataTables
        ///         {
        ///             var parser = new DataTableParser<User>(Request, users);
        ///             return Json(parser.Parse());
        ///         }
        ///         return Json(_itemController.CachedValue);
        ///     }
        ///     
        ///     If you're not using MVC, you can create a web service and write the JSON output as such:
        ///     
        ///     using System.Web.Script.Serialization;
        ///     public class MyWebservice : System.Web.Services.WebService
        ///     {
        ///         public string MyMethod()
        ///         {
        ///             // change the following line per your data configuration
        ///             IQueriable<User> users = datastore.Linq();
        ///             
        ///             response.ContentType = "application/json";
        ///             
        ///             JavaScriptSerializer serializer = new JavaScriptSerializer();
        ///             var parser = new DataTableParser<User>(Request, users);
        ///             return new JavaScriptSerializer().Serialize(parser.Parse());
        ///         }
        ///     }
        /// </example>
        public FormatedList<T> Parse()
        {
            var list = new FormatedList();
            list.Import(_properties.Select(x => x.Name).ToArray());

            list.sEcho = int.Parse(_httpRequest[ECHO]);

            list.iTotalRecords = _queriable.Count();

            ApplySort();

            int skip = 0, take = 10;
            int.TryParse(_httpRequest[DISPLAY_START], out skip);
            int.TryParse(_httpRequest[DISPLAY_LENGTH], out take);

            /* (Edited) This new syntax works well
            list.aaData = _queriable.Where(ApplyGenericSearch)
                                    .Where(IndividualPropertySearch)
                                    .Skip(skip)
                                    .Take(take)
                                    .Select(SelectProperties)
                                    .ToList();

            list.iTotalDisplayRecords = list.aaData.Count;
            */
            list.aaData = _queriable.Where(ApplyGenericSearch)
                                    .Where(IndividualPropertySearch)
                                    .Skip(skip)
                                    .Take(take)
                                    .ToList()
                                    .AsQueryable()
                                    .Select(SelectProperties)
                                    .ToList();

            list.iTotalDisplayRecords = list.iTotalRecords;

            return list;
        }

        private void ApplySort()
        {
            // (Edited) Added one line, see after
            bool firstSort = true;
            foreach (string key in _httpRequest.Params.AllKeys.Where(x => x.StartsWith(INDIVIDUAL_SORT_KEY_PREFIX)))
            {
                int sortcolumn = int.Parse(_httpRequest[key]);
                if (sortcolumn < 0 || sortcolumn >= _properties.Length)
                    break;

                string sortdir = _httpRequest[INDIVIDUAL_SORT_DIRECTION_KEY_PREFIX + key.Replace(INDIVIDUAL_SORT_KEY_PREFIX, string.Empty)];

                var paramExpr = Expression.Parameter(typeof(T), "val");

                /* Edited as per https://stackoverflow.com/a/8974875/5426777 and mentioned here too https://weblogs.asp.net/zowens/jquery-datatables-plugin-meets-c#after-content
                var propertyExpr = Expression.Lambda<Func<T, object>>(Expression.Property(paramExpr, _properties[sortcolumn]), paramExpr);
                */
                var expression = Expression.Convert(Expression.Property(paramExpr, _properties[sortcolumn]),typeof(object));
                var propertyExpr = Expression.Lambda<Func<T, object>>(expression, paramExpr);

                /* Edited cf. https://weblogs.asp.net/zowens/jquery-datatables-plugin-meets-c#after-content
                 * Correcting multi-sort errors
                if (string.IsNullOrEmpty(sortdir) || sortdir.Equals(ASCENDING_SORT, StringComparison.OrdinalIgnoreCase))
                    _queriable = _queriable.OrderBy(propertyExpr);
                else
                    _queriable = _queriable.OrderByDescending(propertyExpr);
                 */
                if (firstSort)
                {
                    if (string.IsNullOrEmpty(sortdir) || sortdir.Equals(ASCENDING_SORT, StringComparison.OrdinalIgnoreCase))
                        _queriable = _queriable.OrderBy(propertyExpr);
                    else
                        _queriable = _queriable.OrderByDescending(propertyExpr);

                    firstSort = false;
                }
                else
                {
                    if (string.IsNullOrEmpty(sortdir) || sortdir.Equals(ASCENDING_SORT, StringComparison.OrdinalIgnoreCase))
                        _queriable = ((IOrderedQueryable<T>)_queriable).ThenBy(propertyExpr);
                    else
                        _queriable = ((IOrderedQueryable<T>)_queriable).ThenByDescending(propertyExpr);
                }
            }
        }

        /// <summary>
        /// Expression that returns a list of string values, which correspond to the values
        /// of each property in the list type
        /// </summary>
        /// <remarks>This implementation does not allow indexers</remarks>
        private Expression<Func<T, List<string>>> SelectProperties
        {
            get
            {
                /* (Edited) This is the edit that does not work and that I don't understand
                return value => _properties.Select
                (
                    // empty string is the default property value
                    prop => (prop.GetValue(value, new object[0]) ?? string.Empty).ToString()
                )
               .ToList();
               */
                var parameterExpression = Expression.Parameter(typeof(T), "value");

               // (Edited) The bug happens there: type_RD is null because GetMethod is not valid
                var type_RD = typeof(Enumerable).GetMethod(
                    "ToList",
                    new Type[] {
                        typeof(IEnumerable<string>)
                    }
                );

                // The programs crashes there (System.Null.Exception)
                var methodFromHandle = (MethodInfo)MethodBase.GetMethodFromHandle(
                    type_RD
                .MethodHandle);

                var expressionArray = new Expression[1];

                var methodInfo = (MethodInfo)MethodBase.GetMethodFromHandle(
                    typeof(Enumerable).GetMethod("Select", new Type[] {
                        typeof(IEnumerable<PropertyInfo>),
                        typeof(Func<PropertyInfo, string>)
                    })
                .MethodHandle);

                var expressionArray1 = new Expression[] {
                    Expression.Field(
                        Expression.Constant(
                            this,
                            typeof(DataTableParser<T>)
                        ),
                        FieldInfo.GetFieldFromHandle(
                            typeof(DataTableParser<T>).GetField("_properties").FieldHandle,
                            typeof(DataTableParser<T>).TypeHandle
                        )
                    ), null
                };

                var parameterExpression1 = Expression.Parameter(
                    typeof(PropertyInfo),
                    "prop"
                );

                var methodFromHandle1 = (MethodInfo)MethodBase.GetMethodFromHandle(
                    typeof(PropertyInfo).GetMethod(
                        "GetValue",
                        new Type[] {
                            typeof(object),
                            typeof(object[])
                        }
                    )
                .MethodHandle);

                var expressionArray2 = new Expression[] {
                    Expression.Convert(
                        parameterExpression,
                        typeof(object)
                    ),
                    Expression.NewArrayInit(
                        typeof(object),
                        new Expression[0]
                    )
                };

                var methodCallExpression = Expression.Call(
                    Expression.Coalesce(
                        Expression.Call(
                            parameterExpression1,
                            methodFromHandle1,
                            expressionArray2
                        ),
                        Expression.Field(
                            null,
                            FieldInfo.GetFieldFromHandle(
                                typeof(string).GetField("Empty").FieldHandle
                            )
                        )
                    ),
                    (MethodInfo)MethodBase.GetMethodFromHandle(
                        typeof(object).GetMethod("ToString").MethodHandle
                    ),
                    new Expression[0]
                );

                expressionArray1[1] = Expression.Lambda<Func<PropertyInfo, string>>(
                    methodCallExpression,
                    parameterExpression1
                );
                expressionArray[0] = Expression.Call(
                    null,
                    methodInfo,
                    expressionArray1
                );

                // Return Lambda
                return Expression.Lambda<Func<T, List<string>>>(
                    Expression.Call(
                        null,
                        methodFromHandle,
                        expressionArray
                    ),
                    parameterExpression
                );
            }
        }

        /// <summary>
        /// Compound predicate expression with the individual search predicates that will filter the results
        /// per an individual column
        /// </summary>
        private Expression<Func<T, bool>> IndividualPropertySearch
        {
            get
            {
                var paramExpr = Expression.Parameter(typeof(T), "val");
                Expression whereExpr = Expression.Constant(true); // default is val => True
                List<Expression> le = new List<Expression>() { whereExpr };
                List<ParameterExpression> lp = new List<ParameterExpression>() { paramExpr };

                foreach (string key in _httpRequest.Params.AllKeys.Where(x => x.StartsWith(INDIVIDUAL_SEARCH_KEY_PREFIX)))
                {
                    var mDataProp = key.Replace(INDIVIDUAL_SEARCH_KEY_PREFIX, OBJECT_DATA_PREFIX);
                    if (string.IsNullOrEmpty(_httpRequest[key]) || string.IsNullOrEmpty(_httpRequest[mDataProp]))
                    {
                        continue; // ignore if the option is invalid
                    }
                    var f = _properties.First(p => p.Name == _httpRequest[mDataProp]);
                    string query = _httpRequest[key].ToLower();

                    MethodCallExpression mce;
                    if (f.PropertyType != typeof(string))
                    {
                        // val.{PropertyName}.ToString().ToLower().Contains({query})
                        mce = Expression.Call(Expression.Call(Expression.Property(paramExpr, f), "ToString", new Type[0]), typeof(string).GetMethod("ToLower", new Type[0]));
                    }
                    else
                    {
                        mce = Expression.Call(Expression.Property(paramExpr, f), typeof(string).GetMethod("ToLower", new Type[0]));
                    }

                    // reset where expression to also require the current constraint
                    whereExpr = Expression.And(whereExpr, Expression.Call(mce, typeof(string).GetMethod("Contains"), Expression.Constant(query)));
                    le.Add(whereExpr);
                }

                var agg = le.Aggregate((prev, next) => Expression.And(prev, next));
                return Expression.Lambda<Func<T, bool>>(agg, paramExpr);
            }
        }

        /// <summary>
        /// Expression for an all column search, which will filter the result based on this criterion
        /// </summary>
        private Expression<Func<T, bool>> ApplyGenericSearch
        {
            get
            {
                string search = _httpRequest["sSearch"];

                // default value
                if (string.IsNullOrEmpty(search) || _properties.Length == 0)
                    return x => true;

                // invariant expressions
                var searchExpression = Expression.Constant(search.ToLower());
                var paramExpression = Expression.Parameter(typeof(T), "val");

                // query all properties and returns a Contains call expression 
                // from the ToString().ToLower()
                var propertyQuery = (from property in _properties
                                     let tostringcall = Expression.Call(
                                                         Expression.Call(
                                                             Expression.Property(paramExpression, property), "ToString", new Type[0]),
                                                             typeof(string).GetMethod("ToLower", new Type[0]))
                                     select Expression.Call(tostringcall, typeof(string).GetMethod("Contains"), searchExpression)).ToArray();

                // we now need to compound the expression by starting with the first
                // expression and build through the iterator
                Expression compoundExpression = propertyQuery[0];

                // add the other expressions
                for (int i = 1; i < propertyQuery.Length; i++)
                    compoundExpression = Expression.Or(compoundExpression, propertyQuery[i]);

                // compile the expression into a lambda 
                return Expression.Lambda<Func<T, bool>>(compoundExpression, paramExpression);
            }
        }
    }

    public class FormatedList<T>
    {
        public FormatedList()
        {
        }

        public int sEcho { get; set; }
        public int iTotalRecords { get; set; }
        public int iTotalDisplayRecords { get; set; }
        public List<T> aaData { get; set; }
        public string sColumns { get; set; }

        public void Import(string[] properties)
        {
            sColumns = string.Empty;
            for (int i = 0; i < properties.Length; i++)
            {
                sColumns += properties[i];
                if (i < properties.Length - 1)
                    sColumns += ",";
            }
        }
    }
}

I am a beginner at C#, .NET, Linq etc., though I know quite a few other languages.
Setup: Windows 7 64, Visual Studio 2017.

Thank you for your help!

LePatay
  • 172
  • 1
  • 8
  • You might want to have a look at [What is a NullReferenceException and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it/) – stuartd Dec 20 '17 at 11:56
  • The jQuery DataTables library has nothing to do with C# the DataTable class or this problem. The code you posted uses Reflection to access the generic [Enumerable.ToList](https://msdn.microsoft.com/en-us/library/bb342261%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) method – Panagiotis Kanavos Dec 20 '17 at 13:19
  • @PanagiotisKanavos Yes, it uses Reflection. So ... ? What is the problem with that and how to make the code work, according to you? – LePatay Dec 20 '17 at 13:54
  • 1
    So remove the unnecessary tags and code and check how Reflection works, especially what [Type.GetMethod(string,Type)](https://msdn.microsoft.com/en-us/library/6hy0h0z1(v=vs.110).aspx) does. That method asks for a method ToList that accepts `IEnumerable` as a parameter. [Enumerable.ToList](https://msdn.microsoft.com/en-us/library/bb342261(v=vs.110).aspx) though is a *generic* method. This code could never work. Why was the original parser modified? Why don't you go back to the original? – Panagiotis Kanavos Dec 20 '17 at 14:41
  • In fact, why use the parser at all? Web Forms couldn't understand requests with a Json payload nor return it. ASP.NET MCV or Web API though use JSON by default. DataTables can use jQuery to call an ASP.NET MVC or ASP.NET Web API service just fine. There are even MVC Helper projects [like this one](https://github.com/mcintyre321/mvc.jquery.datatables) that can take care of the boilerplate code. – Panagiotis Kanavos Dec 20 '17 at 14:47
  • Thank you for the explanations. I assume this unfortunate call to a generic method comes from the decompilation (with JustDecompile). I don't know why the original parser was modified. I tried to come back to the original one, but I always get errors "method unknown" at this step: `list.aaData = _queriable.Where(ApplyGenericSearch) .Where(IndividualPropertySearch) .Skip(skip) .Take(take) .Select(SelectProperties) .ToList(); list.iTotalDisplayRecords = list.aaData.Count;` ` – LePatay Dec 20 '17 at 15:01

1 Answers1

0

TL;DR: Use the original parser's SelectProperties function.

Okay, I solved this part of my problem.
My problem was the fact that I used decompilated code, extracted from a DLL. Both .NET Reflector and JustDecompile gave very heavy portions of code, though it was just a bad decompilation of the original line.

Eventually, I understood that my problems of "data in the wrong columns" and "Error 500 - Exception of type 'Antlr.Runtime.NoViableAltException' was thrown" didn't come from this portion of the code.

Here is the explanation of misordered data in server-side processed data using Zack Owen's DataTableParser, due to the alphabetical reordering of the properties inside Entity Framework POCOs when decompiling with JustDecompile or .NET Reflector:
https://forum.red-gate.com/discussion/80861/bugs-of-field-order-in-reflector#Comment_149618

LePatay
  • 172
  • 1
  • 8