پیاده سازی Generic Filter و Sorting, Grouping و صفحه بندی با Web API

پیاده سازی Generic Filter

پیاده سازی Generic Filter ، در این مقاله می خواهیم با استفاده از web api و angular js انواع مختلف grid را در هنگام بارگذاری سایت نمایش دهیم.

در اینجا از جدول و گرید استفاده کردیم که تمام عملیات مرتب سازی و صفحه بندی و فیلتر کردن و گروه بندی را در سمت client خواهیم داشت و کد های زیادی را در قسمت fron-end برنامه در view ها خواهیم نوشت.

در اینجا مشکلی که به وجود آمده این است که مدل های مختلفی برای هر کدام از عملیات های صفحه بندی و گروه بندی و.. وجود دارد و کد های مشابه برای هر عملیات زده می شود اما هر مدل از مدل ها فیلد و نوع داده ی متفاوتی دارند.

به عنوان مثال

<، <=،>،> =،! =، == از این عملگرها در انواع داده های عددی و تاریخی استفاده می شود.

برای نوع داده های رشته ما باید از فیلتر های مختلف استفاده کنیم که به عنوان مثال با – شروع شود و با , پایان یابد که یا داده خلی هست یا خالی نیست.

زمانی که می خواهیم این کد ها را تست کنیم، باید کد های زیادی شود دردسر بزرگ این است که ما باید برای هر کدام یک مدل ایجاد کنیم چون که view Model ما به خاطر اشتراک گذاری بزرگ می شود.

برای پیاده سازی Generic Filter از کد زیر استفاده کنیم:

برای استفاده کردن از یک فیلتر عمومی بر روی جدول ها ، کلاس زیر را تعریف کنید:

public class FilterUtility  
{  
      
}  

سپس شما داخل کلاسی که برای فیلتر کردن تعریف کردید یک enum تعریف می کنید که در واقع شما دارید موارد و آیتم هایی که برای فیلتر کردن مورد نظر هست در کلاس زیر تعریف می کنید:

        /// <summary>  
        /// Enums for filter options  
        /// same sequence UI is following  
        /// </summary>  
        public enum FilterOptions  
        {  
            StartsWith = 1,  
            EndsWith,  
            Contains,  
            DoesNotContain,  
            IsEmpty,  
            IsNotEmpty,  
            IsGreaterThan,  
            IsGreaterThanOrEqualTo,  
            IsLessThan,  
            IsLessThanOrEqualTo,  
            IsEqualTo,  
            IsNotEqualTo  
        }  

یک کلاس دیگر برای تعریف پارامترهای کلاس فیلتر در داخل کلاس FilterUtility ایجاد می کنید:

        /// <summary>  
        /// Filter parameters Model Class  
        /// </summary>  
        public class FilterParams  
        {  
            public string ColumnName { get; set; } = string.Empty;  
            public string FilterValue { get; set; } = string.Empty;  
            public FilterOptions FilterOption { get; set; } = FilterOptions.Contains;  
        }  

یک کلاس Filter<T> برای برگرداندن داده های فیلتر شده داخل کلاس FilterUtility ایجاد می کنید.

در کلاس زیر ما دو متد به نام FilteredData() و FilterData() ایجاد می کنید.

متد FilterData در واقع اطلاعات را بر اساس FilterOptions بر می گرداند برخی از گزینه های فیلتر برای انواع داده های خاص قابل استفاده هستند مانند نوع داده هایی که ما در شروع و پایان استفاده کردیم.

