Migrating ASP.NET View nach 4.5

In einer rund 8 Jahre alten Web Anwendung muss eine einzelne Page modernisiert werden. Die Webanwendung ist eine Website, also kann man direkt den Code im Code Behind ändern ohne den Rest der Anwendung zu zerstören. Der damaligen Auffassung von OOP folgend wurde mehrfach, teils inhaltslos, gekapselt (DLL, BLL). Die Datenlogik befindet sich in Stored Procedures, die in diesem Fall mindestens zwei Tabellen denormalisiert in einem ADO.NET Dataset bzw. Datatable durch die Schichten reicht. In der ASPX Page wurde ein Repeater eingesetzt, der mit Verzweigungslogik Header ein ausblendet. Im Codebehind befinden sich rund 180 Zeilen Eventlogik und Validierungscode, bzw. die Instanziierung der BLL.

Ein klassischer Fall von Brownfield Projekt, in dem die Veränderung  nur eines Feldes tagelange Arbeit nach sich ziehen kann. Lässt der Entwickler sich dazu hinreißen auch gleich aufzuräumen, werden schnell Monate daraus.

Die Ziele sind modernes UI (zb mit Filter) und so wenig wie möglich Aufwand.

Datasets sind obsolet

Mit .net 4.5 existiert ein LINQ 2 Dataset Provider. Um die 1:N Darstellung zu realisieren, wird mit LINQ die Sicht wieder gesplittet in Master und Client Daten. Da Dataset nicht streng typisiert sind, muss man die Felder über eine “Field” und “Feldname” Methode auslesen. Es existiert eine verkürzte Syntax in VB mit dem !, die hier im VB.NET Code zur Anwendung kommt.

Startpunkt ist die Extension Methode asEnumarable, die Datarow Linq typisch liefert. In diesem Fall wird mit anonymen Strukturen gearbeitet, so das man mit “Key” Schlüsselwort in Verbindung mit Distinct nur die einmaligen Datensätze bekommt. Sozusagen die Masteransicht.

   1:  Dim query = From sem In ds.Tables(0).AsEnumerable
   2:                      
   3:                    Order By If(rdDatum.Checked, sem!Termin, sem!Title)
   4:              Select New With {Key .SEDETID = sem!SEDETID,
   5:                            .Title = sem!Title,
   6:                           .Ort = sem!Ort,
   7:                           .Dauer = sem!Dauer,
   8:                           .Termin = sem!Termin,
   9:                           .Name = sem!Name,
  10:                           .Unterlagen = sem!Unterlagen
  11:                          } Distinct

Repeater sind praktisch

Der HTML Part wurde von mir nahezu komplett gelöscht. Ziel war es klare UL und LI Strukturen im DOM zu bekommen, um diese einfach manipulieren zu können. So wurde ein Client Filter per JavaScript eingebaut. Außerdem kann man auch die LINQ Query am Server beeinflussen. Das ist eine wunderbare neue Funktion von Webforms Model Binding in ASP.NET 4.5. Der Radiobutton wird im obigen LINQ Statement berücksichtigt und beeinflusst die Sortierung.

Der erste Teil der ASPX Seite

   1:     filtern<input id="searchbox1" placeholder="Tippen" />
   2:          <asp:RadioButton ID="rdDatum" runat="server" GroupName="eins" Text="Datum" 
AutoPostBack="true" />
   3:          <asp:RadioButton ID="rdThema" runat="server" GroupName="eins" Text="Thema" 
Checked="true" AutoPostBack="true" />
   4:   
   5:         <ul class="navlist">
   6:              <asp:Repeater ID="rpSemBooked" runat="server" SelectMethod="rpSemBooked_GetData">

Die Methode in der Page “rp_SemBooked_GetData” enthält dann eigentlich nur das LINQ Statement und eine passende Signatur.

   1:  Public Function rpSemBooked_GetData() As System.Collections.IEnumerable
   2:         Dim query = From sem In ds.Tables(0).AsEnumerable
   3:         ...
   4:      Return query

Das finde ich nur cool. Einfach klar und sogar noch mit eine wenig Designer Support. Steht einem ASP.NET MVC Ansatz in kaum was nach.

Master Detail oder Nested Repeaters

Repeater lassen sich einfach und beliebig tief verschachteln. Mein Vorgänger hat sich dafür die Databinding Events gekrallt um dem Client Repeater jeweils seine neue Bindung zuzuweisen auf die 1:N.

Ich habe mich entschieden ein Hidden Field als Träger des Keys im Master Repeater unterzubringen.

   1:  <ul>
   2:  <asp:Repeater ID="rpSemBooked" runat="server" SelectMethod="rpSemBooked_GetData">
   3:     <ItemTemplate>
   4:          <asp:HiddenField runat="server" ID="hID" Value='<%#Eval("SEDETID")%> />
   5:              <ul>
   6:                <h3><%#Eval("Title")%> </h3>
   7:          .....
   8:                 <asp:Repeater ID="rpTln" runat="server" SelectMethod="rpTln_GetData">
   9:                     <ItemTemplate>
  10:                          <li>

In der Select Methode des Client Repeaters lässt sich mit einem simplen Attribut der ID Wert auslesen.

   1:  Public Function rpTln_GetData(<Control()> hID As String) As System.Collections.IEnumerable
   2:          Dim query = From sem In ds.Tables(0).AsEnumerable
   3:              Where sem!SEDETID = hID ...

Es gibt rund 10 mögliche Attribute für ID Quellen. Wichtig ist sicher Querystring, RouteData oder auch Session.

Nur der Vollständigkeit halber der JavaScript code (mit ein wenig JQuery) um im Browser zu filtern.

   1:     $('#searchbox1').keyup(function () {
   2:   
   3:              var valThis = $(this).val().toLowerCase();
   4:              $('.navlist>ul').each(function () {
   5:                  var text = $(this).text().toLowerCase();
   6:                  (text.indexOf(valThis) >= 0) ? $(this).show() : $(this).hide();
   7:              });
   8:          });

Dauer des Projekts waren rund 2h. Das Risiko sehr gering. Die Listendarstellung ist nun wesentlich schneller und bietet zwei neue Filter Funktionen am Webserver und am Client. Am Ende hat sich der benötigte VB.NET Programmiercode und der deklarative ASP.NET Teil fast halbiert.

Kommentare sind geschlossen