Anmelden
Ich möchte für die nächsten 30 Tagen angemeldet bleiben
Deutsch
Several pages in the usergroup are available in English. Click on english to visit these pages.

DotNetNuke News

21.03.2008
DNN-Modulprogrammierung - ein einfaches Beispiel (Schritt 3 - Die Die Geschäftsobjekte) (Michael Tobisch)
Nachdem wir im 2. Schritt Methoden implementiert haben, die den Zugriff auf unsere Daten steuern, kommen wir nun zur nächsten Schicht unserer Anwendung. Die Geschäftsobjekte dienen in erster Linie zum einen dazu, der Präsentationsschicht einen Zugriff auf die Datenobjekte zu bieten.

Diese Proxy-Funktion ist aber nicht alles, was hier liegt bzw. liegen kann aoder soll: So sind die Geschäftsobjekte auch der richtige Platz für die Implementierung von Geschäftsregeln - die Schicht nennt sich auch Geschäftslogik bzw. auf neudeutsch Business Logic Layer.

Stellen wir uns folgendes Szenario vor: Wir wollen, dass Kontakte nicht erfasst werden dürfen, wenn die Domain der E-Mail-Adresse die Domain eines Gratis-Webmail-Anbieters ist, also z.B. "hotmail.com", "gmx.net" oder "web.de". Auch Änderungen bestehender Kontakte sollen nicht erlaubt sein, wenn damit eine Änderung der E-Mail-Adresse auf eine Adresse aus diesen Domains mit sich führt. Wir könnten das über einen Trigger (eine Prozedur im SQL-Server, die automatisch ausgeführt wird, wenn ein Datensatz hinzugefügt, geändert oder gelöscht wird) in der Datenbank lösen, keine Frage, aber das ist sehr tief unten, und Trigger dienen eigentlich dazu, Regeln der referentiellen Integrität zu wahren, und nicht dazu, Geschäftsregeln zu implementieren - vielleicht wollen wir das Modul ja einmal verkaufen, und der Käufer hat ganz andere Regeln, z.B., dass E-Mail-Adressen aus seiner eigenen Domain nicht erlaubt sein sollen.

Sinnvollerweise würden wir in diesem Fall eine Tabelle anlegen, welche die verbotenen Domains enthält, und hier (in den Geschäftsregeln) überprüfen, ob der anzulegende oder zu ändernde Kontakt eine erlaubte E-Mail-Adresse führt oder nicht. Ist die Tabelle leer, so sind natürlich alle Adressen erlaubt. Damit gibt man dem Kunden ein Werkzeug in die Hand, welches ihm erlaubt, eigene Regeln zu definieren. (Ich weiß schon, das Beispiel ist jetzt vielleicht blöd, aber es ist eben nur ein Beispiel, und bei den Daten, die wir hier haben fällt mir nicht viel an Möglichkeiten ein - wir werden es aber dennoch zu einem späteren Zeitpunkt implementieren, damit man hier sieht, wie so etwas sinnvoll von Statten geht).

Voerst werden wir aber keine Regeln definieren, um unser Beispiel einfach zu halten.

Im Großen und Ganzen bestehen die Geschäftsobjekte aus zwei Klassen:

  1. Die Info-Klasse stellt die Eigenschaften des Objekts zur Verfügung. Diese entsprechen im wesentlichen den Feldern der zugrunde liegenden Tabelle.
  2. Die Controller-Klasse dient der Zugriffsteuerung auf die Methoden des Datenzugriffslayers. Sie ist sozusagen ein "Proxy" zwischen Datenzugriffslayer und Info-Klasse (solange nicht zusätzlich Geschäftsregeln implementiert werden).

