views:

289

answers:

3

I haven't worked with GridView too much and after poking around with it, find it to be more complex than I need but missing some very basic abilities that I would expect it to have. No doubt its implementation makes sense given its 90% of the time purpose of being bound to a dataset especially when doing this declaratively, but I intend to bind it to an IEnumerable<T> in code.

What I need is the ability to do the following easily

  • a) be bound to an IEnumerable<T> where the columns can be limited to only certain properties of type T
  • b) be queried to return a collection of its rows where each row can have a cell looked up by the property that cell was bound to

basically something implementing the following interface would be nice

public interface IEasyGridBinder {
  void Bind<T>(IEnumerable<T> bindableObjects, params string[] propertiesToBind);
  IList<IDictionary<string, string>> Values {get;}
}

So to get this, should I write my own custom EasyGridBinder that inherits from GridView and implements this interface or is there a really simple way to do these things that I am just unfamiliar with?

P.S. Bonus points if I can write something like

myGrid.Bind(myEntities, e=>{e.Id; e.Name; e.Customer.Name;});

But I suppose I can figure that out myself after reading up on expressions

Follow-up question: Is there no way to get the original data that was input into the gridview and has not been converted to html? If a field received as input an empty string the cell seems to contain " " so is there no way to distinguish between an input of an empty string and a space? If this is indeed the case then I probably WILL end up re-implementing most of GridView's functionality.

A: 

I would recommend using extension methods to add the required behavior. The only downside to that is that you can't add "Values" as a property.

David
Yeah, but I only want to write an extension if there's a really simple way of doing these things. If there isn't ... say more than three lines, I might as well subclass since I'm probably going to have to do other things as well.
George Mauer
It will definitely be more than 3 lines. The benefit of using the extension method, though, is that you don't have to change any existing ASPX markup to replace "GridView" with "EasyGridBinder".
David
Yeah, this is what I started with but I have a rule that extension methods should be nothing more than convenience shortcuts, any real behavior there tends to be a codesmell.
George Mauer
+1  A: 

If you set the GridViews AutoGenerateColumns property to false it will only generated the columns you specify. You do that by creating BoundFields and adding them to the Gridview Columns collection.

GridView gv = new GridView();
gv.AutoGenerateColumns = false;

BoundField bf = new BoundField();
bf.DataField = "Id";
bf.HeaderText = "ID";
gv.Columns.Add(bf);

BoundField bf = new BoundField();
bf.DataField = "Name";
bf.HeaderText = "Name";
gv.Columns.Add(bf);

BoundField bf = new BoundField();
bf.DataField = "Customer.Name";
bf.HeaderText = "Customer Name";
gv.Columns.Add(bf);

gv.DataSource = IEnumerable<T>;
gv.DataBind();

I had written this explanation up in the comments but figured I'd move it out to where it was more visible and add a code example:

To make the above dynamic create a GridViewDisplayAttribute class which inherits from Attribute. Give GridViewDisplayAttribute a HeaderText property. Decorate the properties of of T specifying HeaderText. By iterating the properties of T creating BoundFields for each decorated property utilizing HeaderText.

Quick untested code example:

using System;
public class GridViewDisplayAttribute : Attribute
{
public GridViewDisplayAttribute(string headerText)
{
        HeaderText = headerText;
}
    public readonly bool HeaderText;
}


GridView gv = new GridView();
gv.AutoGenerateColumns = false;

Type t = <T>.GetType();
PropertyInfo[] pis = t.GetProperties();

foreach (PropertyInfo pi in pis)
{
    GridViewDisplayAttribute[] gvdaArray = pi.GetCustomAttributes(
        typeof(GridViewDisplayAttribute), true);

    foreach (GridViewDisplayAttribute gvda in gvdaArray)
    {
        BoundField bf = new BoundField();
        bf.DataField = pi.Name;
        bf.HeaderText = gvda.HeaderText;
    }

    gv.Columns.Add(bf);
}

gv.DataSource = IEnumerable<T>;
gv.DataBind();
ahsteele
Hmm, that will be pretty helpful, I wasn't having much luck with DataKeyNames. Thanks!
George Mauer
To make dynamic create a GridViewDisplayAttribute : Attribute class. Giving GridViewDisplayAttribute a HeaderText property would allow you to decorate the properties of T specifying HeaderText. By iterating the properties of T creating BoundFields for each decorated property utilizing HeaderText.
ahsteele
+1  A: 

LinqDataSource allows you to specify your object as the backing store for the data source. You'd then bind the GridView to that datasource. It's a bit more declaration in the .aspx, but it's less code to maintain later as feature bloat brings you closer and closer to reimplementing GridView.

DDaviesBrackett
Not bad, but strings everywhere! There goes refactoring support.
George Mauer
I mean is it just me, or are these operations that I'm discussing things that should be quite standard to want out of a GridView? Principle of least astonishment - fail
George Mauer
In my experience, MS's way of attacking these kinds of problems is almost never the least-astonishing way to do things, but in the end it 's the way that (1) works best with what's already there (2) ends up being what I actually wanted, rather than what I thought I wanted.
DDaviesBrackett