متد FilteredData ، وظیقه ای این را دارد که چندین دیتا را همزمان فیلتر کند کد بالا هم زمان چند داده را فیلتر می کند از آن جایی که در کد زیر از Generic ها استفاده شده است باید از reflection ها نوع داده را بدست آوریم.

        /// <summary>  
        /// This is generic class   
        /// responsible for filtering the data  
        /// </summary>  
        /// <typeparam name="T"></typeparam>  
        public class Filter<T>  
        {  
            public static IEnumerable<T> FilteredData(IEnumerable<FilterParams> filterParams, IEnumerable<T> data)  
            {  
  
                IEnumerable<string> distinctColumns = filterParams.Where(x => !String.IsNullOrEmpty(x.ColumnName)).Select(x => x.ColumnName).Distinct();  
  
                foreach (string colName in distinctColumns)  
                {  
                    var filterColumn = typeof(T).GetProperty(colName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);  
                    if (filterColumn != null)  
                    {  
                        IEnumerable<FilterParams> filterValues = filterParams.Where(x => x.ColumnName.Equals(colName)).Distinct();  
  
                        if (filterValues.Count() > 1)  
                        {  
                            IEnumerable<T> sameColData = Enumerable.Empty<T>();  
  
                            foreach (var val in filterValues)  
                            {  
                                sameColData = sameColData.Concat(FilterData(val.FilterOption, data, filterColumn, val.FilterValue));  
                            }  
  
                            data = data.Intersect(sameColData);  
                        }  
                        else  
                        {  
                            data = FilterData(filterValues.FirstOrDefault().FilterOption, data, filterColumn, filterValues.FirstOrDefault().FilterValue);  
                        }  
                    }  
                }  
                return data;  
            }  
            private static IEnumerable<T> FilterData(FilterOptions filterOption, IEnumerable<T> data, PropertyInfo filterColumn, string filterValue)  
            {  
                int outValue;  
                DateTime dateValue;  
                switch (filterOption)  
                {  
                    #region [StringDataType]  
  
                    case FilterOptions.StartsWith:  
                        data = data.Where(x => filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString().ToLower().StartsWith(filterValue.ToString().ToLower())).ToList();  
                        break;  
                    case FilterOptions.EndsWith:  
                        data = data.Where(x => filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString().ToLower().EndsWith(filterValue.ToString().ToLower())).ToList();  
                        break;  
                    case FilterOptions.Contains:  
                        data = data.Where(x => filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString().ToLower().Contains(filterValue.ToString().ToLower())).ToList();  
                        break;  
                    case FilterOptions.DoesNotContain:  
                        data = data.Where(x => filterColumn.GetValue(x, null) == null ||  
                                         (filterColumn.GetValue(x, null) != null && !filterColumn.GetValue(x, null).ToString().ToLower().Contains(filterValue.ToString().ToLower()))).ToList();  
                        break;  
                    case FilterOptions.IsEmpty:  
                        data = data.Where(x => filterColumn.GetValue(x, null) == null ||  
                                         (filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString() == string.Empty)).ToList();  
                        break;  
                    case FilterOptions.IsNotEmpty:  
                        data = data.Where(x => filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString() != string.Empty).ToList();  
                        break;  
                    #endregion  
 
                    #region [Custom]  
  
                    case FilterOptions.IsGreaterThan:  
                        if ((filterColumn.PropertyType == typeof(Int32) || filterColumn.PropertyType == typeof(Nullable<Int32>)) && Int32.TryParse(filterValue, out outValue))  
                        {  
                            data = data.Where(x => Convert.ToInt32(filterColumn.GetValue(x, null)) > outValue).ToList();  
                        }  
                        else if ((filterColumn.PropertyType == typeof(Nullable<DateTime>)) && DateTime.TryParse(filterValue, out dateValue))  
                        {  
                            data = data.Where(x => Convert.ToDateTime(filterColumn.GetValue(x, null)) > dateValue).ToList();  
  
                        }  
                        break;  
  
                    case FilterOptions.IsGreaterThanOrEqualTo:  
                        if ((filterColumn.PropertyType == typeof(Int32) || filterColumn.PropertyType == typeof(Nullable<Int32>)) && Int32.TryParse(filterValue, out outValue))  
                        {  
                            data = data.Where(x => Convert.ToInt32(filterColumn.GetValue(x, null)) >= outValue).ToList();  
                        }  
                        else if ((filterColumn.PropertyType == typeof(Nullable<DateTime>)) && DateTime.TryParse(filterValue, out dateValue))  
                        {  
                            data = data.Where(x => Convert.ToDateTime(filterColumn.GetValue(x, null)) >= dateValue).ToList();  
                            break;  
                        }  
                        break;  
  
                    case FilterOptions.IsLessThan:  
                        if ((filterColumn.PropertyType == typeof(Int32) || filterColumn.PropertyType == typeof(Nullable<Int32>)) && Int32.TryParse(filterValue, out outValue))  
                        {  
                            data = data.Where(x => Convert.ToInt32(filterColumn.GetValue(x, null)) < outValue).ToList();  
                        }  
                        else if ((filterColumn.PropertyType == typeof(Nullable<DateTime>)) && DateTime.TryParse(filterValue, out dateValue))  
                        {  
                            data = data.Where(x => Convert.ToDateTime(filterColumn.GetValue(x, null)) < dateValue).ToList();  
                            break;  
                        }  
                        break;  
  
                    case FilterOptions.IsLessThanOrEqualTo:  
                        if ((filterColumn.PropertyType == typeof(Int32) || filterColumn.PropertyType == typeof(Nullable<Int32>)) && Int32.TryParse(filterValue, out outValue))  
                        {  
                            data = data.Where(x => Convert.ToInt32(filterColumn.GetValue(x, null)) <= outValue).ToList();  
                        }  
                        else if ((filterColumn.PropertyType == typeof(Nullable<DateTime>)) && DateTime.TryParse(filterValue, out dateValue))  
                        {  
                            data = data.Where(x => Convert.ToDateTime(filterColumn.GetValue(x, null)) <= dateValue).ToList();  
                            break;  
                        }  
                        break;  
  
                    case FilterOptions.IsEqualTo:  
                        if (filterValue == string.Empty)  
                        {  
                            data = data.Where(x => filterColumn.GetValue(x, null) == null  
                                            || (filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString().ToLower() == string.Empty)).ToList();  
                        }  
                        else  
                        {  
                            if ((filterColumn.PropertyType == typeof(Int32) || filterColumn.PropertyType == typeof(Nullable<Int32>)) && Int32.TryParse(filterValue, out outValue))  
                            {  
                                data = data.Where(x => Convert.ToInt32(filterColumn.GetValue(x, null)) == outValue).ToList();  
                            }  
                            else if ((filterColumn.PropertyType == typeof(Nullable<DateTime>)) && DateTime.TryParse(filterValue, out dateValue))  
                            {  
                                data = data.Where(x => Convert.ToDateTime(filterColumn.GetValue(x, null)) == dateValue).ToList();  
                                break;  
                            }  
                            else  
                            {  
                                data = data.Where(x => filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString().ToLower() == filterValue.ToLower()).ToList();  
                            }  
                        }  
                        break;  
  
                    case FilterOptions.IsNotEqualTo:  
                        if ((filterColumn.PropertyType == typeof(Int32) || filterColumn.PropertyType == typeof(Nullable<Int32>)) && Int32.TryParse(filterValue, out outValue))  
                        {  
                            data = data.Where(x => Convert.ToInt32(filterColumn.GetValue(x, null)) != outValue).ToList();  
                        }  
                        else if ((filterColumn.PropertyType == typeof(Nullable<DateTime>)) && DateTime.TryParse(filterValue, out dateValue))  
                        {  
                            data = data.Where(x => Convert.ToDateTime(filterColumn.GetValue(x, null)) != dateValue).ToList();  
                            break;  
                        }  
                        else  
                        {  
                            data = data.Where(x => filterColumn.GetValue(x, null) == null ||  
                                             (filterColumn.GetValue(x, null) != null && filterColumn.GetValue(x, null).ToString().ToLower() != filterValue.ToLower())).ToList();  
                        }  
                        break;  
                        #endregion  
                }  
                return data;  
            }  
        }  

