Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Windows Azure, PHP, ...

About the author

Maarten Balliauw is currently employed as a Technical Evangelist at JetBrains. His interests are mainly web applications developed in ASP.NET (C#) or PHP and the Windows Azure cloud platform.
More about me More about me
Send mail E-mail me


ASP.NET MVC Quickly Pro NuGet Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn
Maarten Balliauw - MVP - Most Valuable Professional
Maarten Balliauw - ASPInsider

Search

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright Maarten Balliauw 2013


Code based ASP.NET MVC GridView

ASP.NET MVC GridViewEarlier this week a colleague of mine asked me if there was such thing as a  DataGrid or GridView or something like that in the ASP.NET MVC framework. My first answer was: "Nope!". I advised him to look for a nice foreach implementation or using ExtJS, Dojo or similar. Which made me think... Why not create a simple GridView extension method which generates a nice looking, plain-HTML grid with all required features like paging, editing, deleting, alternating rows, ...?

The idea was simple: an extension method to the HtmlHelper class would be enough. Required parameters: a header and footer template, item template, edit item template, ... But how to pass in these templates using a simple C# parameter... Luckily,  C# 3.0 introduced lambdas! Why? They are super-flexible and versatile! For instance, take the following code:

// C# code:
public void RenderPerson(Person p, Action<T> renderMethod) {
    renderMethod(p);
}

// ASP.NET code:
<% RenderPerson(new Person(), person => { %>
    Hello! You are <%=person.Name%>.
<% } %>

It translates nicely into:

Response.Write("Hello! You are Maarten.");

Creating a GridView extension method should not be that hard! And it sure isn't.

Live demo

Perhaps I should put this last in my blog posts, but there are always people who are only reading the title and downloading an example:

1. The GridView extension method

Quite short and quite easy:

public static class GridViewExtensions
{
    public static void GridView<T>(
        this HtmlHelper html,
        GridViewData<T> data,
        Action<GridViewData<T>> headerTemplate,
        Action<T, string> itemTemplate,
        string cssClass,
        string cssAlternatingClass,
        Action<T> editItemTemplate,
        Action<GridViewData<T>> footerTemplate)
    {
        headerTemplate(data);

        int i = 0;
        foreach (var item in data.PagedList)
        {
            if (!item.Equals(data.EditItem))
            {
                itemTemplate(item, (i % 2 == 0 ? cssClass : cssAlternatingClass));
            }
            else
            {
                editItemTemplate(item);
            }

            i++;
        }

        footerTemplate(data);
    }
}

2. GridViewData

Of couse, data will have to be displayed. And we'll need a property which sets the current item being edited. Here's my Model I'll be passing to the View:

public class GridViewData<T>
{
    public PagedList<T> PagedList { get; set; }

    public T EditItem { get; set; }
}

By the way, the PagedList<T> I'm using is actually a shameless copy from Rob Conery's blog a while ago.

3. The View

Of course, no rendered HTML without some sort of View. Here's a simplified version in which I pass the GridView<T> extension method the required data, header template, item template, edit item template and footer template. Also noice the alternating rows are simply alternating CSS styles (item and item-alternating).

<%Html.GridView<Employee>(
    this.ViewData.Model,
    data => { %>
        <table class="grid" cellpadding="0" cellspacing="0">
    <% },
    (item, css) => { %>
        <tr class="<%=css%>">
            <td><%=Html.ActionImage<HomeController>(c => c.Edit(item.Id), "~/Content/edit.gif", "Edit", null)%></td>
            <td><%=Html.ActionImage<HomeController>(c => c.Delete(item.Id), "~/Content/delete.gif", "Delete", null)%></td>
            <td>&nbsp;</td>
            <td><%=item.Name%></td>
            <td><%=item.Email%></td>
        </tr>
    <% },
    "item",
    "item-alternating",
    item => { %>
        <%using (Html.Form<HomeController>(c => c.Save(item.Id), FormMethod.Post, new { id = "editForm" })) {%>
            <tr class="item-edit">
                <td><%=Html.SubmitImage("save", "~/Content/ok.gif", new { alt = "Update" })%></td>
                <td><%=Html.ActionImage<HomeController>(c => c.Index(), "~/Content/cancel.gif", "Cancel", null)%></td>
                <td>&nbsp;</td>
                <td><%=Html.TextBox("Name", item.Name)%></td>
                <td><%=Html.TextBox("Email", item.Email)%></td>
            </tr>
        <% } %>
    <% },
    data => { %>
        </table>
<% });%>

4. The Controller

The Controller is perhaps the hardest part: it contains all methods that handle actions which are requested by the View. I have a Show action which simply shows the View with current data. Also, I have implemented an Edit and Save action. Make sure to check my example code download for the full example (earlier in this post).

// ...

public ActionResult Show(int? page)
{
    CurrentPage = page.HasValue ? page.Value : CurrentPage;
    GridViewData<Employee> viewData = new GridViewData<Employee>
    {
        PagedList = Employees.ToPagedList<Employee>(CurrentPage, 4)
    };

    return View("Index", viewData);
}

public ActionResult Edit(int id)
{
    GridViewData<Employee> viewData = new GridViewData<Employee>
    {
        PagedList = Employees.ToPagedList<Employee>(CurrentPage, 4),
        EditItem = Employees.Where( e => e.Id == id).FirstOrDefault()
    };

    return View("Index", viewData);
}

public ActionResult Save(int id)
{
    BindingHelperExtensions.UpdateFrom(
        Employees.Where(e => e.Id == id).FirstOrDefault(),
        Request.Form
    );
    return RedirectToAction("Show");
}

// ...

Note: based on ASP.NET MVC preview 3

kick it on DotNetKicks.com 


Categories: ASP.NET | C# | General | MVC | Projects | Software

Comments (24) -

David United States |

Wednesday, June 04, 2008 3:22 PM

David

Nice. I had used the same technique to create a DataList like view (though no paging).

You can probably abstract out the <table>, <tr>, <td> tags using Response.Write(). But yeah, good stuff. Thanks for sharing.

Here's the code:

        public static void RenderSimpleTable<T>(
            this HtmlHelper helper,
            IEnumerable<T> items,
            int columns,
            Object tableTagAttributes,
            Object trTagAttributes,
            Object tdTagAttributes,
            Action<T> itemRenderHandler)
        {
            helper.RenderSimpleTable(
                items,
                columns,
                tableTagAttributes,
                trTagAttributes,
                tdTagAttributes,
                itemRenderHandler,
                true);
        }

        public static void RenderSimpleTable<T>(
            this HtmlHelper helper,
            IEnumerable<T> items,
            int columns,
            Object tableTagAttributes,
            Object trTagAttributes,
            Object tdTagAttributes,
            Action<T> itemRenderHandler,
            bool evenlySpaceColumns)
        {
            var percent = ((int)(((float)1 / (float)(columns)) * 100)).ToString() + "%";
            var response = helper.ViewContext.HttpContext.Response;

            var tableTagAttributeString
                = tableTagAttributes != null
                    ? " " + tableTagAttributes.ToAttributeList()
                    : String.Empty;

            var trTagAttributesString
                = trTagAttributes != null
                    ? " " + trTagAttributes.ToAttributeList()
                    : String.Empty;

            var tdTagAttributesString
                = tdTagAttributes != null
                    ? " " + tdTagAttributes.ToAttributeList()
                    : String.Empty;

            var evenlySpaceStyleString
                = evenlySpaceColumns
                    ? String.Format(@" style=""width:{0}""", percent)
                    : String.Empty;

            helper.RenderTable<T>(
                items,
                columns,
                () => { response.Write(String.Format(@"<table{0}>", tableTagAttributeString)); },
                () => { response.Write(String.Format(@"<tr{0}>", trTagAttributesString)); },
                (item) => {
                    response.Write(String.Format(@"<td{0}{1}>", evenlySpaceStyleString, tdTagAttributesString));
                    itemRenderHandler(item);
                    response.Write(@"</td>");
                },
                () => { response.Write(@"</tr>"); },
                () => { response.Write(@"</table>"); },
                () => { response.Write(String.Format(@"<td{0}{1}>&nbsp;</td>", evenlySpaceStyleString, tdTagAttributesString)); });
        }

        public static void RenderTable<T>(
            this HtmlHelper helper,
            IEnumerable<T> items,
            int columns,
            Action headRenderHandler,
            Action groupHeadRenderHandler,
            Action<T> itemRenderHandler,
            Action groupTailRenderHandler,
            Action tailRenderHandler,
            Action emptyRenderHandler)
        {
            headRenderHandler();
            int column = 0;

            foreach (var item in items)
            {
                if (column == 0)
                    groupHeadRenderHandler();
                itemRenderHandler(item);
                if (column == columns - 1)
                    groupTailRenderHandler();
                column = (column + 1) % columns;
            }

            if (column > 0)
            {
                for (int i = column; i < columns; i++)
                    emptyRenderHandler();
            }

            tailRenderHandler();
        }

Steve United States |

Wednesday, July 28, 2010 11:30 PM

Steve

Hi David- do you have an example of how this DataList could be used in a nested fashion (such as :

Category1
     product1
     product2
Category2
    product3
    product4
   .
   .

Thanks,

Steve

maartenba Belgium |

Wednesday, June 04, 2008 3:30 PM

maartenba

I smell a very nice combination here for creating on-the-fly gridview generation from any kind of data set Smile

Klaus Graefensteiner United States |

Wednesday, June 04, 2008 4:20 PM

Klaus Graefensteiner

Nice MVC example!

Glenn United States |

Wednesday, June 04, 2008 4:31 PM

Glenn

Nice stuff.  What really needs to occur for Web development to move forward.  A couple things though...

Minor - it looks like the Cancel and Delete icons are mixed up.  (Cancel shows to delete line, Delete shows to Cancel Edit.)
Not so Minor - Sorting would be needed for what I usually use a GridView for.
Not so Minor - It posts back with each added line or change.  Can't it do this using Ajax?  This doesn't look much better than a using ViewState Web Page Control.

maartenba Belgium |

Wednesday, June 04, 2008 5:59 PM

maartenba

That's a consideration to make... I would say: use some of the advanced JavaScript + Ajax grids out there (see start of my post). If you need something similar to the old "postback" method, this would be the way to go. Usage really depends on how and what and when.

Rob Conery United States |

Thursday, June 05, 2008 12:23 AM

Rob Conery

Maarten - great stuff! I've noodled with this stuff using a ComponentController (as that's what this is...) - check it out. That's why we made the ComponentController - for this very thing!

maartenba Belgium |

Thursday, June 05, 2008 9:28 AM

maartenba

Actually this was the first thing that popped up in my mind. I've used the ComponentController before in the Mvc Membership provider (www.codeplex.com/MvcMembership) and it proved quite nice to work with.

Using a ComponentController for something like a gridview would indeed be an interesting approach, as one could use 1 ComponentController base class, if needed a specific ComponentController, and multiple Views with that.

Mohammad Javed India |

Monday, June 09, 2008 12:03 PM

Mohammad Javed

your are providing good code.

Duckie |

Sunday, June 22, 2008 7:53 PM

Duckie

Hm, is there any way to convert this to vb.net, i tried and failed Smile

sp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Employees</h2>
    <%Html.GridView<Employee>(
          this.ViewData.Model,
          data => { %>
            <table class="grid" cellpadding="0" cellspacing="0">
                <tr>
                ....

maartenba Belgium |

Monday, June 23, 2008 8:01 AM

maartenba

Affraid I can't help you there. Are multi-line lambda's supported in VB.NET?

Duckie |

Monday, June 23, 2008 9:19 AM

Duckie

Does not look like it :-(

dkl Czech Republic |

Thursday, July 10, 2008 4:43 PM

dkl

It seems a little bit too complicated. Too many lambdas just to abstract away one foreach cycle. This is the real meat of this approach:


      headerTemplate(data);

        int i = 0;
        foreach (var item in data.PagedList)
        {
            if (!item.Equals(data.EditItem))
            {
                itemTemplate(item, (i % 2 == 0 ? cssClass : cssAlternatingClass));
            }
            else
            {
                editItemTemplate(item);
            }
            i++;
        }

        footerTemplate(data);


Should we sacrifice readability just because one simple foreach cycle?

Swordfish Netherlands |

Friday, July 11, 2008 11:50 PM

Swordfish

I second the remark of dlkl (just above). Only why not using the new 3.5 DataList? It's works perfectly with MVC (See Scott Gurthie blog) and it's not too hard to hard to turn it into a datagrid with ajax support. That's reminds me, A few weeks ago I came across a project name Ajax Data Controls from dotnetjunkies. It can do the same as the 'one-liner' function but it has proper template support, it has better maintainability (very important in larger projects where multiple developers are involved) and as dlkl said: readability is also important.



Mani India |

Monday, July 21, 2008 3:32 PM

Mani

Nice & awesome thank u very much fr giving this code...

vamsi India |

Saturday, May 02, 2009 2:50 PM

vamsi

Hi all,

can any one of you ppl please send me the code to bind gridview in mvc 1.0.

i am getting the following error when i try to bind data to the gridview.

Compiler Error Message: CS1928: 'System.Web.Mvc.HtmlHelper' does not contain a definition for 'GridView' and the best extension method overload 'MvcGridView.Extensions.GridViewExtensions.GridView<T>(System.Web.Mvc.HtmlHelper, MvcGridView.Extensions.GridViewData<T>, System.Action<MvcGridView.Extensions.GridViewData<T>>, System.Action<T,string>, string, string, System.Action<T>, System.Action<MvcGridView.Extensions.GridViewData<T>>)' has some invalid arguments


Thanks in Advance,
vamsi.

GreatButNotForReleasedVersion United States |

Friday, May 15, 2009 10:20 PM

GreatButNotForReleasedVersion

Unfortunately this no longer works for MVC1

This is the problem with eatrly adoption I guess, nothing works after the RC ( in Microsoft )

And I"m too new to help fix it or I would but just a heads up it doesn't work anymore.

maartenba Belgium |

Monday, May 25, 2009 1:49 PM

maartenba

I have uploaded an ASP.NET MVC 1.0 version: blog.maartenballiauw.be/file.axd

Petey United States |

Wednesday, June 17, 2009 4:29 PM

Petey

It's a great thing you post the code up-front!! I hate it when bloggers put the download link all the way in the bottom, or even worse, somewhere in the middle.

Kishan Hathiwala India |

Tuesday, July 27, 2010 10:02 AM

Kishan Hathiwala

Hi, Thanks for the great code. What am trying to do is I am trying to use this gridview in master/details application. I have created 2 tabs in the first tab i see master table fields and in the second tab am trying to setup this gridview with add/edit/delete functionality. I am already done with majority of the task but am getting the following error while at design time: Cannot implicitly convert type 'void' to 'object' AND at runtime am getting: CS1525: Invalid expression term ')'. I can surely help with the code if needed.

Kishan Hathiwala India |

Wednesday, July 28, 2010 9:35 AM

Kishan Hathiwala

Never mind I was able to figure it out. Thanks once again for the great article

Alexander Freiria |

Wednesday, July 28, 2010 4:11 PM

Alexander Freiria

Thanks for sharing the code snippet. I've been using a modified version for projects that I have been working on... Thanks Much!

Greg United States |

Wednesday, November 03, 2010 9:50 AM

Greg

Your live demo ...

http://examples.maartenballiauw.be/MvcGridView/

FAILS Frown

Benny Canada |

Thursday, April 28, 2011 5:44 PM

Benny

This code works great in MVC1 however I'm trying to use this in an MVC3 project with Razor in .Net 4.  I'm having trouble with upgrading the code as the assembly system.web.mvc seems to be a very old version.  Any suggestions?

Pingbacks and trackbacks (16)+

Comments are closed