DNN-Modulprogrammierung - ein einfaches Beispiel (Schritt 3 - Die Die Geschäftsobjekte) (Michael Tobisch)
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:
- Die Info-Klasse stellt die Eigenschaften des Objekts zur Verfügung. Diese entsprechen im wesentlichen den Feldern der zugrunde liegenden Tabelle.
- 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:

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:

Im folgenden Dialog wählen wir zunächst unter Sprache Visual C#, und klicken dann auf importieren:

Navigieren wir nun zu dem Ordner, in dem die Datei propm.snippet liegt, markieren diese und klicken auf Öffnen:

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

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):

Drücken wir nochmal auf die Tabulator-Taste. Wir sehen, dass in den get- und set-Accessors der Name des privaten Mitglieds ersetzt wurde:

Nun tippen wir einfach noch den Namen der Eigenschaft, also "ModuleID" ein, drücken die ESC-Taste und fertig ist unsere Eigenschaft
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:
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:

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:

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):

Drücken wir nochmal auf die Tabulator-Taste. Wir sehen, dass in den Get- und Set-Accessors der Name der privaten Variablen ersetzt wurde:

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:

Nun tippen wir einfach noch den Namen der Eigenschaft, also "ModuleID" ein, drücken zweimal die ESC-Taste und fertig ist unsere Eigenschaft:
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:

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:

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