یک کلاس جداگانه به نام SortingUtility مانند زیر ایجاد کنید.

public class SortingUtility  
{  
  
}  

یک enum به نام sortorder برای مرتب سازی مانند کلاس زیر ایجاد کنید:

public enum SortOrders  
        {  
            Asc = 1,  
            Desc = 2  
        }  

یک کلاس فرزند برای مرتب کردن پارامترهای خاص داخل SortingUtility ایجاد کنید

public class SortingParams  
        {  
            public SortOrders SortOrder { get; set; } = SortOrders.Asc;  
            public string ColumnName { get; set; }  
        }  

یک کلاس فرزند برای گروه بندی و مرتب سازی ستون ها داخل کلاس SortingUtilityایجاد کنید

        /// <summary>  
        /// Enum for Sorting order  
        /// Asc = Ascending  
        /// Desc = Descending  
        /// </summary>  
  
        public class Sorting<T>  
        {  
            /// <summary>  
            /// Actual grouping will be done in ui,   
            /// from api we will send sorted data based on grouping columns  
            /// </summary>  
            /// <param name="data"></param>  
            /// <param name="groupingColumns"></param>  
            /// <returns></returns>  
            public static IEnumerable<T> GroupingData(IEnumerable<T> data, IEnumerable<string> groupingColumns)  
            {  
                IOrderedEnumerable<T> groupedData = null;  
  
                foreach (string grpCol in groupingColumns.Where(x => !String.IsNullOrEmpty(x)))  
                {  
                    var col = typeof(T).GetProperty(grpCol, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);  
                    if (col != null)  
                    {  
                        groupedData = groupedData == null ? data.OrderBy(x => col.GetValue(x, null))  
                                                        : groupedData.ThenBy(x => col.GetValue(x, null));  
                    }  
                }  
  
                return groupedData ?? data;  
            }  
            public static IEnumerable<T> SortData(IEnumerable<T> data, IEnumerable<SortingParams> sortingParams)  
            {  
                IOrderedEnumerable<T> sortedData = null;  
                foreach (var sortingParam in sortingParams.Where(x=> !String.IsNullOrEmpty(x.ColumnName)))  
                {  
                    var col = typeof(T).GetProperty(sortingParam.ColumnName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);  
                    if (col != null)  
                    {  
                        sortedData = sortedData == null ? sortingParam.SortOrder == SortOrders.Asc ? data.OrderBy(x => col.GetValue(x, null))  
                                                                                                   : data.OrderByDescending(x => col.GetValue(x, null))  
                                                        : sortingParam.SortOrder == SortOrders.Asc ? sortedData.ThenBy(x => col.GetValue(x, null))  
                                                                                            : sortedData.ThenByDescending(x => col.GetValue(x, null));  
                    }  
                }  
                return sortedData ?? data;  
            }  
  
        }  

