Archive for Juni, 2008

WELTHERRSCHAFT: Boykottiert Spanien!

Achtung. In diesem Beitrag versteckt sich Ironie.

Jenau! Ab sofort keinen Spanienurlaub mehr, kein Malle, keine spanischen Weine, keine spanischen Tapas, keine spanischen Autos, keine spanischen Frauen! Ich verspreche, nie wieder Flamengo zu tanzen und niemals wieder auf Kreta Urlaub zu machen.

Och, Leute, es war nur Fussball. Nur ein Spiel. Mit Ballack. Der wird sowieso nie was Großes gewinnen. So ist es halt.

ASP.NET: Schichtentrennung – Implementierung

Das Schicke an der Schichtentrennung und den damit verbundenen Konzepten, ist, dass die Implementierung später gegen eine andere Implementierung ausgetauscht werden kann. Genau diesen Ansatz macht man sich zu Nutze, wenn man schnell Ergebnisse erzielen möchte, indem man zunächst eine einfache Implementierung erstellt und eben erst später die echte, große, datenbankgestützte Version bereit stellt.

Das vorausgeschickt, ist die folgende Implementierung nur als ein erster Ansatz zu sehen, den Sie durch eine Version ersetzen können, der Ihren eigenen Anforderungen besser entspricht. Die hier vorgestellte Implementierung nutzt nur den Speicher, um die angelegten Kunden vorzuhalten. Technisch macht sie nix anderes, als eine Liste von Kunden im Speicher zu halten. Einfach, aber für einen Test und einen ersten Prototypen sicherlich ausreichen.

Wichtig: Diese Komponente liegt in einem eigenen Projekt mit dem Namen MemoryCustomerManager (genau so wird der Name der Assemblierung heißen).

Der Code ist ziemlich selbsterklärend – hier werden schließlich nur Operationen auf einer Liste von Kunden vorgenommen. Damit alles später zusammen funktioniert, muss diese Implementierung somit lediglich von der Basisklasse CustomerManager erben und die benötigten Methoden implementieren:

using System;
using System.Collections.Generic;
using System.Text;
using BusinessLayer;

namespace MemoryCustomerManager
{
   ///

   /// Implementation of the business layer
   ///

   public class MemoryCustomerManager : CustomerManager
   {

      private static List _customers =
         new List
();

      ///

      /// List of customers
      ///

           
      private static List CustomersList
      {
         get { return _customers; }
      }  

      ///

      /// Returns all customers
      ///

      public override List GetAllCustomers()
      {
         // Sort
         Sort(CustomersList);

         // Return the customers
         return CustomersList;
      }

      ///

      /// Returns a specific customer
      ///

      public override Customer GetCustomer(Guid id)
      {
         // Check all customers
         foreach (Customer cust in CustomersList)
         {
            // Compare the id
            if (cust.Id.Equals(id))
            {
               // Found it!
               return cust;
            }
         }

         // Found nothing
         return null;
      }

      ///

      /// Finds all customers by their names
      ///

      public override List
         FindCustomersByName(string name)
      {
         List
customers =
            new List
();
         string nameLower = name.ToLower();

         // Check every customer
         foreach (Customer cust in CustomersList)
         {
            if (cust.LastName.ToLower()
               .Equals(nameLower))
            {
               customers.Add(cust);
            }
         }

         // Sort the customers
         Sort(customers);

         // Done
         return customers;
      }

      ///

      /// Finds all customers by their email-addresses
      ///

      public override List
         FindCustomersByEMail(string email)
      {
         List
customers =
            new List
();
         string emailLower = email.ToLower();

         // Check every customer
         foreach (Customer cust in CustomersList)
         {
            if (cust.EMail.ToLower().Equals(emailLower))
            {
               customers.Add(cust);
            }
         }

         // Sort the list
         Sort(customers);

         // Return the customers
         return customers;
      }

      ///

      /// Updates a customer
      ///

      public override Customer
         UpdateCustomer(Customer customer)
      {
         // Delete an existing customer
         DeleteCustomer(customer);

         // Add the customer
         CustomersList.Add(customer);

         return customer;
      }

      ///

      /// Deletes a customer
      ///

