ASP.net Gridview Forward Only Paging

Erinnern Sie sich noch an “Frames sind böse”? Oder HTML Layout Tabellen sind pfui. Dabei sind <table> Element immer noch völlig OK wenn es um die Darstellung von Daten in Tabellen geht. Streng genommen sind sie sogar erste Wahl, da die Struktur z.B. von Lesegeräten für Sehbehinderte ausgewertet werden kann. 

GirdView Bäh oder Super?

Eine HTML Tabelle sollte heute so aussehen. Ein Thead und tbody Element und die Header Spalten mit th.

   1:    <div>
   2:      <table cellspacing="0"  id="GridView1" style="border-style:None;border-collapse:collapse;">
   3:          <thead>
   4:              <tr>
   5:                  <th scope="col">Customer_ID</th><th scope="col">Company_Name</th>
<
th scope="col">Contact_Name</th><th scope="col">Postal_Code</th>
   6:              </tr>
   7:          </thead><tbody>
   8:              <tr>
   9:                  <td>ALFKI</td><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>12209</td>
  10:              </tr><tr>
  11:                  <td>ANATR</td><td>Ana Trujillo Emparedados y helados</td><td>Ana Trujillo</td>
<
td>05021</td>
  12:              </tr><tr>
  13:                  <td>ANTON</td><td>Antonio Moreno Taquer&#237;a</td><td>Antonio Moreno</td>
<
td>05023</td>
  14:              </tr><tr>
  15:                  <td>AROUT</td><td>Around the Horn</td><td>Thomas Hardy</td><td>WA1 1DP</td>
  16:              </tr><tr>
  17:                  <td>BERGS</td><td>Berglunds snabbk&#246;p</td><td>Christina Berglund</td>
<
td>S-958 22</td>
  18:              </tr>
  19:          </tbody>
  20:      </table>
  21:  </div>
  22:   

Man wird es kaum glauben. Obiges HTML wird so vom ASP.NET Gridview Control erzeugt. Einen Haken gibt es, um das Rendering des THEAD und TBODY  zu erzwingen muss man im Code zb Page_Load das Attribut  setzen GridView1.HeaderRow.TableSection = TableRowSection.TableHeader

Das GridView wird per ASP.NET Model Binding an die Northwind Customers gebunden

   1:  <asp:GridView ID="GridView1" runat="server" SelectMethod="loadData" AutoGenerateColumns="false"  
   2:        ShowHeaderWhenEmpty="true" BorderStyle="None"  ClientIDMode="Static"  Showheader="true"
   3:       EmptyDataText="empty" GridLines="None" ViewStateMode="Disabled">
   4:      <Columns>
   5:          <asp:BoundField DataField="Customer_ID"  HeaderText="Customer_ID" ReadOnly="True"/>
   6:          <asp:BoundField DataField="Company_Name" HeaderText="Company_Name" ReadOnly="True"/>
   7:          <asp:BoundField DataField="Contact_Name" HeaderText="Contact_Name" ReadOnly="True"/>
   8:          <asp:BoundField DataField="Postal_Code" HeaderText="Postal_Code" ReadOnly="True" />
   9:      </Columns>
  10:   
  11:  </asp:GridView>

Erster Checkpoint ist also bestanden. Das Gridview Steuerelement erzeugt sauberen HMTL 5 Code. Einzig ein seltsames DIV liegt noch außen rum. Ich kann also das Gridview als Server Seitiges Layout Template benutzen.

Teil 2 meiner Idee ist, für das Paging immer fünf Datensätze im Gridview anzeigen zu lassen und nur den Table Part per RenderControl am Server erzeugen zu lassen. Ein kleines Stück JavaScript soll dann eine Methode am Server aufrufen und die pure <TABLE><TR><TD> .. Sequenz einfach unten an die bestehende Tabelle anhängen.  Die Server Methode kann man per miniService in die Page per <WebMethod> einbetten. Leider liefert diese nur SOAP oder JSON. Die wunderbare native HTML Code Anweisung muss also in ein JSON serialsiert und am Client wieder deserialisiert werden. Ohne diesen Overhead (an dem ich später noch feile) wird dieser Ansatz wesentlich effizienter sein als ein Web Api, Knockout und HTML Binding Ansatz. Außerdem muss man dafür ein ganz eigenes Userinterface und Service bauen. Ziel soll es aber sein so nah wie möglich an Webforms dran zu bleiben.

