Mình viết lại cái server-side data của DataTables 1.10+ cho ASP.NET MVC.
Đã có rất nhiều trên NuGet để có thể ngó qua tham khảo. Ví dụ:
https://github.com/ALMMa/datatables.aspnet
https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
Nhưng mình thấy rằng cũng ko cần support nhiều thứ như vậy, chủ yếu là ModelBinder để request thôi, còn lại putput JSON thì tự do. Cho nên modify lại chút, lấy những cái cần xài thôi.
Việc còn lại là query data theo cái DataTablesRequest thì dùng QueryOver là tiện nhất. Nếu cần biết về QueryOver thì series của Andrew Whitaker là nên đọc
http://blog.andrewawhitaker.com/blog/2014/03/12/queryover-series-part-1-why-queryover/
Request
OK code thôi. Lười không tạo project trên GitHub nên post code thẳng lên đây. Giả sử code query Project, nếu cần thì có thể refactor đoạn này thành DataTables<T>columns[i][data]
columns[i][name]
columns[i][orderable]
columns[i][search][regex]
columns[i][search][value]
columns[i][searchable]
...
draw
length
order[i][column]
order[i][dir]
...
search[regex]
search[value]
start
Column name
Mình điều chỉ lại column name cần lấy từ column data và column name trong request gửi lên. Nếu không name mà có data thì xài Inflector chuyển dạng Pascal case ví dụ start_date thành StartDate.Nếu có column data nhưng là số thường là những column như column số thứ tự, column action (Edit, View, Delete) thì bỏ qua.
Sorting
Dùng
query.UnderlyingCriteria.AddOrder(new Order(column.Name, sort.Direction == 1));
Searching
Với search không phải regular expression thì dùng Restrictions
query.Where(Restrictions.InsensitiveLike(column.Name, val, MatchMode.Anywhere));Trường hợp regular expression có thể viết thêm extension cho MS SQL Server, tạm thời không giải quyết ở đây.
Select list
Để select các column sẽ dùng SelectList(), tuy nhiên cần phần AliasToBean nên phải dùng WithAlias. Còn nếu không cần thì mình có thể dùng ngay IList<object>public static class MemberExpressionBuilder { public static Expression<Func<T>> Create<TModel, T>(string propertyName) { var propertyInfo = typeof(TModel).GetProperty(propertyName); var entityParam = Expression.Parameter(typeof(TModel), "e"); Expression columnExpr = Expression.Property(entityParam, propertyInfo); if (propertyInfo.PropertyType != typeof(T)) { columnExpr = Expression.Convert(columnExpr, typeof(T)); } return Expression.Lambda<Func<T>>(columnExpr); } }
Sau khi có data xong thì transform data theo JSON property name yêu cầu. Dùng JsonProperty cho property như draw, recordsTotal, recordsFiltered...
Code của controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | /// <summary> /// GET: /Projects/DataTable /// </summary> [HttpPost] [Route("Projects/DataTables")] public ActionResult DataTables(DataTablesRequest filterRequest) { //if (!Request.IsAjaxRequest()) //{ // ViewBag.Message = "Not Supported"; // return View(); //} var query = NHibernateSession.Current.QueryOver<Project>(); foreach (var column in filterRequest.Columns.Values) { if (string.IsNullOrEmpty(column.Name) && !string.IsNullOrEmpty(column.Data)) { // If data is number, maybe it is compute column? int i; if (int.TryParse(column.Data, out i)) { continue; } // Use reflector from data column.Name = column.Data.Pascalize(); } } // Order by if (filterRequest.Sort.Count > 0) { foreach (var sort in filterRequest.Sort) { var column = filterRequest.Columns[sort.Column]; if (!column.Sortable || string.IsNullOrEmpty(column.Name) || string.IsNullOrEmpty(column.Data)) { continue; } query.UnderlyingCriteria.AddOrder( new Order(column.Name, sort.Direction == 1)); } } // Apply search if (filterRequest.Search != null && !string.IsNullOrWhiteSpace(filterRequest.Search.Value)) { foreach (var column in filterRequest.Columns.Values) { if (!column.Searchable || string.IsNullOrEmpty(column.Name) || string.IsNullOrEmpty(column.Data)) { continue; } var val = filterRequest.Search.Value; if (filterRequest.Search.IsRegex) { } else { query.Where(Restrictions.InsensitiveLike( column.Name, val, MatchMode.Anywhere)); } } } var countQuery = query.ToRowCountQuery(); var totalCount = countQuery.FutureValue<int>(); Project project = null; query.SelectList( list => { list = list.Select(x => x.Id).WithAlias(() => project.Id); foreach (var column in filterRequest.Columns.Values) { if (string.IsNullOrEmpty(column.Name) || string.IsNullOrEmpty(column.Data)) { continue; } list = list.Select(Projections.Property(column.Name)) .WithAlias(MemberExpressionBuilder .Create<Project, object>(column.Name)); } return list; } ).TransformUsing(Transformers.AliasToBean<Project>()) .Skip(filterRequest.Start); if (filterRequest.Length > 0) { query.Take(filterRequest.Length); } var results = query.List<Project>() ?? new List<Project>(); var dataSource = results.ToArray(); var response = new DataTablesResponse<Project>(filterRequest.Draw, totalCount.Value, dataSource.Length, dataSource); var transformResponse = response.Transform<Project, object>(x => { var row = new Dictionary<string, object>(); row.Add("id", x.Id); foreach (var column in filterRequest.Columns.Values) { if (string.IsNullOrEmpty(column.Name) || string.IsNullOrEmpty(column.Data)) { continue; } // Get property value var propertyInfo = typeof(Project).GetProperty(column.Name); object propertyValue = null; if (propertyInfo != null) { propertyValue = propertyInfo.GetValue(x, null); } if (!row.ContainsKey(column.Data)) { row.Add(column.Data, propertyValue); } } return row; }); string serialized = JsonConvert.SerializeObject( transformResponse, Formatting.Indented); Response.ContentEncoding = Encoding.UTF8; string contentType = "application/json; charset=utf-8"; var bytes = Encoding.UTF8.GetBytes(serialized); return new FileContentResult(bytes, contentType); } |
DataTables request
Các interface class dùng parse request.
IColumn
/// <summary> /// Columns' parameters for filter requests /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public interface IColumn { /// <summary> /// Column's data source. /// </summary> string Data { get; set; } /// <summary> /// Column's name. /// </summary> string Name { get; set; } /// <summary> /// Flag to indicate if this column is searchable (true) or not (false). /// </summary> bool Searchable { get; set; } /// <summary> /// Flag to indicate if this column is sortable (true) or not (false). /// </summary> bool Sortable { get; set; } /// <summary> /// The search component for the column. /// </summary> ISearch Search { get; set; } }
ISearch
/// <summary> /// Search configuration for columns of a filter request /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public interface ISearch { /// <summary> /// Search value to apply to this specific column. /// </summary> string Value { get; set; } /// <summary> /// Flag to indicate if the search term for this column should be treated as regular expression (true) or not (false). As with global search, normally server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the discretion of your script. /// </summary> bool IsRegex { get; set; } }
ISort
/// <summary> /// Sort configuration for filter requests /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public interface ISort { /// <summary> /// Column to which ordering should be applied. This is an index /// reference to the columns array of information that is also submitted to the server. /// </summary> int Column { get; set; } /// <summary> /// Ordering direction for this column. It will be ASC or DESC to /// indicate ascending ordering or descending ordering, respectively. /// </summary> int Direction { get; set; } }
IDataTablesRequest
/// <summary> /// Filter request parameters /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public interface IDataTablesRequest { /// <summary> /// Draw counter. This is used by DataTables to ensure that the AJAX /// returns from server-side processing requests are drawn in sequence by DataTables /// (AJAX requests are asynchronous and thus can return out of sequence). /// </summary> int Draw { get; set; } /// <summary> /// Paging first record indicator. This is the start point in the current data set /// (0 index based - i.e. 0 is the first record). /// </summary> int Start { get; set; } /// <summary> /// Number of records that the table can display in the current draw. /// It is expected that the number of records returned will be equal to /// this number, unless the server has fewer records to return. /// Note that this can be -1 to indicate that all records should be returned /// (although that negates any benefits of server-side processing!) /// </summary> int Length { get; set; } /// <summary> /// Global search component to be applied to all columns which have searchable as true. /// </summary> ISearch Search { get; set; } /// <summary> /// The columns' sorting configuration. /// </summary> List<ISort> Sort { get; set; } /// <summary> /// The columns collection. /// </summary> Dictionary<int, IColumn> Columns { get; set; } }
IDataTablesResponse
/// <summary> /// Mutable page response /// /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public interface IDataTablesResponse { /// <summary> /// The draw counter that this object is a response to - /// from the draw parameter sent as part of the data request. /// </summary> int Draw { get; set; } /// <summary> /// Total records, before filtering (i.e. the total number of records in the database). /// </summary> int TotalRecords { get; set; } /// <summary> /// Total records, after filtering (i.e. the total number of records /// after filtering has been applied - not just the number of records being returned for this page of data). /// </summary> int TotalFilteredRecords { get; set; } /// <summary> /// The data to be displayed in the table. This is an array of data /// source objects, one for each row, which will be used by DataTables. /// Note that this parameter's name can be changed using the ajaxDT option's dataSrc property. /// </summary> object[] Data { get; set; } /// <summary> /// Optional: If an error occurs during the running of /// the server-side processing script, you can inform the user of /// this error by passing back the error message to be displayed using this parameter. /// Do not include if there is no error. /// </summary> string Error { get; set; } } /// <summary> /// Type-safe page response /// </summary> /// <typeparam name="TSource">The type of the data array</typeparam> public interface IDataTablesResponse<TSource> : IDataTablesResponse { TSource[] DataSource { get; set; } IDataTablesResponse<TSource> Transform(Func<TSource, TSource> rowTransform); IDataTablesResponse<object> Transform<TMutableSource, TTransform>( Func<TMutableSource, TTransform> rowTransform); }
FormConvertHelper
Dùng parse optional parameter cho DataTablesRequest, không cần thì có thể bỏ luôn.
/// <summary> /// Form convert helper /// /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public static class FormConvertHelper { public static void ReadForm(object message, Dictionary<string, object> otherValues, Func<PropertyDescriptor, bool> shouldBypass) { var objectType = message.GetType(); var typeDescriptor = TypeDescriptor.GetProvider(objectType).GetTypeDescriptor(objectType); foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties()) { if (shouldBypass != null && shouldBypass(propertyDescriptor)) { continue; } if (propertyDescriptor.Attributes.Cast<Attribute>() .OfType<JsonIgnoreAttribute>().Count() == 0) { var propAttr = propertyDescriptor.Attributes.Cast<Attribute>() .OfType<JsonPropertyAttribute>().SingleOrDefault(); var propertyName = propAttr != null ? propAttr.PropertyName : propertyDescriptor.Name; if (otherValues.ContainsKey(propertyName)) { if (otherValues[propertyName] != null) { var convertedValue = TypeHelper.GetDefaultValue(propertyDescriptor.PropertyType); var success = TypeHelper.TryCast(otherValues[propertyName], out convertedValue, propertyDescriptor.PropertyType); if (success) { propertyDescriptor.SetValue(message, convertedValue); } } } } } } public static void ReadForm(object message, IValueProvider valueProvider, Func<PropertyDescriptor, bool> shouldBypass) { var objectType = message.GetType(); var typeDescriptor = TypeDescriptor.GetProvider(objectType).GetTypeDescriptor(objectType); foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties()) { if (shouldBypass != null && shouldBypass(propertyDescriptor)) { continue; } if (propertyDescriptor.Attributes.Cast<Attribute>().OfType<JsonIgnoreAttribute>().Count() == 0) { var propAttr = propertyDescriptor.Attributes .Cast<Attribute>().OfType<JsonPropertyAttribute>().SingleOrDefault(); var propertyName = propAttr != null ? propAttr.PropertyName : propertyDescriptor.Name; var valueResult = valueProvider.GetValue(propertyName); if (valueResult != null) { object convertedValue = valueResult.ConvertTo(propertyDescriptor.PropertyType); propertyDescriptor.SetValue(message, convertedValue); } } } } public static IEnumerable<PropertyDescriptor> GetPropertiesFromType(Type type) { var typeDescriptor = TypeDescriptor.GetProvider(type).GetTypeDescriptor(type); foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties()) { yield return propertyDescriptor; } } }
TypeHelper
/// <summary> /// Type helper /// /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public static class TypeHelper { public static bool CanAssignValue(this PropertyDescriptor p, object value) { return value == null ? p.IsNullable() : p.PropertyType.IsInstanceOfType(value); } public static bool IsNullable(this PropertyDescriptor p) { return p.PropertyType.IsNullable(); } public static bool IsNullable(this Type t) { return !t.IsValueType || Nullable.GetUnderlyingType(t) != null; } public static T GetDefaultValue<T>() { // We want an Func<T> which returns the default. // Create that expression here. var e = Expression.Lambda<Func<T>>( // The default value, always get what the *code* tells us. Expression.Default(typeof(T)) ); // Compile and return the value. return e.Compile()(); } public static object GetDefaultValue(Type type) { // Validate parameters. if (type == null) { throw new ArgumentNullException("type"); } // We want an Func<object> which returns the default. // Create that expression here. var e = Expression.Lambda<Func<object>>( // Have to convert to object. Expression.Convert( // The default value, always get what the *code* tells us. Expression.Default(type), typeof(object) ) ); // Compile and return the value. return e.Compile()(); } public static bool TryCast<T>(object value, out T result) { var type = typeof(T); // If the type is nullable and the result should be null, set a null value. if (type.IsNullable() && (value == null || value == DBNull.Value)) { result = default(T); return true; } // Convert.ChangeType fails on Nullable<T> types. We want to try to cast to the underlying type anyway. var underlyingType = Nullable.GetUnderlyingType(type) ?? type; try { if (underlyingType == typeof(Guid)) { if (value is string) { value = new Guid(value as string); } if (value is byte[]) { value = new Guid(value as byte[]); } } else if (underlyingType.IsEnum && value != null) { value = Enum.Parse(underlyingType, value.ToString(), true); } result = (T)Convert.ChangeType(value, underlyingType); return true; } catch { result = default(T); return false; } } public static bool TryCast(object value, out object result, Type type) { var s = "TryCast"; var openTryCastMethod = typeof(TypeHelper) .GetMethods().Single(x => x.Name == s && x.GetGenericArguments().Count() == 1); var closedTryCastMethod = openTryCastMethod.MakeGenericMethod(type); result = TypeHelper.GetDefaultValue(type); var args = new object[] { value, result }; bool success = (bool)closedTryCastMethod.Invoke(null, args); result = args[1]; return success; } }
Column
/// <summary> /// Datatables column /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public class Column : IColumn { /// <summary> /// Column's data source. /// </summary> public virtual string Data { get; set; } /// <summary> /// Column's name. /// </summary> public virtual string Name { get; set; } /// <summary> /// Flag to indicate if this column is searchable (true) or not (false). /// </summary> public virtual bool Searchable { get; set; } /// <summary> /// Flag to indicate if this column is sortable (true) or not (false). /// </summary> public virtual bool Sortable { get; set; } /// <summary> /// The search component for the column. /// </summary> private ISearch _search; public virtual ISearch Search { get { if (_search == null) { _search = new Search(); } return _search; } set { _search = value ?? new Search(); } } public Column() { } }
Search
/// <summary> /// Search configuration for columns of a filter request /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public class Search : ISearch { /// <summary> /// Search value to apply to this specific column. /// </summary> public virtual string Value { get; set; } /// <summary> /// Flag to indicate if the search term for this column should be /// treated as regular expression (true) or not (false). /// As with global search, normally server-side processing scripts /// will not perform regular expression searching for performance /// reasons on large data sets, but it is technically possible and at the discretion of your script. /// </summary> public virtual bool IsRegex { get; set; } public Search() { } public Search(string value, bool isRegex) { Value = value; IsRegex = isRegex; } }
Sort
/// <summary> /// Sort configuration for filter requests /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public class Sort : ISort { /// <summary> /// Column to which ordering should be applied. This is an index reference /// to the columns array of information that is also submitted to the server. /// </summary> public virtual int Column { get; set; } /// <summary> /// Ordering direction for this column. It will be ASC or DESC to /// indicate ascending ordering or descending ordering, respectively. /// </summary> public virtual int Direction { get; set; } public Sort() { } public Sort(int column, int direction) { Column = column; Direction = direction; } public Sort(int column, bool asc) { Column = column; Direction = asc ? 1 : 0; } }
ModelBinder
Để sử dụng ModelBinder, register trong Global.asax
public class BinderConfig { public static void RegisterBinders(ModelBinderDictionary modelBinderDictionary) { modelBinderDictionary.Add(typeof(DataTablesRequest), new ModelBinder()); } } ...Global.asax protected void Application_Start() { ... FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // Register DataTables FilterRequest BinderConfig.RegisterBinders(ModelBinders.Binders); ... }
Model để parse request
/// <summary> /// Model binder for datatables.js parameters /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public class ModelBinder : IModelBinder { private readonly Type _concreteType; public ModelBinder(Type concreteType = null) { if (concreteType != null) { if (!concreteType.IsClass) { throw new NotSupportedException(); } if (!concreteType.GetInterfaces().Any(x => x == typeof(IDataTablesRequest))) { throw new NotSupportedException(); } _concreteType = concreteType; } else { _concreteType = typeof(DataTablesRequest); } } public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var request = Activator.CreateInstance(_concreteType) as IDataTablesRequest; Parse(ref request, controllerContext.HttpContext.Request.QueryString); Parse(ref request, controllerContext.HttpContext.Request.Form); return request; } private void Parse(ref IDataTablesRequest request, NameValueCollection collection) { var otherValues = new Dictionary<string, object>(); foreach (string key in collection.AllKeys) { if (key == "draw") { request.Draw = GetValue<int>(collection[key]); } else if (key == "start") { request.Start = GetValue<int>(collection[key]); } else if (key == "length") { request.Length = GetValue<int>(collection[key]); } else if (key == "search[value]") { request.Search.Value = GetValue<string>(collection[key]); } else if (key == "search[regex]") { request.Search.IsRegex = GetValue<bool>(collection[key]); } else if (key.StartsWith("order")) { ParseSorting(ref request, key, collection[key]); } else if (key.StartsWith("columns")) { ParseColumn(ref request, key, collection[key]); } else { otherValues.Add(key, collection[key]); } } FormConvertHelper.ReadForm(request, otherValues, prop => FormConvertHelper.GetPropertiesFromType(typeof(IDataTablesRequest)) .Select(x => x.Name).Contains(prop.Name)); } private void ParseSorting(ref IDataTablesRequest request, string key, object value) { var match = Regex.Match(key, @"order\[([0-9]+)\](.+)"); if (match.Success && match.Groups.Count == 3) { var index = Convert.ToInt32(match.Groups[1].Value); var propertyName = match.Groups[2].Value; while (index >= request.Sort.Count) { request.Sort.Add(new Sort()); } if (propertyName == "[column]") { request.Sort[index].Column = GetValue<int>(value); } else if (propertyName == "[dir]") { request.Sort[index].Direction = string.Compare(GetValue<string>(value), "asc", true) == 0 ? 1 : 0; } } } private void ParseColumn(ref IDataTablesRequest request, string key, object value) { var match = Regex.Match(key, @"columns\[([0-9]+)\](.+)"); if (match.Success && match.Groups.Count == 3) { var index = Convert.ToInt32(match.Groups[1].Value); var propertyName = match.Groups[2].Value; IColumn currentColumn = null; if (!request.Columns.ContainsKey(index)) { currentColumn = new Column(); request.Columns.Add(index, currentColumn); } else { currentColumn = request.Columns[index]; } if (propertyName == "[data]") { currentColumn.Data = GetValue<string>(value); } else if (propertyName == "[name]") { currentColumn.Name = GetValue<string>(value); } else if (propertyName == "[searchable]") { currentColumn.Searchable = GetValue<bool>(value); } else if (propertyName == "[orderable]") { currentColumn.Sortable = GetValue<bool>(value); } else if (propertyName == "[search][value]") { currentColumn.Search.Value = GetValue<string>(value); } else if (propertyName == "[search][regex]") { currentColumn.Search.IsRegex = GetValue<bool>(value); } } } private static T GetValue<T>(object value) { return (value == null) ? default(T) : (T)Convert.ChangeType(value, typeof(T)); } }
DataTablesRequest
/// <summary> /// Datatables request /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public class DataTablesRequest : IDataTablesRequest { /// <summary> /// Draw counter. This is used by DataTables to ensure that the AJAX returns /// from server-side processing requests are drawn in sequence by DataTables /// (Ajax requests are asynchronous and thus can return out of sequence). /// </summary> public virtual int Draw { get; set; } /// <summary> /// Paging first record indicator. This is the start point in the /// current data set (0 index based - i.e. 0 is the first record). /// </summary> public virtual int Start { get; set; } /// <summary> /// Number of records that the table can display in the current draw. /// It is expected that the number of records returned will be equal /// to this number, unless the server has fewer records to return. /// Note that this can be -1 to indicate that all records should be returned /// (although that negates any benefits of server-side processing!) /// </summary> public virtual int Length { get; set; } /// <summary> /// Global search component to be applied to all columns which have searchable as true. /// </summary> private ISearch _search; public virtual ISearch Search { get { if (_search == null) { _search = new Search(); } return _search; } set { _search = value ?? new Search(); } } /// <summary> /// The columns' sorting configuration. /// </summary> private List<ISort> _sort; public virtual List<ISort> Sort { get { if (_sort == null) { _sort = new List<ISort>(); } return _sort; } set { _sort = value ?? new List<ISort>(); } } /// <summary> /// The columns collection. /// </summary> private Dictionary<int, IColumn> _columns; public virtual Dictionary<int, IColumn> Columns { get { if (_columns == null) { _columns = new Dictionary<int, IColumn>(); } return _columns; } set { _columns = value ?? new Dictionary<int, IColumn>(); } } public DataTablesRequest() { Length = 10; } }
DataTables response
Sau khi có response dạng IDataTablesResponse<Project> chuyển về dạng IDataTablesResponse<object>
DataTablesResponse
/// <summary> /// Mutable page response /// /// https://github.com/ALMMa/datatables.aspnet /// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables /// </summary> public abstract class DataTablesResponse { /// <summary> /// The draw counter that this object is a response to - /// from the draw parameter sent as part of the data request. /// </summary> [JsonProperty("draw")] public virtual int Draw { get; set; } /// <summary> /// Total records, before filtering (i.e. the total number of records in the database). /// </summary> [JsonProperty("recordsTotal")] public virtual int TotalRecords { get; set; } /// <summary> /// Total records, after filtering (i.e. the total number of records after /// filtering has been applied - not just the number of records being returned for this page of data). /// </summary> [JsonProperty("recordsFiltered")] public virtual int TotalFilteredRecords { get; set; } /// <summary> /// The data to be displayed in the table. This is an array of data source objects, /// one for each row, which will be used by DataTables. Note that this /// parameter's name can be changed using the ajaxDT option's dataSrc property. /// </summary> [JsonProperty("data")] public virtual object[] Data { get; set; } /// <summary> /// Optional: If an error occurs during the running of the server-side processing script, /// you can inform the user of this error by passing back the error /// message to be displayed using this parameter. Do not include if there is no error. /// </summary> [JsonProperty("error")] public virtual string Error { get; set; } public DataTablesResponse() { } } /// <summary> /// Type-safe page response /// </summary> /// <typeparam name="TSource">The type of the data array</typeparam> public class DataTablesResponse<TSource> : DataTablesResponse, IDataTablesResponse<TSource> { [JsonIgnore] public virtual TSource[] DataSource { get { return base.Data != null ? base.Data.Cast<TSource>().ToArray() : null; } set { base.Data = value != null ? value.Cast<object>().ToArray() : null; } } public DataTablesResponse() { } public DataTablesResponse(int draw, TSource[] dataSource) { Draw = draw; TotalRecords = dataSource.Length; TotalFilteredRecords = dataSource.Length; DataSource = dataSource; } public DataTablesResponse(int draw, int totalRecords, int totalDisplayRecords, TSource[] dataSource) { Draw = draw; TotalRecords = totalRecords; TotalFilteredRecords = totalDisplayRecords; DataSource = dataSource; } public virtual IDataTablesResponse<TSource> Transform(Func<TSource, TSource> rowTransform) { var data = DataSource.Select(rowTransform).ToArray(); var response = new DataTablesResponse<TSource>( Draw, TotalRecords, TotalFilteredRecords, data); return response; } public virtual IDataTablesResponse<object> Transform<TMutableSource, TTransform>( Func<TMutableSource, TTransform> rowTransform) { var data = Data.Cast<TMutableSource>() .Select(rowTransform).Cast<object>().ToArray(); var response = new DataTablesResponse<object>( Draw, TotalRecords, TotalFilteredRecords, data); return response; } }
No comments:
Post a Comment