در کلاس فوق از دو متد استفاده شده است یکی از متد ها وظیفه ی sort کردن و دیگری وظیفه ی مرتب سازی ستون ها را بر عهده دارد
از آنجایی که ما از Generic استفاده می کنیم با استفاده از reflection می توانیم نوع داده ها را برگردانیم.

یک کلاس دیگر برای صفحه بندی جدول ها استفاده می کنیم که این را می توانیم در سمت سرور ایجاد کنیم این کلاس ۳ ویژگی دارد.

یکی مشخص کردن صفحه ، جمع کل صفحات ، و جمع آیتم ها در هر صفحهبرای محاسبه ی تعداد آیتم ها در یک گرید ، یک سازنده ی پارامتری مورد نیاز است که در این کد از دو مقدار بولین استفاده شده است که برای رفتن به صفحه ی بعد و قبل استفاده می شود.

در نهایت یک متد استاتیک برای برگرداندن لیستی از انواع داده های عمومی ایجاد می کنید

 public class PaginatedList<T> : List<T>  
    {  
        public int PageIndex { get; private set; }  
        public int TotalPages { get; private set; }  
        public int TotalItems { get; private set; }  
  
        public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)  
        {  
            PageIndex = pageIndex;  
            TotalItems = count;  
            TotalPages = (int)Math.Ceiling(count / (double)pageSize);  
  
            this.AddRange(items);  
        }  
  
        public bool HasPreviousPage  
        {  
            get  
            {  
                return (PageIndex > 1);  
            }  
        }  
  
        public bool HasNextPage  
        {  
            get  
            {  
                return (PageIndex < TotalPages);  
            }  
        }  
  
        public static async Task<PaginatedList<T>> CreateAsync(IList<T> source, int pageIndex, int pageSize)  
        {  
            var count =  source.Count;  
            var items =  source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();  
            return new PaginatedList<T>(items, count, pageIndex, pageSize);  
        }  
    }  