      public override bool
         DeleteCustomer(Customer customer)
      {
         // Check, whether the customer
         // exists in the list
         Customer existing = null;
         foreach (Customer cust in CustomersList)
         {
            if (cust.Id.Equals(customer.Id))
            {
               existing = cust;
               break;
            }
         }

         // Replace the old customer
         if (null != existing)
         {
            CustomersList.Remove(existing);
            return true;
         }

         return false;
      }

      ///

      /// Sort the customers
      ///

      private void Sort(List customers)
      {
         customers.Sort(new CustomerSorter());
      }
   }
}

Innerhalb der Klasse wird Bezug auf eine Klasse CustomerSorter genommen. Diese hat die Aufgabe, die Liste der Kunden stets alphabetisch sortiert zu halten. Wir implementieren dies mit Hilfe des Interfaces IComparer. Dabei ist lediglich die Methode Compare() zu überschreiben – und diese Überschreibung ist ziemlich simpel, denn tatsächlich geschieht nix anderes, als die Nachnamen der Kunden über deren Funktionalitäten miteinander zu vergleichen und das numerische Ergebnis dieses Vergleiches zurück zu geben. Dabei kann es zu folgenden Rückgaben kommen:

  • Zahl größer als 0: Der Wert der Instanz, auf der verglichen worden ist, ist größer.
  • 0: Beide Werte sind gleich.
  • Zahl kleiner als 0: Der Wert der Instanz, mit der verglichen worden ist, ist größer.

Dementsprechend kann das natürlich auch umgedreht werden, wenn man am Ende des Tages eine absteigende Sortierung wünscht. Wenn die Nachnamen gleich sind, werden die Vornamen miteinander verglichen. Sind auch die gleich, kommen die E-Mail-Adressen an die Reihe.

Lange Rede, kurzer Sinn: Dies ist der Code der CustomerSorter-Klasse:

using System;
using System.Collections.Generic;
using System.Text;
using BusinessLayer;

namespace MemoryCustomerManager
{
   public class CustomerSorter : IComparer
   {
      ///

      /// Compares two customers
      ///

      public int Compare(Customer x, Customer y)
      {
         int result = x.LastName.CompareTo(y.LastName);
        
         // Compare the first names when neccessary
         if (result == 0)
         {
            result = x.FirstName.CompareTo(y.FirstName);
         }

         // Compare the emails when neccessary
         if (result == 0)
         {
            result = x.EMail.CompareTo(y.EMail);
         }

         return result;
      }
   }
}

Damit ist die Implementierung komplett. Im nächsten Teil unserer Serie widmen wir uns dann der Nutzung dieser ganzen Komponenten im Web-Umfeld.

Teil 1. Teil 2. Teil 3.

ASP.NET: Schichtentrennung – Fassade

Schichtentrennung lebt vom Verbergen der Implementierung und davon, möglichst wenig an Informationen über das Erzeugen von Instanzen oder Abhängigkeiten nach draußen gelangen zu lassen. Das Grundprinzip sollte stets sein, so wenig wie möglich fest miteinander zu koppeln.

Aus diesem Grund bedienen wir uns einer Fassadenklasse, die das Instanzieren und Benutzen der Implementierung unserer Basisklasse verbirgt. Somit haben wir eine dedizierte Abgrenzung zur Frontendschicht geschaffen, was es uns im weiteren Verlauf des Lebenszyklus eines Projektes erleichtern würde, auch weitreichendere Änderungen zu implementieren (und sei es, die komplette Basisklasse gegen irgend eine andere Implementierung auszutauschen). Also, Fassade davor, und schon kann man auch mal was ändern, ohne das es weh tun muss.

So sieht die Fassadenklasse aus:

using System;
using System.Collections.Generic;
using System.Text;

using de.ksamaschke.Tools;


namespace BusinessLayer
{
   ///

   /// API for handling customers
   ///

   public class Customers
   {

      private static CustomerManager _manager;

      ///

      /// Reference to the
      /// CustomerManager-implementation
      ///

           
      private static CustomerManager Manager
      {
         get
         {
            // Get the manager
            if (null == _manager)
            {
               try
               {
                  _manager = ManagerLoader
                     .Load();
               }
               catch
               {
                  throw;
               }
            }

            return _manager;
         }
      }