Die Info-Klasse (C#)

Alle Quellcodes für die C#-Variante können hier heruntergeladen und müssen nicht abgetippt werden.

Die Info-Klasse stellt die Eigenschaften des Objekts zur Verfügung. Wir müssen hier nichts weiter machen, als für jedes Feld unserer Tabelle eine Eigenschaft zu definieren. Klicken wir (in Visual Studio) also wieder mit der rechten Maustaste auf unseren Ordner DnnUgDe_Contacts (im Ordner App_Code), und dann im Kontextmenü auf "Neues Element hinzufügen…". Im folgenden Dialog geben wir folgendes ein:

ContactInfo.cs

Wir markieren wieder Klasse, wählen als Sprache C# und nennen die Klasse ContactInfo.cs. Dann klicken wir auf Hinzufügen, um den Vorgang anzuschließen.

Zuerst verpacken wir die Klasse wieder in einen Namespace, einen Konstruktor benötigen wir, aber keine Logik, wie dieser entsteht. Wir lassen diesen Teil also leer. Der Code sieht nun aus wie folgt:

namespace DnnUgDe.DNN.Modules.CS.Contacts.Business
{
   public class ContactInfo
   {
      public ContactInfo()
      {
      }
   }
}

Nun definieren wir für jedes Feld der Tabelle eine Eigenschaft der Klasse. Diese Eigenschaften sind sowohl les- als auch beschreibbar, das heißt, wir benötigen für jede Eigenschaft eine private Variable (die den Wert der Eigenschaft enthält), einen (öffentlichen) get-Accessor (der den Wert der Eigenschaft zurückgibt) und einen set-Accessor (der den Wert der Eigenschaft setzt).

Properties sind etwas sehr einfaches, und um diese Einfachheit noch abzukürzen können wir auch einen Codeausschnitt verwenden. Das funktioniert so, dass man den Namen des Codeausschnitte in den Code tippt und dann die Tabulator-Taste drückt - und der Code wird automatisch eingefügt.

Leider wurde in Visual Studio 2008 der von mir oft verwendete Code-Ausschnitt "prop" so verändert, dass man auf den Code des privaten Mitglieds verzichtet hat, und die set- und get-Accessors auf ein denkbares Minimum reduziert hat. Ich habe daher einen Codeausschnitt hergestellt, der dem Verhalten von Visual Studio 2005 entspricht, und den ich "propm" (Poperty mit Member) genannt habe. Er steht hier zum Download bereit. Wird Visual Studio 2005 verwendet, so kann man den Codeausschnitt "prop" verwenden, er funktioniert ganz gleich.

Um den Codeausschnitt zu importieren wählen wir im Menü Extras die Option Codeausschnitts-Manager:

Codeausschnitts-Manager (Menü Extras)
Im folgenden Dialog wählen wir zunächst unter Sprache Visual C#, und klicken dann auf importieren:
Codeausschnitts-Manager
Navigieren wir nun zu dem Ordner, in dem die Datei propm.snippet liegt, markieren diese und klicken auf Öffnen:
Snippet auswählen

Im folgenden Dialog wählen wir "Visual C#" als Speicherort und klicken dann auf Fertigstellen:

Codeausschnitt importieren

Zum Abschluss klicken wir noch auf OK.

Geben wir im Code (innerhalb der Klasse) nun propm ein (in Visual Studio 2005 prop) und drücken dann auf die Tabulatortaste. Der Code sieht dann wie folgt aus:

Wir wollen nun eine Eigenschaft ModuleID erzeugen, die vom Typ Integer ist. int steht ja schon als Typ da, wir drücken also einfach auf die Tabulator-Taste, um ins nächste Feld zu kommen. Benennen wir das private Mitglied "moduleID", in dem wir dieses Wort einfach eintippen (im Gegensatz zu Visual Basic unterscheidet C# ja zwischen Groß- und Kleinschreibung, wir könnnen also unsere öffentliche Eigenschaft "ModuleID" nennen, ohne dass es hier zu Konflikten kommt. In Visual Basic müssten wir das private Mitglied z.B. "_ModuleID" nennen):
Property - privater Member
Drücken wir nochmal auf die Tabulator-Taste. Wir sehen, dass in den get- und set-Accessors der Name des privaten Mitglieds ersetzt wurde:
Property - privater Member in den Accessors ersetzt
Nun tippen wir einfach noch den Namen der Eigenschaft, also "ModuleID" ein, drücken die ESC-Taste und fertig ist unsere EigenschaftProperty ModuleID
Auf diese Weise legen wir auch noch die restlichen Eigenschaften an:

  • ContactID (int)
  • Firstname (string)
  • Lastname (string)
  • EmailAddress (string)

Bei Eigenschaften, die nicht vom Typ Integer sind, ist (bevor man nach dem Einfügen des Snippets die Tabulator-Taste betätigt) das gelieferte "int" durch den jeweiligen Typ zu überschreiben (im Fall Firstname, Lastname und EmailAddress also durch "string").

Unser Code sieht nun wie folgt aus:

      private int moduleID;

      public int ModuleID
      {
         get { return moduleID; }
         set { moduleID = value; }
      }

      private int contactID;

      public int ContactID
      {
         get { return contactID; }
         set { contactID = value; }
      }

      private string firstname;

      public string Firstname
      {
         get { return firstname; }
         set { firstname = value; }
      }

      private string lastname;

      public string Lastname
      {
         get { return lastname; }
         set { lastname = value; }
      }

      private string emailAddress;

      public string EmailAddress
      {
         get { return emailAddress; }
         set { emailAddress = value; }
      }

Damit wären wir eigentlich fertig - aber aus verschiedenen Gründen empfiehlt sich noch etwas zusätzlicher Aufwand. Mit der Version 04.06.00 wurde im DotNetNuke-Framework eine neue Schnittstelle eingeführt, die ein Info-Objekt aus einem DataReader hydriert. Diese Schnittstelle nennt sich IHydratable, und spätestens mit Version 04.08.00 ist es absolut notwendig, diese zu implementieren - möglicherweise wegen eines Bugs im Framework, aber so genau kann ich das nicht sagen. Bis Version 04.07.00 hat es jedenfalls ohne auch funktioniert.

Dazu muss man etwas ausholen. Bis Version 04.04.xx wurde zum Hydrieren eines Objekts die Reflection-Klasse verwendet, indem man die Methoden FillCollection bzw. FillObject aus der CBO-Klasse eingesetzt hat. Diese Methoden waren einfach anzusprechen, aber Reflection ist nicht gerade die schnellstmögliche Variante. In Version 04.04.00 wurde begonnen, eigene Hydrier-Funktionen einzubauen (man kann sich das ja z.B. im Quellcode der PortalController-Klasse ansehen, wenn man will). Das Problem dabei war, dass man sich dabei auch um den DataReader kümmern musste - dieser konnte z.B. geschlossen werden, nachdem er hydriert wurde, aber die darunterliegende Datenbankverbindung blieb eventuell geöffnet.

IHydratable ermöglicht, einerseits die Objekte selbst (und rasch) zu hydrieren, andererseits kümmert sich CBO um den DataReader und die Connection. Man hat also deutlich weniger Aufwand. Eine genaue Dokumentation kann man im Blog von Charles Nurse nachlesen: 4.6.0 A Sneak Peek (4) - IHydratable part 1 bzw. 4.6.0 A Sneak Peek (4) - IHydratable part 2.

IHydratable besteht aus einer Eigenschaft (KeyID) und einer Methode (Fill). Diese sind zu implementieren. Zuerst aber müssen wir in der Klassendefinition angeben, dass IHydratable implementiert ist:

public class ContactInfo : IHydratable

Nachdem wir den Namespace DotNetNuke.Entities.Modules, in dem die Schnittstelle definiert ist, importiert haben, können wir wieder mit der Maus auf das kleine blaue Rechteck am Beginn des Wortes IHydratable zeigen und im erscheinenden Smarttag angeben, dass die Schnittstelle implementiert werden soll:
IHydratable-Schnittstelle implementieren

Das erzeugt folgenden Code:

#region IHydratable Member
      public void Fill(IDataReader dr)
      {
         throw new NotImplementedException();
      }

      public int KeyID
      {
         get
         {
            throw new NotImplementedException();
         }
         set
         {
            throw new NotImplementedException();
         }
      }
#endregion

Die Eigenschaft KeyID muss dem Primärschlüssel der zugrundeliegenden Tabelle entsprechen - ContactID also, und wir müssen hier nur den Wert unserer entspechenden Eigenschaft setzen bzw. zurückgeben:

      public int KeyID
      {
         get { return this.ContactID; }
         set { this.ContactID = value; }
      }

Um die Daten des DataReaders in unser Info-Objekt zu hydrieren müssen nur noch die Eigenschaften des Objekts mit den Spalten des DataReaders befüllt werden:

      public void Fill(IDataReader dr)
      {
         this.ModuleID = Convert.ToInt32(dr["ModuleID"]);
         this.ContactID = Convert.ToInt32(dr["ContactID"]);
         this.Firstname = Convert.ToString(dr["Firstname"]);
         this.Lastname = Convert.ToString(dr["Lastname"]);
         this.EmailAddress = Convert.ToString(dr["EmailAddress"]);
      }

Und damit ist die Erstellung des Info-Objekts tatsächlich abgeschlossen.

Die Info-Klasse (VB.Net)

Alle Quellcodes für die VB.Net-Variante können hier heruntergeladen und müssen nicht abgetippt werden.

Die Info-Klasse stellt die Eigenschaften des Objekts zur Verfügung. Wir müssen hier nichts weiter machen, als für jedes Feld unserer Tabelle eine Eigenschaft zu definieren. Klicken wir (in Visual Studio) also wieder mit der rechten Maustaste auf unseren Ordner DnnUgDe_Contacts (im Ordner App_Code), und dann im Kontextmenü auf "Neues Element hinzufügen…". Im folgenden Dialog geben wir folgendes ein:

ContactInfo.vb

Wir markieren wieder Klasse, wählen als Sprache Visual Basic und nennen die Klasse ContactInfo.vb. Dann klicken wir auf Hinzufügen, um den Vorgang anzuschließen.

Zuerst verpacken wir die Klasse wieder in einen Namespace, einen Konstruktor benötigen wir, aber keine Logik, wie dieser entsteht. Wir lassen diesen Teil also leer. Der Code sieht nun aus wie folgt:

Namespace DnnUgDe.DNN.Modules.VB.Contacts.Business
   Public Class ContactInfo
      Public Sub New()
      End Sub
   End Class
End Namespace

Nun definieren wir für jedes Feld der Tabelle eine Eigenschaft der Klasse. Diese Eigenschaften sind sowohl les- als auch beschreibbar, das heißt, wir benötigen für jede Eigenschaft eine private Variable (die den Wert der Eigenschaft enthält), einen (öffentlichen) get-Accessor (der den Wert der Eigenschaft zurückgibt) und einen set-Accessor (der den Wert der Eigenschaft setzt).

Properties sind etwas sehr einfaches, und um diese Einfachheit noch abzukürzen können wir auch einen Codeausschnitt verwenden. Das funktioniert so, dass man den Namen des Codeausschnitte in den Code tippt und dann einmmal die Tabulator-Taste drückt - und der Code wird automatisch eingefügt.

Geben wir im Code (innerhalb der Klasse) nun Property ein und drücken dann auf die Tabulatortaste. Der Code sieht dann wie folgt aus:
Property - Codeausschnitt
Wir wollen nun eine Eigenschaft ModuleID erzeugen, die vom Typ Integer ist, der Wert der Eigenschaft wird in der privaten Variablen _ModuleID gespeichert (im Gegensatz zu C# unterscheidet Visual Basic ja nicht zwischen Groß- und Kleinschreibung, wir könnnen also unsere private Variable nicht wie oben "moduleID" nennen, ohne dass es hier zu Konflikten kommt. In C# könnten wir das private Mitglied z.B. "moduleID" nennen):
Property - private Variable
Drücken wir nochmal auf die Tabulator-Taste. Wir sehen, dass in den Get- und Set-Accessors der Name der privaten Variablen ersetzt wurde:
Property - private Variable in den Accessors ersetzt
Als nächstes müssen wir den Datentyp eingeben - in unserem Fall "Integer". Nachdem wir das bei der privaten Variablen getan haben und die Tabulator-Taste drücken, wird der Datentyp automatisch auch für die Eigenschaft gesetzt:
Property - Datentyp
Nun tippen wir einfach noch den Namen der Eigenschaft, also "ModuleID" ein, drücken zweimal die ESC-Taste und fertig ist unsere Eigenschaft:
Property ModuleID

Auf diese Weise legen wir auch noch die restlichen Eigenschaften an:

  • ContactID (Integer)
  • Firstname (String)
  • Lastname (String)
  • EmailAddress (String)

Unser Code sieht nun wie folgt aus:

      Private _ModuleID As Integer
      Public Property ModuleID() As Integer
         Get
            Return _ModuleID
         End Get
         Set(ByVal value As Integer)
            _ModuleID = value
         End Set
      End Property


      Private _ContactID As Integer
      Public Property ContactID() As Integer
         Get
            Return _ContactID
         End Get
         Set(ByVal value As Integer)
            _ContactID = value
         End Set
      End Property


      Private _Firstname As String
      Public Property Firstname() As String
         Get
            Return _Firstname
         End Get
         Set(ByVal value As String)
            _Firstname = value
         End Set
      End Property


      Private _Lastname As String
      Public Property Lastname() As String
         Get
            Return _Lastname
         End Get
         Set(ByVal value As String)
            _Lastname = value
         End Set
      End Property


      Private _EmailAddress As String
      Public Property EmailAddress() As String
         Get
            Return _EmailAddress
         End Get
         Set(ByVal value As String)
            _EmailAddress = value
         End Set
      End Property

Damit wären wir eigentlich fertig - aber aus verschiedenen Gründen empfiehlt sich noch etwas zusätzlicher Aufwand. Mit der Version 04.06.00 wurde im DotNetNuke-Framework eine neue Schnittstelle eingeführt, die ein Info-Objekt aus einem DataReader hydriert. Diese Schnittstelle nennt sich IHydratable, und spätestens mit Version 04.08.00 ist es absolut notwendig, diese zu implementieren - möglicherweise wegen eines Bugs im Framework, aber so genau kann ich das nicht sagen. Bis Version 04.07.00 hat es jedenfalls ohne auch funktioniert.

Dazu muss man etwas ausholen. Bis Version 04.04.xx wurde zum Hydrieren eines Objekts die Reflection-Klasse verwendet, indem man die Methoden FillCollection bzw. FillObject aus der CBO-Klasse eingesetzt hat. Diese Methoden waren einfach anzusprechen, aber Reflection ist nicht gerade die schnellstmögliche Variante. In Version 04.04.00 wurde begonnen, eigene Hydrier-Funktionen einzubauen (man kann sich das ja z.B. im Quellcode der PortalController-Klasse ansehen, wenn man will). Das Problem dabei war, dass man sich dabei auch um den DataReader kümmern musste - dieser konnte z.B. geschlossen werden, nachdem er hydriert wurde, aber die darunterliegende Datenbankverbindung blieb eventuell geöffnet.

IHydratable ermöglicht, einerseits die Objekte selbst (und rasch) zu hydrieren, andererseits kümmert sich CBO um den DataReader und die Connection. Man hat also deutlich weniger Aufwand. Eine genaue Dokumentation kann man im Blog von Charles Nurse nachlesen: 4.6.0 A Sneak Peek (4) - IHydratable part 1 bzw. 4.6.0 A Sneak Peek (4) - IHydratable part 2.

IHydratable besteht aus einer Eigenschaft (KeyID) und einer Methode (Fill). Diese sind zu implementieren. Zuerst aber müssen wir in der Klassendefinition angeben, dass IHydratable implementiert ist:

public class ContactInfo
   Implements IHydratable

Nun müssen wir noch dem Namespace importieren, in dem die Schnittstelle definiert ist. Dazu geben wir am Anfang der Datei folgendes ein:

Imports DotNetNuke.Entities.Modules

Die Eigenschaft KeyID muss dem Primärschlüssel der zugrundeliegenden Tabelle entsprechen - ContactID also, und wir müssen hier nur den Wert unserer entspechenden Eigenschaft setzen bzw. zurückgeben:

      Public Property KeyID() As Integer Implements IHydratable.KeyID
         Get
            Return Me.ContactID
         End Get
         Set(ByVal value As Integer)
            Me.ContactID = value
         End Set
      End Property

Um die Daten des DataReaders in unser Info-Objekt zu hydrieren müssen nur noch die Eigenschaften des Objekts mit den Spalten des DataReaders befüllt werden:

      Public Sub Fill(ByVal dr As IDataReader) Implements IHydratable.Fill
         Me.ModuleID = Convert.ToInt32(dr("ModuleID"))
         Me.ContactID = Convert.ToInt32(dr("ContactID"))
         Me.Firstname = Convert.ToString(dr("Firstname"))
         Me.Lastname = Convert.ToString(dr("Lastname"))
         Me.EmailAddress = Convert.ToString(dr("EmailAddress"))
      End Sub

Und damit ist die Erstellung des Info-Objekts tatsächlich abgeschlossen.

Die Controller-Klasse (C#)

Die Controller-Klasse stellt - wie schon eingangs erwähnt - eine Schnittstelle zu den Methoden unseres Datenproviders dar. Hier - und sinnvollerweise nur hier - können auch Geschäftsregeln implementiert werden. Nachdem wir dies vorläufig nicht benötigen, dient die Controller-Klasse als Proxy.

Was wir benötigen, sind Methoden, die auf die Methoden des Datenproviders in (unterschiedlicher) Weise zugreifen. Wir werden uns aber hier erstmals darauf beschränken, genau für die fünf Methoden (alle Datensätze eines Moduls auslesen, einen Datensatz auslesen, einen Datensatz einfügen, einen Datensatz ändern und einen Datensatz löschen) zu implementieren.

Beginnen wir wieder damit, in unserem Ordner DnnUgDe_Contacts (im Ordner App_Code) ein neues Element hinzuzufügen:

ContactController.cs

Wir wählen wieder Klasse, stellen als Sprache Visual C# ein und nennen die Datei ContactController.cs. Klicken wir auf Hinzufügen, um den Vorgang abzuschließen.

Packen wir zunächst die Klasse in einen Namespace, sinnvollerweise auch hier den gleichen Namespace, den wir bereits für die Info-Klasse verwendet haben. Auch den Konstruktor benötigen wir, dieser erzeugt - wie bereits in der Info-Klasse - aber nur ein leeres Objekt.

Nun kommt etwas, was nicht unbedigt notwendig, in späterer Folge aber (zumindest unter Visual Studio) eine immense Arbeitserleichterung darstellt. Wir markieren das Objekt als Datenobjekt. Wir werden später - bei der Erstellung der Präsentationsschicht - Objektdatenquellen verwenden, und wenn wir hier ein wenig Vorarbeit leisten, werden wir später sehen, dass man die Objektliste beim Definieren der Datenquelle sinnvoll einschränken kann. Dieses Attribut markiert die Klasse nämlich als Klasse, die an eine Objektdatenquelle gebunden werden kann, und damit lässt sich später im Assistenten zum Definieren der Objektdatenquelle die zur Verfügung stehende Liste einfach filtern. Bei der Ausführung spielt dieses Attribut keine Rolle - es funktioniert genauso gut, wenn wir es weglassen.

Damit es aber funktioniert, muss auf den Namespace System.ComponentModel verwiesen werden, was wir entweder durch Einfügen des Verweises am Anfang der Datei oder durch unsere bereits bekannte Intellisense-Methode mit dem kleinen braunen Rechteck und dem Smarttag erreichen können. Der Code sieht nun folgendermaßen aus:

namespace DnnUgDe.DNN.Modules.CS.Contacts.Business
{
   [DataObject]
   public class ContactController
   {
      public ContactController()
      {
      }
   }
}

Alles, was wir noch benötigen, sind die Datenzugriffsmethoden. Auf die Implementierung von Geschäftslogik verzichten wir ja vorläufig einmal. Diese Methoden sind einfach - sie nehmen die benötigten Parameter entgegen, rufen die Methode der Datenzugriffsschicht (des Datenproviders) auf und liefern dessen Ergebnis in der von uns gewünschten Form zurück. Zusätzlich markieren wir die Methoden noch als Datenobjekt-Methode und beschreiben die Art des Zugriffs sowie, dass die Methode nicht automatisch verwendet werden soll:

#region Data Methods
      [DataObjectMethod(DataObjectMethodType.Select, false)]
      public List GetContacts(int moduleID)
      {
         return CBO.FillCollection(DataProvider.Instance().GetContacts(moduleID));
      }

      [DataObjectMethod(DataObjectMethodType.Select, false)]
      public ContactInfo GetContact(int contactID)
      {
         return (ContactInfo)CBO.FillObject(DataProvider.Instance().GetContact(contactID), typeof(ContactInfo));
      }

      [DataObjectMethod(DataObjectMethodType.Insert, false)]
      public int AddContact(int moduleID, string firstname, string lastname, string emailAddress)
      {
         return DataProvider.Instance().AddContact(moduleID, firstname, lastname, emailAddress);
      }

      [DataObjectMethod(DataObjectMethodType.Update, false)]
      public void ChangeContact(int contactID, string firstname, string lastname, string emailAddress)
      {
         DataProvider.Instance().ChangeContact(contactID, firstname, lastname, emailAddress);
      }

      [DataObjectMethod(DataObjectMethodType.Delete, false)]
      public void DropContact(int contactID)
      {
         DataProvider.Instance().DropContact(contactID);
      }
#endregion

Dazu werden noch einige Verweise auf Namespaces benötigt, die wir auch während des Tippens über das Smarttag importieren können:

  • System.Collections.Generic - für die generische Liste der GetAll()-Methode
  • DotNetNuke.Common.Utilities - für die CBO - Methoden
  • DnnUgDe.DNN.Modules.CS.Contacts.Data - für den Zugriff auf den Datenprovider

Generische Listen sind übrigens ein sehr mächtiges Werzeug, sie stehen seit .Net 2.0 zur Verfügung. Ich persönlich bevorzuge sie gegenüber Arrays aus unterschiedlichen Gründen - im wesentlichen halte ich sie für leichter handzuhaben.

Damit wären wir auch mit den Geschäftsobjekten fertig. Bevor wir nun frisch und fröhlich zur Erstellung unserer Präsentationsschicht schreiten (naja, langsam wollen wir was sehen, oder?), möchte ich noch auf Scott Mitchell's Data Access Tutorials hinweisen. Hier ist nachzulesen, wie man eine Datenzugriffschicht und Geschäftsobjekte in einer ASP.Net-Anwendung erstellt und verwendet.

Die Controller-Klasse (VB.Net)

Die Controller-Klasse stellt - wie schon eingangs erwähnt - eine Schnittstelle zu den Methoden unseres Datenproviders dar. Hier - und sinnvollerweise nur hier - können auch Geschäftsregeln implementiert werden. Nachdem wir dies vorläufig nicht benötigen, dient die Controller-Klasse als Proxy.

Was wir benötigen, sind Methoden, die auf die Methoden des Datenproviders in (unterschiedlicher) Weise zugreifen. Wir werden uns aber hier erstmals darauf beschränken, genau für die fünf Methoden (alle Datensätze eines Moduls auslesen, einen Datensatz auslesen, einen Datensatz einfügen, einen Datensatz ändern und einen Datensatz löschen) zu implementieren.

Beginnen wir wieder damit, in unserem Ordner DnnUgDe_Contacts (im Ordner App_Code) ein neues Element hinzuzufügen:

ContactController.vb

Wir wählen wieder Klasse, stellen als Sprache Visual Basic ein und nennen die Datei ContactController.vb. Klicken wir auf Hinzufügen, um den Vorgang abzuschließen.

Packen wir zunächst die Klasse in einen Namespace, sinnvollerweise auch hier den gleichen Namespace, den wir bereits für die Info-Klasse verwendet haben. Auch den Konstruktor benötigen wir, dieser erzeugt - wie bereits in der Info-Klasse - aber nur ein leeres Objekt.

Nun kommt etwas, was nicht unbedigt notwendig, in späterer Folge aber (zumindest unter Visual Studio) eine immense Arbeitserleichterung darstellt. Wir markieren das Objekt als Datenobjekt. Wir werden später - bei der Erstellung der Präsentationsschicht - Objektdatenquellen verwenden, und wenn wir hier ein wenig Vorarbeit leisten, werden wir später sehen, dass man die Objektliste beim Definieren der Datenquelle sinnvoll einschränken kann. Dieses Attribut markiert die Klasse nämlich als Klasse, die an eine Objektdatenquelle gebunden werden kann, und damit lässt sich später im Assistenten zum Definieren der Objektdatenquelle die zur Verfügung stehende Liste einfach filtern. Bei der Ausführung spielt dieses Attribut keine Rolle - es funktioniert genauso gut, wenn wir es weglassen. Der Code sieht nun folgendermaßen aus:

Namespace DnnUgDe.DNN.Modules.VB.Contacts.Business
    _
   Public Class ContactController
      Public Sub New()
      End Sub
   End Class
End Namespace

Alles, was wir noch benötigen, sind die Datenzugriffsmethoden. Auf die Implementierung von Geschäftslogik verzichten wir ja vorläufig einmal. Diese Methoden sind einfach - sie nehmen die benötigten Parameter entgegen, rufen die Methode der Datenzugriffsschicht (des Datenproviders) auf und liefern dessen Ergebnis in der von uns gewünschten Form zurück. Zusätzlich markieren wir die Methoden noch als Datenobjekt-Methode und beschreiben die Art des Zugriffs sowie, dass die Methode nicht automatisch verwendet werden soll:

#Region "Data Objects"
       _
      Public Function GetAll(ByVal ModuleID As Integer) As List(Of ContactInfo)
         Return CBO.FillCollection(Of ContactInfo)(DataProvider.Instance().GetContacts(ModuleID))
      End Function

       _
      Public Function GetContact(ByVal ContactID As Integer) As ContactInfo
         Return CType(CBO.FillObject(DataProvider.Instance().GetContact(ContactID), GetType(ContactInfo)), ContactInfo)
      End Function

       _
      Public Function AddContact(ByVal ModuleID As Integer, ByVal Firstname As String, ByVal Lastname As String, _
                                 ByVal EmailAddress As String) As Integer
         Return DataProvider.Instance().AddContact(ModuleID, Firstname, Lastname, EmailAddress)
      End Function

       _
      Public Sub ChangeContact(ByVal ContactID As Integer, ByVal Firstname As String, ByVal Lastname As String, _
                                 ByVal EmailAddress As String)
         DataProvider.Instance().ChangeContact(ContactID, Firstname, Lastname, EmailAddress)
      End Sub

       _
      Public Sub DropContact(ByVal ContactID As Integer)
         DataProvider.Instance().DropContact(ContactID)
      End Sub
#End Region

Dazu werden noch einige Verweise auf Namespaces benötigt, die wir am Anfang der Datei einfügen müssen:

Imports System.Collections.Generic
Imports DotNetNuke.Common.Utilities
Imports DnnUgDe.DNN.Modules.VB.Contacts.Data

Generische Listen sind übrigens ein sehr mächtiges Werzeug, sie stehen seit .Net 2.0 zur Verfügung. Ich persönlich bevorzuge sie gegenüber Arrays aus unterschiedlichen Gründen - im wesentlichen halte ich sie für leichter handzuhaben.

Damit wären wir auch mit den Geschäftsobjekten fertig. Bevor wir nun frisch und fröhlich zur Erstellung unserer Präsentationsschicht schreiten (naja, langsam wollen wir was sehen, oder?), möchte ich noch auf Scott Mitchell's Data Access Tutorials hinweisen. Hier ist nachzulesen, wie man eine Datenzugriffschicht und Geschäftsobjekte in einer ASP.Net-Anwendung erstellt und verwendet.

(PS: Nachdem ich am Sonntag in den Süden abrauschen werde, und das obwohl die Wettervorschau eher tragisch ist, werde ich hier erst in der ersten Aprilwoche wieder von mir hören lassen. Bis dahin müssen wir warten, um endlich etwas zu sehen. Nachdem aber alle Vorarbeiten abgeschlossen sind geht der Rest sehr einfach - wie wir dann staunend feststellen werden… Bis dahin wünsche ich allen geneigten Lesern und Leserinnen des Blogs ein Frohes Osterfest und viele bunte Eier :-))

C#-Quellcode herunterladen

VB.Net-Quellcode herunterladen

Kommentare: 1

Dirk Marx meint

Der Stil einer 3-Tier Applikation gefällt mir. So baue ich meine Applikationen auch auf ;) Für dieses Beispiel meint man vielleicht das es zu überdimensioniert ist und es einfacher haben kann, aber wenn das Modul wächst, ist man froh es auf diese Art und Weise konzipiert zu haben.
# 25.03.2008 11:09