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.

3 Comments so far

  1. Andi on Juni 18th, 2008

    Hallo,
    danke das Sie sich die Mühe machen uns das Thema zu erklären. :-)

    Ich werde auf jeden Fall am Ball bleiben.

  2. haarrrgh on Juni 19th, 2008

    Hm…ehrlich gesagt, da hört’s bei mir schon auf. Ich kapiere nicht was diese Klasse machen soll und was sie mit Schichtentrennung zu tun hat.

    Und nu?

    Fehlt mir essentielles Grundwissen? Muß ich noch ein paar Stufen weiter unten anfangen?

    Oder soll ich einfach bis zum ersten “richtigen” Schichtentrennungs-Artikel warten und hoffen daß es dann klarer wird?

  3. Andi on Juni 21st, 2008

    Hi haarrrgh,
    soweit ich diesen Code verstanden hab hat das jetzt weniger mit der Schichtentrennung zu tun sonder ist ein geniale Art ein Programm richtig in OOP zu schreiben.

    Der Sinn dieser Klassen ist, andere Klassen die vom BaseManager erben, automatisch laden zu lassen. Beim Hinzufügen oder Löschen von Klassen muss ich dann nicht in jeder Klasse etwas ändern sonder man braucht dies nur in der web.config / app.config zu tun.
    So braucht man dann nicht jede Klasse anfassen wodurch die Wiederverwendbarkeit und Wartbarkeit einfacher wird.

    Um so ein Beispiel zu finden habe ich echt schon 2 Wochen lange im Internet ohne erfolg gesucht.
    Nochmals danke für dieses Beispiel.