Monday 29 November 2010

Vertical GridViews in ASP.net - Part 2c: Plumbing in the custom rendering

Now, as promised, how to plumb in the custom rendering code for the cell level rendering:

The key function you will need to work in is an override to the RenderChildren function in your inherited gridview (protected override void  RenderChildren(HtmlTextWriter writer)).

First off, you need to create a dictionary of your custom row renderers, one for each row in the controls Rows collection (of type GridViewRowCollection), indexed by the original row id (or a list of them in the same order as the original collection. As set up previously, the 'row renderer' is constructed by passing in the gridviewrow.

As you're overriding the child controls the one drawback is that you have to manually set up that table, using the htmlwriter to output the various rendering parameters:

 if(this.BorderWidth != null)
     writer.AddAttribute(HtmlTextWriterAttribute.Border, this.BorderWidth.ToString());
 writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, this.CellSpacing.ToString());
 writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, this.CellPadding.ToString());
 if (this.CssClass != null)
     writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass.ToString());

 writer.RenderBeginTag(HtmlTextWriterTag.Table);

then the important bit - for the moment we'll go with the simplified case that you are displaying all your datarows in one long row (I'll cover wrapping in a future post).

Firstly, iterate over the columns in the GridView's Columns collection (of type DataControlFieldCollection) - there is one entry in this collection for every field (BoundField,TemplateField, etc.) specified in the markup using the gridview.

Within this loop you then need to:
 - Tell the html writer to write out the <TR> tag:

  
writer.RenderBeginTag(HtmlTextWriterTag.Tr);

 - Loop over the rows in renderer list/dictionary, and within that call your renderer's custom cell Render Method:

  Renderers[RowIndex].Render(writer, ColumnIndex, IsHeader);
 - Tell the htmlwriter to write out the closing </TR> tag:

  writer.RenderEndTag();

After the loops you then need to tell the html writer to write the closing </TABLE> tag with a final call to writer.RenderEndTag()


With the code detailed about you now have the full basic solution. I will post more on some additions that you will most likely want - wrapping, empty cell handling, and Custom CSS properties, all of which are used in the featured product grids on http://public.presalesadvisor.com/ - but hopefully this is enough to get you started.

Friday 26 November 2010

Databound google sitemaps with repeaters in asp.net

I thought I'd put out another use for the xml databinding as described in the earlier post on RSS feeds.


The basic principle can be used for anything, but one use we've put it to on the Pre-Sales Advisor site is to generate a databound xml sitemap for google submission (for full details of the format see the official site at http://www.sitemaps.org/).