      ///

      /// Returns a list of all customers
      ///

      public static List GetAllCustomers()
      {
         return Manager.GetAllCustomers();
      }

      ///

      /// Returns a specific customer
      ///

      public static Customer GetCustomer(Guid id)
      {
         return Manager.GetCustomer(id);
      }

      ///

      /// Returns a list of customers
      /// identified by their names
      ///

      public static List
         FindCustomersByName(string name)
      {
         return Manager.FindCustomersByName(name);
      }

      ///

      /// Returns a list of customers identified
      /// by their email-addresses
      ///

      public static List
         FindCustomersByEMail(string email)
      {
         return Manager.FindCustomersByEMail(email);
      }

      ///

      /// Updates a customer
      ///

      public static Customer
         UpdateCustomer(Customer customer)
      {
         return Manager.UpdateCustomer(customer);
      }

      ///

      /// Deletes a customer
      ///

      public static bool
         DeleteCustomer(Customer customer)
      {
         return Manager.DeleteCustomer(customer);
      }
   }
}

Diese Fassade ist absichtlich sehr übersichtlich gehalten. Im Wesentlichen stellt sie nur die gleichen Funktionalitäten wie die intern verwendete CustomerManager-Klasse dar. Die konkret verwendete Instanz wird über die Eigenschaft Manager abgerufen, wo sie in Form eines Singletons gehalten wird – somit gibt es nur eine Instanz und die Instanz muss nicht bei jedem Zugriff neu erzeugt werden.

Im nächsten Teil widmen wir uns der konkreten Implementierung einer CustomerManager-Ableitung.

Hier geht es zu Teil 1 und Teil 2.

WELTHERRSCHAFT: Guus vs. Heimat – 3:1

Und alle zusammen:

“Ohne Holland spiel’n wir die EM, spiel’n wir die EM, spiel’n wir die EM”!

Do swidanija, dorogie gollandskie drusja.

ASP.NET: Schichtentrennung – Basisklasse und Überlegungen

Bevor wir nun tatsächlich auf das Thema Schichtentrennung intensiver zu sprechen kommen, hier ein paar grundlegende Überlegungen zum Sinn und Zweck von Schichtentrennungen:

Viele Applikationen, leider auch sehr viele Webapplikationen, sind monolithisch aufgebaut. Das bedeutet, dass alles im Frontend stattfindet und die einzelnen Komponenten des Frontends fest miteinander verdrahtet sind. Das führt jedoch zu ein paar grundsätzlichen Problemen:

  • Einzelne Komponenten können nicht gegeneinander ausgetauscht werden
  • Komponenten müssen sehr viel voneinander wissen, um überhaupt sinnvoll interagieren zu können
  • Die Wartbarkeit fällt hinten herunter, denn der selbe Code findet sich an verschiedenen Stellen im Frontend wieder (Redundanz)
  • Änderungen werden zu Geduldsspielen, da die Änderungen an vielen Stellen vorgenommen werden müssen und es Abhängigkeiten gibt
  • Die Testbarkeit einzelner Funktionalitäten ist nicht gegeben

Es gibt potentiell dutzende weiterer Probleme.

Mit all diesen Problemen möchte die Schichtentrennung und die Kapselung von Funktionalitäten aufräumen. Hier geht es in erster Linie einmal darum, verschiedene Ebenen einer Applikation zu identifizieren:

  • Frontend (Webseite, Windows-Client, Fassade, Webdienst, …)
  • Geschäftslogik (Implementierung von Geschäftsprozessen, Verbergung von deren Details vor der Frontend-Logik)
  • Infrastruktur- / Datenlogik (Kapselung des Zugriffs auf Daten)

Dabei gibt es die Regel, dass die einzelnen Schichten stets nur über genau definierte Kommunikationspfade miteinander sprechen:

  • Das Frontend spricht mit der Geschäftslogik
  • Die Geschäftslogik spricht mit der Infrastruktur
  • Die Infrastruktur spricht mit der Geschäftslogik
  • Die Geschäftslogik spricht mit der Infrastruktur
  • Die Infrastruktur spricht nicht direkt mit dem Frontend!
  • Das Frontend spricht nicht direkt mit der Infrastruktur!