Allerdings und nun folgen weitere Haken und Ösen ist das mit dem RenderControl weit weniger trivial als ich das in Erinnerung hatte. Erstens muss man das Gridview in ein UserControl packen. Außerdem muss das Pagegerüst außen rum  liegen. Also erst Page bauen, Control laden, rendern und am ende ein wenig überflüssigen HTML wieder wegschnippeln.

   1:    <WebMethod()>
   2:      Public Shared Function getTable(seite As Integer) As String
   3:          Dim p As New basepage
   4:          Dim Grid1 As myGrid = DirectCast(p.LoadControl("myGrid.ascx"), myGrid)
   5:          Grid1.pageNumber = seite
   6:          Grid1.DataBind()
   7:          Dim sw As System.IO.StringWriter = New System.IO.StringWriter()
   8:          Dim hw As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(sw)
   9:          Grid1.RenderControl(hw)
  10:          Dim xml As String = sw.ToString()
  11:          Dim s1 = xml.IndexOf("<tr>")
  12:          Dim s2 = xml.IndexOf("</table")
  13:          Return xml.Substring(s1, s2 - s1)
  14:      End Function

Wer genau hinsieht, wird bemerken das ich kein Page verwende, sondern basepage. Dies um den erzeugten HTML Code vorab schon zu minimieren, weil eigentlich wäre d auch noch FORM nötig.

   1:  Public Class basepage
   2:      Inherits Page
   3:   
   4:      Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control)
   5:          Return
   6:      End Sub
   7:  End Class

Geht auch mit Page aber muss dann im Nachgang mehr HTML Code beschnitten werden. Das erscheint mir unsinnig.

Um die PageMethod aufzurufen, verwende ich JQuery.  Wenn man die PageMethod mit einem ASP.NET Scriptmanager referenziert, geht's auch mit dem erzeugten Proxy bzw nur einer Zeile Code

PageMethods.getTable

Wie gesagt ich verwende Jquery, ein wenig Paging Logik am Client für den Seitenzähler und einen “ich will mehr” Hyperlink. Weil mein GridView ClientIDMode Static gesetzt ist, kann man sich auch auf die generierte ClientID 100%tig verlassen und dann einfach den erhaltenen <Table> Wust anhängen.

   1:         <script>
   2:              function loadmeins() {
   3:                  var p = "{seite:" + page + "}";
   4:                  $.ajax({
   5:                      type: "POST",
   6:                      url: "WebForm19.aspx/getTable",
   7:                      contentType: "application/json; charset=utf-8",
   8:                      data: p,
   9:                      dataType: "json",
  10:                      success: function (result) {
  11:   
  12:                          $('#GridView1 tbody').append(result.d);
  13:                          page++;
  14:                      }
  15:                  });
  16:              }
  17:              var page = 1;
  18:   
  19:          </script>
  20:          <uc1:myGrid ID="myGrid1" runat="server" />
  21:          <a href="#" onclick="loadmeins()">mehr</a>

Das User Control das als Layout Container für das Gridview dient brauch noch ein wenig, ganz wenig, Code Logik um das Paging abzubilden und den Header zu unterdrücken.

   1:   Public Property pageNumber As Integer
   2:      Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
   3:          GridView1.HeaderRow.TableSection = TableRowSection.TableHeader
   4:      End Sub
   5:      Public Function loadData() As IQueryable(Of Customers)
   6:          If pageNumber > 0 Then
   7:              GridView1.ShowHeader = False
   8:          End If
   9:          Dim nw As New NorthwindEntities1
  10:          Dim query = nw.Customers.OrderBy(Function(c) c.Customer_ID).Skip(pageNumber * 5).Take(5)
  11:          Return query.AsQueryable
  12:      End Function

Am Ende sieht das genauso aus wie am Anfang geplant.

forwardpaging

Obwohl es mit relativ wenig Aufwand möglich war mit dem ASP.NET Gridview ein Vorwärts Blättern zu realisieren, fühlt es sich seltsam an. Auch das verpacken in JSon gefällt mir nicht. Darüberhinaus ist es für ein einfaches Szenario mit anderen Controls wie Repeater oder Listview einfacher eine saubere und passende HTML Tabelle zu rendern.

Wer Tipps hat was man besser machen kann, soll wie immer hier ein Kommentar hinterlassen.

Kommentare sind geschlossen