自定义服务器端DataTables处理:“typeof(Enumerable).GetMethod”为null

问题描述 投票:1回答:1

我已经在一个旧的(2011)C#代码上苦苦挣扎了几天,我从我的老板希望我编辑的DLL中提取。

应用程序使用LINQ查询数据库,服务器端处理它并使用DataTable显示数据。从我收集的内容来看,编写它的人使用.NET Framework 4.0在Visual Studio 2010中创建了一个ASP.NET Web窗体站点。

他使用DataTables pluginserver-side parser from Zack Owens。在我在VS 2017中重新创建的项目中,一切都很好地构建,但是在运行时,一个错误来自他对DataTable解析器的小定制:其中,SelectProperties()函数已经完全重写了:

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();
    }
}

对此:

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
        );
    }
}

我的问题:

  • 如何使这个SelectProperties功能工作?
  • 与原始产品相比,它的目的究竟是什么?我没有得到任何“MethodHandle”位......

我唯一的提示是,当我使用原始的SelectProperties代码时,数据位于错误的列中,并且排序导致错误500“某些列的类型'Antlr.Runtime.NoViableAltException'的异常被抛出”。以下是此自定义DataTable.cs解析器的完整代码: 提示:查找(已编辑)标记

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 += ",";
            }
        }
    }
}

我是C#,.NET,Linq等的初学者,虽然我知道很多其他语言。 设置:Windows 7 64,Visual Studio 2017。

谢谢您的帮助!

c# asp.net .net generics reflection
1个回答
0
投票

TL; DR:使用原始解析器的SelectProperties函数。

好的,我解决了这部分问题。 我的问题是我使用从DLL中提取的分解代码。 .NET Reflector和JustDecompile都提供了非常繁重的代码,尽管它只是原始行的一个糟糕的反编译。

最后,我理解我的“错误列中的数据”和“错误500 - 类型'Antlr.Runtime.NoViableAltException'的异常被抛出”的问题并非来自代码的这一部分。

以下是使用Zack Owen的DataTableParser解释服务器端处理数据中的错误数据,因为在使用JustDecompile或.NET Reflector进行反编译时,实体框架POCO中的属性按字母顺序重新排序: https://forum.red-gate.com/discussion/80861/bugs-of-field-order-in-reflector#Comment_149618

© www.soinside.com 2019 - 2024. All rights reserved.