Diese genau definierten Kommunikationspfade müssen herausgearbeitet werden. Zu diesem Zweck muss man sich Gedanken über die Abbildung von Geschäftsprozessen (also großflächigeren Vorgängen, etwa einer Benutzerregistrierung samt aller Bedingungen) und Use-Cases (also kleinere, atomarere Schritte, etwa das Abfragen, ob ein Benutzer bereits im System existiert) machen. Geschäftsprozesse bestehen dabei in der Regel aus mehreren Schritten, Use Cases stellen üblicherweise kleinere Schritte dar. Beide können auf Ebene der Geschäftslogik implementiert sein, wobei die Geschäftsprozesse üblicherweise von außen ansprechbar sind und ihrerseits intern die verschiedenen kleinen Schritte (in Methodenform) umsetzen bzw. aufrufen. Als Regel sollte hier gelten: Je weniger ein Client (also eine Komponente, die eine Funktionalität nutzt) von der internen Implementierung einer Funktionalität weiß, desto besser ist es!

Aus diesem Grund wird die Geschäftslogik üblicherweise in Form einer Fassade definiert, die ihrerseits intern die verschiedenen Funktionalitäten (Use Cases) aufruft bzw. auslöst. Diese Fassade sollte aber so gestaltet sein, dass die interne Implementierung auch wieder gelöst werden kann (üblicherweise geschieht dies in Form einer Factory oder eines Providers). Das bedeutet für uns: Es muss eine Basisklasse oder ein Interface implementiert werden, das seinerseits alle notwendigen Funktionalitäten definiert und später implementiert werden kann.

Dies soll im Folgenden anhand einer kleinen Kundenverwaltung demonstriert werden, die ein paar rudimentäre Funktionalitäten (anlegen, ändern, löschen, zurückgeben) definiert. Diese Definition findet anhand einer abstrakten Basisklasse statt. Dieser Entwurfsansatz nennt sich Contract-First-Design, da zunächst der Vertrag (die Basisklasse) definiert wird und erst anschließend die Implementierung dieser Klasse stattfinden kann und muss.

Die Basisklasse sieht dabei wie folgt aus:

using System;
using System.Collections.Generic;
using System.Text;
using de.ksamaschke.Tools;

namespace BusinessLayer
{
   ///

   /// Defines methods for the handling of customers
   ///

   public abstract class CustomerManager : BaseManager
   {
      ///

      /// Returns a list of all customers
      ///

      public abstract List GetAllCustomers();

      ///

      /// Returns a specific customer
      ///

      public abstract Customer GetCustomer(Guid id);

      ///

      /// Returns a list of customers identified
      /// by their names
      ///

      public abstract List
         FindCustomersByName(string name);

      ///

      /// Returns a list of customers identified
      /// by their email-addresses
      ///

      public abstract List
         FindCustomersByEMail(string email);

      ///

      /// Updates a customer
      ///

      public abstract Customer
         UpdateCustomer(Customer customer);

      ///

      /// Deletes a customer
      ///

      public abstract bool
         DeleteCustomer(Customer customer);
   }
}

Verwendet wird hier auch eine Customer-Klasse, die die einzelnen Datensätze repräsentiert:

using System;
using System.Collections.Generic;
using System.Text;

namespace BusinessLayer
{
   public class Customer
   {

      private Guid _id = Guid.NewGuid();

      ///

      /// Id of the customer
      ///

           
      public Guid Id
      {
         get { return _id; }
         set { _id = value; }
      }

      private string _firstname;

      ///

      /// First name of the customer
      ///

           
      public string FirstName
      {
         get { return _firstname; }
         set { _firstname = value; }
      }

      private string _lastname;

      ///

      /// Last name of the customer
      ///

           
      public string LastName
      {
         get { return _lastname; }
         set { _lastname = value; }
      }

      private string _email;

      ///

      /// E-Mail-Address of the customer
      ///

           
      public string EMail
      {
         get { return _email; }
         set { _email = value; }
      }  
   }
}

