Telerik MVC Grid with Dynamic Columns (OR) Binding Grid to Dynamic DataTable

You may come across a requirement where you need to create a grid with dynamic columns. In such a case our model is may not fixed, it is getting change at runtime, in other words you can say it’s dynamic. In such cases it’s really a headache to bind such data to a single grid. This can be achieved by binding DataTable to the grid.  

Here is how we can achieve this.

1. First, we have to get all our data in one DataTable.

      a.  We can convert list into DataTable, one can use below method to convert list into DataTable

        public static DataTable ConvertListToDataTable<T>(List<T> items)
        {
            DataTable dataTable = new DataTable(typeof(T).Name); 
            //Get all the properties
            PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo prop in Props)
            {
                //Setting column names as Property names
                dataTable.Columns.Add(prop.Name);
            } 
            //Fill data into DataTable
            foreach (var item in items)
            {
                var values = new object[Props.Length];
                for (int i = 0; i < Props.Length; i++)
                {
                    //inserting property values to datatable rows
                    values[i] = Props[i].GetValue(item, null);
                }
                dataTable.Rows.Add(values);
            }
            //put a breakpoint here and check datatable
            return dataTable;
       }

(OR)

      b.  We can directly get datatable from database.

        private DataTable GetDataTable()
        {
            var connection = ConfigurationManager.ConnectionStrings["ConnectionStringName"].ConnectionString;
            using (var dataAdapter = new SqlDataAdapter("SELECT * from TableName", connection))
            {
                var dataTable = new DataTable(); 
                dataAdapter.Fill(dataTable);
                dataAdapter.FillSchema(dataTable, SchemaType.Mapped);
                return dataTable;              
            }
        }


2. Then write a method to read data for grid, this method will be called when data requested.

        public ActionResult Read([DataSourceRequest] DataSourceRequest request)
        {
            DataTable Data = GetDataTable();
            return Json(products.ToDataSourceResult(request));
        }


3. And at the end we need to create telerik mvc grid in our view, code snippet is as follows:-

@model System.Data.DataTable

@(Html.Kendo().Grid<dynamic>()
    .Name("gridDataView")
    .Columns(columns =>
    {
        foreach (System.Data.DataColumn column in Model.Columns)
        {
            //You can write additional validation on columns if needed, as shown
            if (column.DataType.FullName == "System.DateTime")
            {
                var c = columns.Bound(column.ColumnName).Format("{0:MM/dd/yyyy}");
            }
            else
            {
                var c = columns.Bound(column.ColumnName);
            }
            //Can have footar Template
            if (column.ColumnName == "UnitPrice")
            {
                c.ClientFooterTemplate("sum:#:sum#").ClientGroupFooterTemplate("sum:#:sum#");
            }
        }
        //Can Have EDIT/DELETE/UPDATE buttons
        //columns.Command(cmd=>cmd.Edit());
    })
    .Pageable()
    .Sortable()
    //.Editable(ed=>ed.Mode(GridEditMode.PopUp))
    .Editable(editable => editable.Mode(GridEditMode.InLine))
    .Filterable()
    .Groupable()
    .DataSource(dataSource => dataSource
        .Ajax()       
        .Model(model =>
            {
                var id = Model.PrimaryKey[0].ColumnName;
                model.Id(id);
                foreach (System.Data.DataColumn column in Model.Columns)
                {
                    var field = model.Field(column.ColumnName, column.DataType);
                    if (column.ColumnName == id)
                    {
                        field.Editable(false);
                    }
                   
                }               
            })
        .Aggregates(a=>
        {
            a.Add("UnitPrice", typeof(decimal?)).Sum();
        })
        .Read(read => read.Action("Read", "Home"))
        //.Update(update => update.Action("Update", "Home"))
    )
)


We are done!



Comments

  1. I was trying to implement this but getting nullreferancr exception "System.NullReferenceException: Object reference not set to an instance of an object.". Any idea?

    ReplyDelete
    Replies
    1. This should work, looks like you are declaring some object but not creating it's instance.

      Delete
  2. Mangal Help Plz.I am using the telerik extension grid.
    My code is :
    @(Html.Telerik().Grid(Model.CustomReportData.Tables[0])
    .Name("CustomReportRunGrid")
    .Groupable(grouping => grouping
    .Groups(groups =>
    {
    foreach (var item in (IList)ViewBag.GroupBy)
    {
    groups.Add(item.GroupOn, typeof(string));
    }
    }))
    .ClientEvents(events => events.OnDataBinding("onDataBinding"))
    .Columns(columns =>
    {
    foreach (DataColumn column in Model.CustomReportData.Tables[0].Columns)
    {
    var GroupByList = (IList)ViewBag.GroupBy;
    bool ContainsItem = (GroupByList).Any(Col => Col.FieldName == column.ColumnName);
    if (ContainsItem)
    {
    IList item = (GroupByList).Where(col => col.FieldName == column.ColumnName).Select(col => col).ToList();
    if (item[0].Condition.ToString() == "MAX")
    {
    if (column.DataType.Name == "DateTime")
    {
    columns.Bound(column.DataType, column.ColumnName).Aggregate(a => a.Max()).GroupFooterTemplate(result => "Max : " + result.Max.Format("{0:MM/dd/yyyy}")).Format("{0:MM/dd/yyyy}").ClientGroupFooterTemplate("Max : <#= Max #>").Format("{0:MM/dd/yyyy}");
    }
    else
    {
    columns.Bound(column.DataType, column.ColumnName).Aggregate(a => a.Max()).GroupFooterTemplate(result => "Max : " + result.Max).ClientGroupFooterTemplate("Max : <#= Max #>");
    }
    }
    else if (item[0].Condition.ToString() == "MIN")
    {
    if (column.DataType.Name == "DateTime")
    { columns.Bound(column.DataType, column.ColumnName).Aggregate(a => a.Min()).GroupFooterTemplate(result => "Min : " + result.Min.Format("{0:MM/dd/yyyy}")).Format("{0:MM/dd/yyyy}").ClientGroupFooterTemplate("Min : <#= Min #>").Format("{0:MM/dd/yyyy}"); }
    else { columns.Bound(column.DataType, column.ColumnName).Aggregate(a => a.Min()).GroupFooterTemplate(result => "Min : " + result.Min).ClientGroupFooterTemplate("Min : <#= Min #>"); }

    }
    }
    }
    })
    .DataBinding(dataBinding =>
    {
    dataBinding.Ajax().OperationMode(GridOperationMode.Client)
    .Select("CustomReportRunAction", "Report");
    })
    .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both))
    .Sortable()
    )
    It works well in every scenario. Except the max min aggregate on dates.
    When I apply max or min or dates , it gives me zero on group footer.

    ReplyDelete
    Replies
    1. I think Aggregate function should not be declared this way.

      Check this link:- http://www.telerik.com/forums/datatable-aggregete-functions
      (Also check sample project link available there.)

      Delete
  3. Mangal I am not using Kendo. I am using telerik extension grid. And telerik extension doesn't support any aggregate function on grid level. It need to declared with columns.
    And also only max & min are not working for dates. It's works for count date or Max min numbers.

    ReplyDelete
  4. Hi Mangalsingh,
    article is good. is it possible to have sample application for download

    ReplyDelete
    Replies
    1. Thanks Ganesh.

      Yes,you can download sample application from below link,

      Sample Application

      Delete
  5. Kendo ui not showing records if record count is more than 50000?

    ReplyDelete

Post a Comment