In most data driven websites the data pages you really want found are displayed by calling a standard page with the correct querystring (e.g. http://public.presalesadvisor.com/Common/BuildByFamily.aspx?identity=2141&dso=en-US for Bladecenter HS22, http://public.presalesadvisor.com/Common/BuildByFamily.aspx?identity=3013&dso=en-US for system x3630 M3s). As long as you can pull out a list of valid identities through a datasource then all you need to do is set up your repeater templates as follows:

 <HeaderTemplate>
 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:image="http://www.sitemaps.org/schemas/sitemap-image/1.1"
        xmlns:video="http://www.sitemaps.org/schemas/sitemap-video/1.1">
</HeaderTemplate>

<FooterTemplate>
</urlset>
</FooterTemplate>

<ItemTemplate>
  <url>
      <loc><%# Server.HtmlEncode(String.Format("http://www.yoursite.com/page.aspx?id={0}", DataBinder.Eval(Container.DataItem, "IDENTITYCOLUMN")))%></loc>
  </url>
</ItemTemplate>


and there you have it. For an example of what it comes out like have a look at the xml site map linked from http://public.presalesadvisor.com/sitemap.aspx (incidentally, if you have a look at the global part map that uses basically. the same data sources and repeater setup).

Thursday 25 November 2010

Vertical GridViews in ASP.net - Part 2b: Cell level rendering a GridViewRow

The key to the solution is the order in which the table cells are rendered out – in order to take over control of this we created a new class to wrap around the GridViewRow so that we could manually call the rendering on the actual cell we required. The structure of the class is quite simple:


  • A class variable to store the GridViewRow this is wrapping around
  • A constructor that takes in a GridViewRow and assigns it to the class variable
  • A render method that takes in a HTMLTextWriter, the index of the cell you want to render within the row, and a flag to indicate if this is to be rendered as a ‘header cell’ (more on the last in a post to follow)
All you need to do then is, within the render method get the correct  cell (of type DataControlFieldCell) within the Cells collection of the base GridViewRow, using the passed index.


If you’re not needing to output the cell as a header (i.e. render as a TH rather than a TD) all you need to do now is call the cell’s RenderControl method, passing in the HtmlTextWriter


If you do need to render as a header cell things aren’t quite as simple – header cells are actually set up during CreateChildControls as a different type -  DataControlFieldHeaderCell rather than DataControlFieldCell.
The simplest solution we found was creating a new DataControlFieldHeaderCell and manually copying the properties that we were interesting in preserving from the original cell (in our case CssClass, HorizontalAlign, and VerticalAlign), and then reassign the child controls from the original cell to the new cell:
 
while (OriginalCell.Controls.Count > 0)
{
  Control Child = OriginalCell.Controls[0];
  HeaderCell.Controls.Add(Child);
}


Once this is done call the header cell’s RenderControl as before.
 
I’ll leave it there for now, but will be post again soon with how to plumb this in to the standard control rendering

Saturday 20 November 2010

Easy guide to databound RSS feeds in Asp.Net

 Ok, while I'm making sure the next part on the vertical datagrids makes sense I thought I'd drop in a quick bit on databound RSS feeds in asp.net


 I'd like to empasise first off this is in no way a method I can take credit for, but I wanted to pass it along to help out any dev's out there who get the fateful brief "oh, before we put this live can you just stick an RSS feed in"


 If you've done any reading then you'll know that RSS (Really Simple Syndication) is based around a specific XML format. The key to simple databound RSS is to use a standard page and standard controls, but writing out XML rather than HTML markup.


 To make the page work as a feed you need to do the following:


 -Delete out all preentered content except for the @Page directive


 -In the @Page directive include the attribute ContentType="text/xml"


 -Include a standard datasource that will output the data that you want to publish


 -Include an asp repeater with the DataSourceID set to your datasource, and with the following content:


HeaderTemplate:


 <rss version="2.0">
      <channel>
        <title>FEED NAME</title>
        <link>URL THAT THE FEED WILL BE PUBLISHED TO</link>
        <description>DESCRIPTION OF THE FEED</description>
       
FooterTemplate:


</channel>
</rss>


ItemTemplate:

        <item>
            <title><%#DataBinder.Eval(Container.DataItem, "POST TITLE COLUMN")%></title>
            <description><%#DataBinder.Eval(Container.DataItem, "POST TEXT SUMMARY COLUMN")%></description>
            <link>CLICK-THROUGH URL</link>
            <pubdate><%#DataBinder.Eval(Container.DataItem, "ARTICLE TIMESTAMP COLUMN")%></pubdate>
        </item>


That's all it takes - Enjoy :)

Tuesday 16 November 2010

Vertical GridViews in ASP.net - Part 2a : The general solution

The approach taken was to override the rendering of the standard gridview to give the desired HTML output.

There are 2 main ways you would do this:

1) Create a new control that inherits from GridView and override the methods involved in rendering

or

2) Use a Control Adapter to provide custom rendering

Personally I don't like the idea of method 2 - unless I'm misunderstanding something here it means rely on the browser capability file to apply, and it generally seems that even if we got the required rendering the application of it would be less precise than explicity specifiying which control you are using.

For our custom control - VerticalGridView - the point where we intercept the standard GridView behaviour is function RenderChildren (protected void RenderChildren(HtmlTextWriter writer)).

In a standard GridView this would render to the output Html writer:

- The table header with attributes set based on the properties set on the grid view

- One Table Row (TR) per DataRow in the source data within the parent table (with attributes set based on the properties set on the grid view)

- One Table Cell (TH/TD) per ItemTemplate specified in the markup, rendered within the parent row (with cell type and attributes set based on the properties in the ItemTemplate)

- Closing table tags

Note that this is not a canonical representation of the standard functionality - just detail appropriate to the level that we needed to control for our purposes. For a full view of the code then I'd recommend downloading the free and wonderful RedGate Reflector and disassembling the method in System.Web.UI.WebControls.GridView, and GridViewRow in System.Web.dll

So this is where we rewrite the rules. How exactly we do that to follow shortly


Just to mention to those who don't already know, the vertical grids described here can be seen in action on the front page of http://public.presalesadvisor.com/




Incedentally, just to thank the GiantCodeMonkey himself for the inspiration for the solution, and the permission to write it up