Im nächsten Teil der Serie zeige ich dann, wie die Fassadenklasse aufgebaut und implementiert sein kann. Erst danach widmen wir uns einer konkreten Implementierung der obigen Basisklasse und dem Einsatz in einem Beispielprojekt.

Zum 1. Teil. Zum 3. Teil.

WELTHERRSCHAFT: JAAAAAAAAAAAAAAAAAH!

3:2.

JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH!

ASP.NET: Schichtentrennung – Hilfskomponente

So, auf vielfachen Wunsch: Eine kleine Serie zum Thema Schichtentrennung. Bevor wir aber anfangen können, mit Schichten zu arbeiten, benötigen wir einen Mechanismus, der es uns erlaubt, Komponenten anhand von Konfigurationseinstellungen zu laden. Das nutzen wir später, um diverse Entwurfsmuster brauchbar implementieren zu können.

Damit Komponenten geladen werden können, definieren wir zunächst eine Basisklasse BaseManager. Diese Basisklasse verfügt über eine überschreibbare Methode Init(), die von ableitenden Klassen genutzt werden kann, um sich zu initialisieren. Wir lagern also diese spezielle und individuelle Logik auf die einzelnen nachzuladenden Komponenten aus. Die Nutzung dieser Basisklasse ist jedoch optional – Komponenten sollten sich stets auch laden lassen, ohne das diese Basisklasse erweitert wird:

using System;

namespace de.ksamaschke.tools
{
   ///

   /// Base manager implementation
   ///

   public abstract class BaseManager
   {
      ///

      /// Initializes the component
      ///

      public virtual void Init()
      {
         // Intentionally left blank
      }
   }
}

Im nächsten Schritt wird der Lademechanismus definiert. Dieser ist wiederverwendbar gestaltet – er sollte also in jedem Kontext funktionieren können. Somit gibt es hier keine festen Verdrahtungen mit irgendwelchen Klassen, sondern die Instanzen werden per Reflection erzeugt.

Wie aber kommen wir an die benötigten Informationen (Klassenname, Assembly-Name)?

Diese liegen in der Konfigurationsdatei der Applikation (je nach Einsatzzweck entweder die web.config oder die app.config für Client-Applikationen). Der Einfachheit halber tragen wir die Typinformationen im appSettings-Bereich ein (in meinen Projekten gibt es hier üblicherweise einen eigenen Konfigurationsbereich je Komponente, aber das würde hier den Rahmen sprengen). Gefunden werden diese Typinformationen über den Typnamen der beim Aufruf der Methode Load() angegebenen Basiskomponente. Damit schlagen wir gleich zwei Fliegen mit einer Klappe: Das Ding wird wiederverwendbar, da ich keine feste Verdrahtung mit irgendwelchen Klassen habe und wir zwingen uns selbst zum so genannten Contract First-Design, bei dem wir zunächst eine Basisklasse und erst später konkrete Implementierungen bereit stellen.

Der Lademechanismus befindet sich in der Klasse ManagerLoader. Hier werden zunächst die Informationen aus der Konfigurationsdatei ausgelesen und anschließend wird versucht, die Komponente zu instanzieren. War dies erfolgreich, wird geprüft, ob die Komponente von der Basisklasse BaseManager erbt und wenn dem so ist, wird die Init()-Methode eingebunden:

using System;
using System.Configuration;
using System.Runtime.Remoting;

namespace de.ksamaschke.tools
{
   ///

   /// Loads a manager
   ///

   public class ManagerLoader
   {
      ///

      /// Loads a specific manager
      ///