در قطعه کد پایین یک مدل ورودی برای برگرداندن داده ها ایجاد می کنید مانند کد زیر:

    /// <summary>  
    /// This class contains properites used for paging, sorting, grouping, filtering and will be used as a parameter model  
    ///   
    /// SortOrder   - enum of sorting orders  
    /// SortColumn  - Name of the column on which sorting has to be done,  
    ///               as for now sorting can be performed only on one column at a time.  
    ///FilterParams - Filtering can be done on multiple columns and for one column multiple values can be selected  
    ///               key :- will be column name, Value :- will be array list of multiple values  
    ///GroupingColumns - It will contain column names in a sequence on which grouping has been applied   
    ///PageNumber   - Page Number to be displayed in UI, default to 1  
    ///PageSize     - Number of items per page, default to 25  
    /// </summary>  
    public class PaginatedInputModel  
    {  
        public IEnumerable<SortingUtility.SortingParams> SortingParams {set; get;}  
        public IEnumerable<FilterUtility.FilterParams> FilterParam { get; set; }  
        public IEnumerable<string> GroupingColumns { get; set; } = null;  
        int pageNumber = 1;  
        public int PageNumber { get { return pageNumber; } set { if (value > 1) pageNumber = value; } }  
  
        int pageSize = 25;  
        public int PageSize { get { return pageSize; } set { if (value > 1) pageSize = value; } }  
    }  

در repository شما می توانید با هر دیتابیسی مثل EF و یا ADO.Net بر گردانید و استفاده کنید در این مثال همه ی داده ها در یک حافظه ی Catch ذخیره شده است .

ما تمام عملیات را مثل مرتب سازی ، گروه بندی، صفحه بندی را با استفاده از SampleViewModel ایجاد کردیم شما می توانید از viewModel خودتان استفاده کنید

static readonly MemoryCache objCache = new MemoryCache(new MemoryCacheOptions());  
public async Task<PaginatedList<SampleViewModel>> PagingMethod(PaginatedInputModel pagingParams)  
        {  
            List<SampleViewModel> sampleList = null;  
 
            #region [Caching]  
            if (objCache.Get("SampleCacheId") != null)  
            {  
                sampleList = (List<SampleViewModel>)objCache.Get("SampleCacheId");  
            }  
            else  
            {  
                sampleList = _context.SampleViewModel.FromSql("usp_AllJobs").ToList();  
                objCache.Set("SampleCacheId", sampleList, new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(10)));  
            }  
            #endregion  
 
            #region [Filter]  
            if (pagingParams != null && pagingParams.FilterParam.Any())  
            {  
                sampleList = FilterUtility.Filter<SampleViewModel>.FilteredData(pagingParams.FilterParam, sampleList).ToList() ?? sampleList;  
            }  
            #endregion  
 
            #region [Sorting]  
            if (pagingParams != null && pagingParams.SortingParams.Count() > 0 && Enum.IsDefined(typeof(SortingUtility.SortOrders), pagingParams.SortingParams.Select(x => x.SortOrder)))  
            {  
                sampleList = SortingUtility.Sorting<SampleViewModel>.SortData(sampleList, pagingParams.SortingParams).ToList();  
            }  
            #endregion  
 
            #region [Grouping]  
            if (pagingParams != null && pagingParams.GroupingColumns != null && pagingParams.GroupingColumns.Count() > 0)  
            {  
                sampleList = SortingUtility.Sorting<SampleViewModel>.GroupingData(sampleList, pagingParams.GroupingColumns).ToList() ?? sampleList;  
            }  
            #endregion  
 
            #region [Paging]  
            return await PaginatedList<SampleViewModel>.CreateAsync(sampleList, pagingParams.PageNumber, pagingParams.PageSize);  
            #endregion  
        }  

قبل از فراخوانی تابع باید مقدار های معتبر را از پیاده سازی مدل برای تابع های صفحه بندی و مرتب سازی و سایز صفحه ارسال کرد.

این کد با web api نوشته شده است و از نوشتن کد های زیاد در قسمت fron end جلوگیری می کند.

در زمانی که شبکه ی شما با ترافیکی دارد بدون هیچ مشکلی می تواند داده ها را ارسال کند و کد ساده و بدون پیچیدگی است .


زهره سلطانیان

نوشته‌های مرتبط

دیدگاه‌ها

*
*

این سایت از اکیسمت برای کاهش هرزنامه استفاده می کند. بیاموزید که چگونه اطلاعات دیدگاه های شما پردازش می‌شوند.