      public static T Load()
      {
         // Get the name of the settings-area
         string settingsName = typeof(T).Name;
        
         // Get the config-value
         string nameAndAssembly =
            ConfigurationManager.AppSettings[
            settingsName];

         // Check the config-value
         if (!String.IsNullOrEmpty(nameAndAssembly))
         {
            // Get the type-name
            string typeName =
               nameAndAssembly.Substring(0,
                  nameAndAssembly.IndexOf(“,”));

            // Get the assembly-name
            string assemblyName =
               nameAndAssembly.Substring(
               nameAndAssembly.IndexOf(“,”) + 1);

            // Try to create an instance
            try
            {
               // Get the instance
               ObjectHandle handle =
                  Activator.CreateInstance(
                     assemblyName, typeName);

               if (null != handle)
               {
                  object result = handle.Unwrap();

                  // Try to initialize
                  if (null != result &&
                      result is BaseManager)
                  {
                     ((BaseManager)result).Init();
                  }

                  if (result is T)
                  {
                     return  (T) result;
                  }
               }
            }
            catch
            {
               // Intentionally left blank
            }
         }
        
         throw new Exception(
            string.Format(
               “Unable to load manager for type {0}”,
               settingsName));
      }
   }
}

Um später mit dieser Klasse Komponenten laden zu können, müssen Sie in der Konfigurationsdatei einen Eintrag anlegen, der dieses Format hat:


  
              value=”Managers.MemoryCustomerManager,
                  CustomerManagerImplementation” />

(Ohne den Zeilenumbruch innerhalb des -Elements)

Damit haben wir die Grundvoraussetzungen geschaffen, um später mit Schichtentrennungen arbeiten zu können. Sie sollten diese Klassen in einem eigenen C#-Projekt ablegen, so dass Sie sie später immer wieder verwenden können.

Zum 2. Teil. Zum 3. Teil.

ASP.NET: Ja, was wollt ihr denn?

Mit meinem letzten Post scheine ich ja einen wunden Punkt getroffen zu haben. Damit der etwas weniger wund wird, bin ich gerne bereit, praxisnahe Beispiele zu entwickeln – aber blind machen? Nö, nix ist.

Also, was wollt ihr? Welche Themen sollen mal ausführlicher behandelt werden? Was muss unbedingt mal erläutert werden? Und wer würde mir dabei ggf. helfen?

Gebt mal Feedback!

ASP.NET: Handarbeit statt DataSource!

Achtung: Polemik, Zuspitzung, Gemeinheit!

Das ASP.NET-Framework bietet diverse datengebundene Controls an, die der Visualisierung von Listen dienen. Die bekanntesten sind Repeater, DataGrid und GridView, gefolgt von einigen Exoten, etwa der DataList. Dazu gibt es wundervolle DataSource-Steuerelemente, die es erlauben, komplett ohne eine einzige Zeile an .NET-Code, Datenbanken an diese Steuerelemente zu binden.

Wenn man sich nun den Spaß macht, mal durch die Foren zu hirschen, dann bekommt man das kalte Grausen, denn die Kombination DataSource-Steuerelement + GridView-Steuerelement beherrscht seit .NET 2.0 quasi die Programmierung im ASP.NET-Umfeld. Was auch letztlich kein Wunder ist, denn sie sind idiotensicher einzusetzen und bringen Ergebnisse in wenigen Sekunden. Mit dabei sind dann gleich Features, wie etwa Paging und Sorting. Geil.

Nur leider ist es totaler Mumpitz und absoluter Schwachsinn, diese Controls extensiv und in Kombination miteinander einzusetzen. Denn sie räumen gründlich auf mit allem, was gemeinhin als Schichtentrennung, Wartbarkeit, Skalierbarkeit oder Qualität bekannt ist.

Die Gründe liegen auf der Hand:

Schichtentrennung ist nicht mehr gegeben, da der Code zum Abrufen von Daten – samt SQL-Statements! – speziell bei Verwendung vom SqlDataSource-Steuerelement in der WebForm selbst liegt. Statt also eine dedizierte Geschäftslogik damit zu beauftragen, Daten zu laden (zu cachen, aufzubereiten, zu analysieren, …), wird hier direkt auf die Datenquelle zugegriffen. Die komplette Verarbeitungslogik wandert dann in irgendwelche Ereignisbehandlungsmethoden im Code-Bereich der WebForm, statt in einer eigenen Verarbeitungsschicht zu liegen, wo sie ggf. auch gegen andere Implementierungen, Weiterentwicklungen oder sonstwas ausgetauscht werden könnte. Einzige akzeptierbare Ausnahme hier: Das ObjectDataSource-Steuerelement, das es erlaubt, gegen statische Methoden von Geschäftsobjekten zu arbeiten. Doof nur, dass mir dann dabei quasi vorgegeben wird, dass ich eben eine statische Methode brauche. Was ist mit meiner Factory (Provider, Builder, Broker, …), die ich ggf. habe?

Wartbarkeit? Gibts nicht mehr. Wie auch? Ein Beispiel: Es gibt zwanzig WebForms in einer Applikation, die auf diese Art arbeiten – also GridView + DataSource-Steuerelement. Jetzt wird das Datenmodell geändert, was im Rahmen von Weiterentwicklungen durchaus vorkommen soll. Das Ergebnis? Ich darf an mindestens zwanzig WebForms Änderungen vornehmen, statt es an nur einer Stelle in meiner Geschäfts- oder Datenlogik zu haben. Klasse. Nicht zu reden von der quasi nicht mehr vorhandenen Lesbarkeit des Codes. Neee, Wartbarkeit gibt es damit nicht.

Die Skalierbarkeit verschwindet ebenfalls hinter dem Horizont, denn ich habe schlicht keine brauchbare Möglichkeit mehr, in Datenhaltungs- und Caching-Prozesse einzugreifen. Geht nicht, ist ja alles inline. Klar, die SqlDataSource kann auch cachen – aber das kann ich im Code über das Application Data Caching viel zielgerichteter und genauer umsetzen. Wie also soll es skalieren? Zumal ich ja nicht mal in der Lage bin, einfach per Konfiguration etwa auf eine Web-Service-basierende Lösung umzustellen – ich müsste es halt überall ändern, wo ich auf die Daten zugreife. Oder eben eine andere Implementierung der Geschäfts- oder Datenhaltungslogik zu verwenden. Klar, es gibt die ObjectDataSource, aber… siehe oben. Dazu kommt: DataGrid und GridView sind zwar mächtig, aber eben auch langsam, denn die ganzen Funktionalitäten müssen ja irgendwo eingebunden werden. Auch, wenn man sie unter Umständen nicht benötigt. Und Tschüss, Skalierbarkeit!

Qualität? ROTFL! Wie denn, wo denn? Wie soll denn mit solch einem Gewurschtel, mit nicht mehr vorhandenen anerkannten Entwurfs- und Design-Patterns noch sowas wie Qualität erzeugt werden? Wenn man auch nur einen kurzen Moment mal nicht nur an die persönliche Bequemlichkeit denkt, dann muss man eigentlich zwangsläufig erkennen, dass man so ganz sicher alles baut, aber keine qualitativ hochwertige Software.

Die Lösung?

Back to the roots! Wenn datengebundene Listensteuerelemente, dann einen Repeater, keine komplett überdimensionierten GridViews und DataGrids. Und aus dem Code heraus die Daten binden. Kontrolle zurück gewinnen! Weiß denn eigentlich überhaupt noch jemand, wie man händisch die Daten binden lässt? Nein? Na, dann hier ein wenig Beispielcode:

protected override void OnPreRender(EventArgs e)
{
   // Factory für den Business-Layer
   BusinessLayer bl = BusinessLayer.GetInstance();

   // Personen laden
   IList persons = bl.GetPersons();

   // Personen binden
   rptPersons.DataSource = persons;

   // Datenbindung
   DataBind();
}

Oh je, sieben Zeilen Code! Wie schrecklich!

Leute, seid mir nicht böse, aber von nix kommt auch nix. Sicher, ich spitze hier zu, aber Webseiten entwickeln hat eben tatsächlich was mit Entwicklung, mit Code, mit Abläufen, mit Architektur, mit Denken zu tun. Ihr müsst das nicht machen, keine Frage – aber genau so werden dann eure Ergebnisse aussehen: Zusammengestückelt, unprofessionell, langsam, nicht pflegbar. You get, what you pay for – kein Einsatz, kein Ergebnis.

Zu den Themen Paging, Sorting und Caching dann ein anderes Mal. Google hilft sicher bis dahin weiter – Stichworte: PagedDataSource, IComparer und Cache.

Update: Ihr wollt, dass man mal was über bestimmte Themen schreibt? Dann sagt es mir.