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

19.03.2008
DNN-Modulprogrammierung - ein einfaches Beispiel (Schritt 2 - Die Datenzugriffsschicht) (Michael Tobisch)
Der nächste Schritt in unserer Applikation ist die Erstellung der Datenzugriffsschicht. Diese dient uns als Schnittstelle zwischen der Datenbank und der Geschäftslogik und ermöglicht den Klassen der Geschäftslogik, auf einfache Weise auf die Daten zuzugreifen.

Dazu implementieren wir zwei Klassen:

  • einen abstrakten Datenprovider, der nichts weiter tut als anzugeben, wie der Zugriff auf die Daten zu erfolgen hat
  • einen konkreten Datenprovider, der die abstrakte Klasse für das verwendete Datenbanksystem - in unserem Fall also SQL Server - implementiert

Der Vorteil dieser Implementierung liegt darin, dass man durch den Austausch des konkreten Datenproviders den Zugriff für ein anderes Datenbanksystem zur Verfügung stellen könnte. Den Rest der Applikation würde das nicht betreffen.

Ich verzichte für den Beginn auch einmal bewusst auf die Templates des DotNetNuke-Starterkits. Was das Starterkit tut ist nicht viel anderes, als was wir hier Schritt für Schritt erledigen werden, aber ich glaube, dass es zum besseren Verständnis beiträgt, die Dinge der Reihe nach zu machen. Die von uns in diesem und den nächsten Schritten angelegten Ordner und Dateien macht das Starterkit beinahe genau so - mit dem wesentlichen Unterschied, dass die vom Starterkit angelegten Ordner umbenannt werden müssen, und im Fall von C# der unten erwähnte Schritt ebenfalls manuell in die web.config eingetragen werden muss.

Starten wir also Visual Studio und öffnen wir unser Web-Projekt, dazu gibt es je nach dem verwendeten Visual Studio und dem installierten DotNetNuke verschiedene Möglichkeiten. Läuft DotNetNuke z.B auf einem lokalen IIS, so kann man mit DateiWebsite öffnen… im folgenden Dialog das Web auf dem lokalen IIS auswählen und öffnen.

Navigieren wir nun im Projektmappen-Explorer zum Ordner App_Code des Web-Projekts und klicken mit der rechten Maustaste darauf. Aus dem Kontextmenü wählen wir Neuer Ordner:

Neuer Ordner

Den neuen Ordner benennen wir DnnUgDe_Contacts. Nun kommt eine wichtige Entscheidung. Programmieren wir in VB.Net oder in C#? Für VB.Net spricht, dass DotNetNuke selbst in dieser Sprache geschrieben ist, wir uns also nicht weiter darum kümmern müssen, dass wir hier eine andere Sprache ins Spiel bringen. Andererseites: Um zu gewährleisten, dass unser Projekt nicht mit dem Gesamtprojekt unter VB.Net kompiliert wird reicht eine einzige Zeile in der web.config.

Ich persönlich habe - nachdem ich bei früheren Projekten (klassisches ASP) immer mit VBScript gearbeitet habe und die unterhalb der Präsentationsschicht liegenden Schichten in VB 6.0 geschrieben habe - so meine Probleme gehabt. Nachdem ich die ersten ASP.Net Projekte realisiert habe und weiterhin alte Projekte warten und erweitern musste, habe ich relativ rasch begonnen, die Dinge durcheinanderzubringen. Also habe ich mir einmal C# angeschaut, und festgestellt, dass die Sprache nicht so schwierig ist, und, wenn man bereits Erfahrung mit Javascript hat, wirklich leicht erlernbar ist. Also habe ich mit dem Wechsel zu ASP.Net auch den Wechsel zu C# vollzogen, und damit weniger Probleme als vorher - naja, ab und zu rutscht mir ein Semikolon an das Zeilenende von VBScript-Code, aber solche Fehler findet man relativ leicht. Darüberhinaus musste ich feststellen, dass sich Visual Studio in vielen Fällen intelligenter verhält, wenn man C# verwendet - zwei Beispiele werden wir gleich sehen.

Ich werde aber hier (für alle, die bei VB.Net bleiben wollen) beide Code-Varianten anbieten und es dem Leser überlassen, die Entscheidung zu treffen. Vielleicht nimmt der eine oder andere das ja auch zum Anlass, und schaut sich den C#-Code einmal an - und bemerkt, dass der Unterschied gar nicht sooooo groß ist. Fällt die Entscheidung auf C#, so ist jetzt ein Eintrag in der web.config unbedingt notwendig (in VB.Net kann man diesen Eintrag auch machen - der Code wird dann einfach unabhängig vom Gesamtprojekt zur Laufzeit kompiliert):

<configuration>
   ...
   <system.web>
      ...
      <compilation ...>
         <codeSubDirectories>
            ...
            <add directoryName="DnnUgDe_Contacts" />
         </codeSubDirectories>
      </compilation>
      ...
   </system.web>
   ...
</configuration>

Wichtig zu wissen ist, dass der angegebene Namen des hinzugefügten Verzeichnisses sich relativ auf das Verzeichnis App_Code bezieht. Alle Dateien in diesem Verzeichnis werden zur Laufzeit kompiliert. Genauere Informationen zu diesem Element findet man in der MSDN-Library.

Der abstrakte Datenprovider - C#

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

Klicken wir zunächst einmal wieder mit der rechten Maustaste auf den soeben erstellten Ordner und wählen aus dem Kontextmenü Neues Element hinzufügen. Das führt uns zu folgendem Dialog:

Neues Element hinzufügen - DataProvider.cs

Unter Von Visual Studio installierte Vorlagen markieren wir Klasse, in der Liste ganz unten wählen wir C# aus und dann geben wir der Datei den Namen DataProvider.cs. Um die Datei anzulegen klicken wir schließlich auf Hinzufügen.

Zuerst einmal sollten wir die soeben angelegte Klasse in einen Namespace verpacken. Das dient dazu, nicht im globalen Namespace zu arbeiten, und vor allem, nicht mit Klassen zu kollidieren, die eventuell gleich heißen. Dazu fügen wir ganz am Anfang (nach den Verweisen) den Namen des Namespaces ein. Nachdem wir hier eine abstrakte Klasse implementieren fügen wir der Deklaration der Klasse noch das Schlüsselwort abstract hinzu. Eine abstrakte Klasse dient als Basis für spätere (konkrete) Implementierungen. Den Konstruktor benötigen wir nicht, also löschen wir ihn einmal. Unser Code sieht nun folgendermaßen aus:

namespace DnnUgDe.DNN.Modules.CS.Contacts.Data
{
   /// <summary>
   /// Zusammenfassungsbeschreibung für DataProvider
   /// </summary>
   public abstract class DataProvider
   {
   }
}

Die Datenprovider-Klasse benötigt eine einzige Referenz auf das von der Klasse instantiierte Objekt. Das klingt etwas kompliziert, ist es aber nicht. Unser Objekt wird über die Reflection-Klasse des DotNetNuke-Frameworks erzeugt, alles was wir benötigen ist eine einzige Referenz auf dieses Objekt. Das Objekt muss also nur ein einziges Mal erzeugt werden, über die Referenz greifen wir immer wieder darauf zu. Auf Englisch nennt man das singleton reference, was so viel wie "ein Einzelstück der Referenz" bedeutet. Wir erzeugen also eine (statische) Objektvariable, der wir zunächst den Wert NULL zuweisen, und eine (öffentliche und statische) Methode, die überprüft, ob das Objekt NULL ist. Wenn das Objekt NULL ist, wird es ein einziges Mal (beim ersten Zugriff) über die Reflection-Klasse erzeugt, und sonst einfach nur zurückgegeben. Das ist alles. Der Code dafür (der in unserer Klasse eingefügt wird) sieht so aus:

      // Erzeugt ein Einzelstück einer Referenz auf das instantiierte Objekt
      static DataProvider provider = null;

      // Stellt den Provider zur Verfügung
      public static DataProvider Instance()
      {
         if (provider == null)
            provider = (DataProvider)Reflection.CreateObject("data", "DnnUgDe.DNN.Modules.Contacts.Data", "");
         return provider;
      }

Um die Reflection-Klasse korrekt anzusprechen müssen wir noch auf den korrekten Namespace verweisen, indem wir am Anfang der Datei folgendes einfügen:

using DotNetNuke.Framework;

Unter C# bietet sich hier ein immenser Vorteil. Ein fehlender Verweis erzeugt ein kleines rotbraunes Rechteck am Ende des Klassennamens:

Intellisense C#

Fährt man mir der Maus darauf, so erscheint die Möglichkeit, Optionen für die Bindung auszuwählen:

Intellisense C#

Öffnet man diese Liste, so braucht man nur noch auf eine sinnvolle Option zu klicken (in unserem Fall eine der ersten beiden), und entweder das using-Statement oder der vollständige Namespace wird automatisch eingefügt:

Intellisense C#

Was wir noch benötigen sind die abstrakten Methoden, die der konkrete Datenprovider später erfüllen muss, um auf die Daten zuzugreifen. Abstrakte Methoden stellen keine konkrete Implementierung zur Verfügung, sondern dienen lediglich wieder als Basis für die Methoden der konkreten Implementierungen der Klasse. Die Definition endet also einfach mit einem Semikolon (;), sie enthalten keinen "Körper" und daher auch keine geschwungenen Klammern ({}) nach der Bezeichnung. Diese Methoden sind in unserem Fall rasch definiert und entsprechen den im ersten Schritt angelegten Gespeicherten Prozeduren:

#region Abstract Methods
      // Alle Kontakte auslesen
      public abstract IDataReader GetContacts(int moduleID);

      // Einzelnen Kontakt auslesen
      public abstract IDataReader GetContact(int contactID);

      // Kontakt einfügen
      public abstract int AddContact(int moduleID, string firstname, string lastname, string emailAddress);

      // Kontakt aktualisieren
      public abstract void ChangeContact(int contactID, string firstname, string lastname, string emailAddress);

      // Kontakt löschen
      public abstract void DropContact(int contactID);
#endregion

Damit wäre der abstakte Datenprovider fertig.

Der abstrakte Datenprovider - VB.Net

(Vorweg: Ich habe das Verzeichnis, in dem ich die VB-Version erstelle, bei mir DnnUgDe_ContactsVB genannt, damit auf meiner Installation keine Reibereien rauskommen. Wenn man nur die VisualBasic-Variante programmiert, dann kann der Ordner ohne weiteres so heißen, wie wir ihn am Beginn dieses Artikels genannt haben.)

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

Klicken wir zunächst einmal wieder mit der rechten Maustaste auf den soeben erstellten Ordner und wählen aus dem Kontextmenü Neues Element hinzufügen. Das führt uns zu folgendem Dialog:

Neues Element hinzufügen - DataProvider.vb

Unter Von Visual Studio installierte Vorlagen markieren wir Klasse, in der Liste ganz unten wählen wir Visual Basic aus und dann geben wir der Datei den Namen DataProvider.vb. Um die Datei anzulegen klicken wir schließlich auf Hinzufügen.

Zuerst einmal sollten wir die soeben angelegte Klasse in einen Namespace verpacken. Das dient dazu, nicht im globalen Namespace zu arbeiten, und vor allem, nicht mit Klassen zu kollidieren, die eventuell gleich heißen. dazu fügen wir ganz am Anfang (nach den Verweisen) den Namen des Namespaces ein. Nachdem wir hier eine abstrakte Klasse implementieren fügen wir der Deklaration der Klasse noch das Schlüsselwort MustInherit hinzu. Eine abstrakte Klasse dient als Basis für spätere (konkrete) Implementierungen. Den Konstruktor benötigen wir nicht, also löschen wir ihn einmal. Unser Code sieht nun folgendermaßen aus:

Namespace DnnUgDe.DNN.Modules.VB.Contacts.Data
   Public MustInherit Class DataProvider
   End Class
End Namespace

Die Datenprovider-Klasse benötigt eine einzige Referenz auf das von der Klasse instantiierte Objekt. Das klingt etwas kompliziert, ist es aber nicht. Unser Objekt wird über die Reflection-Klasse des DotNetNuke-Frameworks erzeugt, alles was wir benötigen ist eine einzige Referenz auf dieses Objekt. Das Objekt muss also nur ein einziges Mal erzeugt werden, über die Referenz greifen wir immer wieder darauf zu. Auf Englisch nennt man das singleton reference, was so viel wie "ein Einzelstück der Referenz" bedeutet. Wir erzeugen also eine (statische) Objektvariable, der wir zunächst den Wert NULL zuweisen, und eine (öffentliche und statische) Methode, die überprüft, ob das Objekt NULL ist. Wenn das Objekt NULL ist, wird es ein einziges Mal (beim ersten Zugriff) über die Reflection-Klasse erzeugt, und sonst einfach nur zurückgegeben. Das ist alles. Der Code dafür (der in unserer Klasse eingefügt wird) sieht so aus:

      ' Erzeugt ein Einzelstück einer Referenz auf das instantiierte Objekt
      Private Shared _provider As DataProvider = Nothing

      ' Stellt den Provider zur Verfügung
      Public Shared Shadows Function Instance() As DataProvider
         If (_provider Is Nothing) Then
            _provider = CType(Reflection.CreateObject("data", "DnnUgDe.DNN.Modules.VB.Contacts.Data", ""), DataProvider)
         End If
         Return _provider
      End Function

Da uns hier die schöne Intellisense-Unterstützung fehlt, müssen wir am Anfang der Datei noch folgenden Verweis einfügen:

Imports DotNetNuke.Framework

Was wir noch benötigen sind die abstrakten Methoden, die der konkrete Datenprovider später erfüllen muss, um auf die Daten zuzugreifen. Abstrakte Methoden stellen keine konkrete Implementierung zur Verfügung, sondern dienen lediglich wieder als Basis für die Methoden der konkreten Implementierungen der Klasse. Die Definition endet also einfach mit dem Zeilenende, sie enthalten keinen "Körper" und daher kein End Function oder End Sub nach der Bezeichnung. Diese Methoden sind in unserem Fall rasch definiert und entsprechen den im ersten Schritt angelegten Gespeicherten Prozeduren:

#Region "Abstract Methods"
      ' Alle Kontakte auslesen
      Public MustOverride Function GetContacts(ByVal ModuleId As Integer) As IDataReader

      ' Einzelnen Kontakt auslesen
      Public MustOverride Function GetContact(ByVal ContactId As Integer) As IDataReader

      ' Kontakt einfügen
      Public MustOverride Function AddContact(ByVal ModuleId As Integer, ByVal Firstname As String, _
ByVal LastName As String, ByVal EmailAddress As String) As Integer ' Kontakt aktualisieren Public MustOverride Sub ChangeContact(ByVal ContactId As Integer, ByVal Firstname As String, _
ByVal LastName As String, ByVal EmailAddress As String) ' Kontakt löschen Public MustOverride Sub DropContact(ByVal ContactId As Integer) #End Region

Damit wäre der abstakte Datenprovider fertig.

Der SQL-Datenprovider (C#)

Um nun konkret auf unser eingesetztes Datenbanksystem (SQL Server) zugreifen zu können, benötigen wir eine Implementierung unserer abstrakten Datenprovider-Klasse für dieses System. In unserem Ordner DnnUgDe_Contacts fügen wir also ein neues Element hinzu:

Neues Element hinzufügen - SqlDataProvider.cs

Das Element ist wieder eine Klasse, als Sprache wählen wir C# und nennen das Element SqlDataProvider.cs. Klicken wir auf Hinzufügen, um den Vorgang abzuschließen.

Auch hier packen wir die Klasse zunächst in einen Namespace (sinnvollerweise in den gleichen wie den abstrakten Datenprovider), und geben an, dass die Klasse die DataProvider-Klasse implementiert. Den Konstruktor werden wir benötigen, also lassen wir ihn wie er ist stehen. Unser Code sieht nun aus wie folgt:

namespace DnnUgDe.DNN.Modules.CS.Contacts.Data
{
   /// <summary>
   /// Zusammenfassungsbeschreibung für SqlDataProvider
   /// </summary>
   public class SqlDataProvider : DataProvider
   {
      public SqlDataProvider()
      {
         //
         // TODO: Konstruktorlogik hier hinzufügen
         //
      }
   }
}

Bevor wir unseren Klassenkonstruktor schreiben benötigen wir noch einige Eigenschaften und Methoden. Zunächst einmal definieren wir eine Modulqualifizierer-Konstante, um uns bei der Definition der abstrakten Methoden später die ständige Schreibarbeit des Präfixes DnnUgDe_ zu ersparen - Faulheit siegt. Dann benötigen wir eine einzelne Referenz auf die Provider-Konfiguration, um unseren Connection-String, Objektqualifizierer, Datenbankbesitzer usw. aus der web.config auszulesen. Die (private) Methode GetFullyQualifiedName() setzt aus diesen Informationen den Namen der Gespeicherten Prozedur zusammen. Was wir noch benötigten, hätten wir Datenfelder, die NULL-Werte erlauben, ist eine Methode, die Null-Werte behandelt. Nachdem das (noch) nicht der Fall ist kommen wir später darauf zurück.

#region Private Members
      private const string MODULE_QUALIFIER = "DnnUgDe_";
      private ProviderConfiguration providerConfiguration = null;
      private string connectionString;
      private string providerPath;
      private string objectQualifier;
      private string databaseOwner;
#endregion

#region Protected Properties
      protected ProviderConfiguration ProviderConfiguration
      {
         get
         {
            if (providerConfiguration == null)
               providerConfiguration = ProviderConfiguration.GetProviderConfiguration("data");
            return providerConfiguration;
         }
      }
#endregion

#region Private Methods
      private string GetFullyQualifiedName(string objectName)
      {
         return this.DatabaseOwner + this.ObjectQualifier + MODULE_QUALIFIER + objectName;
      }
#endregion

#region Public Properties
      public string ConnectionString
      {
         get { return connectionString; }
      }

      public string ProviderPath
      {
         get { return providerPath; }
      }

      public string ObjectQualifier
      {
         get { return objectQualifier; }
      }

      public string DatabaseOwner
      {
         get { return databaseOwner; }
      }
#endregion

Die Klasse ProviderConfiguration liegt im Namespace DotNetNuke.Framework.Providers - wir können den Verweis darauf wie im Beispiel vorher einfügen lassen oder an den Anfang der Datei schreiben:

using DotNetNuke.Framework.Providers;

Nun können wir also unseren Klassenkonstruktor implementieren. Der Konstruktor liest Attribute aus dem Standardprovider unserer Installation aus und ordnet diese unseren vorhin definierten Eigenschaften zu. Dazu kommt noch, dass sicherheitshalber ein Unterstrich an den Objektqualifizierer angehängt wird (wenn dieser nicht vorhanden ist) und ebenso ein Punkt an den Datenbankbesitzer.

#region Constructor
      public SqlDataProvider()
      {
         Provider provider = (Provider)ProviderConfiguration.Providers[ProviderConfiguration.DefaultProvider];

         if ((provider.Attributes["connectionStringName"] != string.Empty) 
&& (ConfigurationManager.AppSettings[provider.Attributes["connectionStringName"]] != string.Empty)) connectionString = ConfigurationManager.AppSettings[provider.Attributes["connectionStringName"]]; else connectionString = provider.Attributes["connectionString"]; providerPath = provider.Attributes["providerPath"]; objectQualifier = provider.Attributes["objectQualifier"]; if ((objectQualifier != string.Empty) && (!(objectQualifier.EndsWith("_")))) objectQualifier += "_"; databaseOwner = provider.Attributes["databaseOwner"]; if ((databaseOwner != string.Empty) && (!(databaseOwner.EndsWith(".")))) databaseOwner += "."; } #endregion

Was uns noch fehlt ist die Implementierung unserer abstrakten Klassen. C# bietet hier eine einfache Möglichkeit, sich wieder einiges an Schreibarbeit zu ersparen. Stellt man den Cursor in den Namen der abzuleitenden Klasse, so erscheint am Beginn des Klassennamens ein kleines blaues Rechteck:
Intellisense - Klasse implementieren
Fährt man mit der Maus darauf erscheint wieder eine Liste mit Optionen für die Implementierung der abstrakten Klasse:

Öffnet man diese Liste und klickt auf die (einzige) Option Abstakte DataProvider-Klasse implementieren, so finden wir am Ende der Datei alle Methoden, die in unserer abstrakten Klasse als abstrakt gekennzeichnet sind - also genau unsere fünf mit den Gespeicherten Prozeduren korrellierenden Methoden:

#region DataProvider Implementation
      public override IDataReader GetContacts(int moduleID)
      {
         throw new NotImplementedException();
      }

      public override IDataReader GetContact(int contactID)
      {
         throw new NotImplementedException();
      }

      public override int AddContact(int moduleID, string firstname, string lastname, string emailAddress)
      {
         throw new NotImplementedException();
      }

      public override void ChangeContact(int contactID, string firstname, string lastname, string emailAddress)
      {
         throw new NotImplementedException();
      }

      public override void DropContact(int contactID)
      {
         throw new NotImplementedException();
      }
#endregion

Natürlich weiß Visual Studio nicht, wie wir unsere Methoden implementieren wollen, wie unsere Gespeicherten Prozeduren heißen usw. Daher wird in den Methoden hier nichts anderes getan, als eine Exception gefeuert. Die Namen, Datentypen und Parameter unserer Methoden stehen allerdings bereits da - und alles was wir noch erledigen müssen ist, die Exceptions durch den gewünschten Code zu ersetzen.

Im Microsoft Data Access Application Block finden wir Methoden, um auf Gespeicherte Prozeduren des SQL Servers zugreifen zu können, die sogenannten SqlHelper-Methoden - und genau diese wollen wir hier verwenden. Dazu benötigen wir den Namespace Microsoft.ApplicationBlocks.Data, den wir aber - wie bereits oben gesehen - während unserer Tipperei importieren lassen können. Die Methoden, die wir verwenden, sind:

  • ExecuteReader - führt eine Datenabfrage aus und gibt das Ergebnis der Abfrage als SqlDataReader zurück - um zu einem IDataReader zu kommen müssen wir das Rückgabeobjekt also "casten"
  • ExecuteScalar - führt eine Abfrage mit einem skalaren Ergebnis aus und gibt dieses als Objekt zurück - um zu einem Integer zu kommen, müssen wir das Objekt also konvertieren.
  • ExecuteNonQuery - führt eine Aktionsabfrage aus und gibt die Anzahl der betroffenen Datensätze zurück (was uns hier nicht weiter interessiert).

Alle diese Methoden erwarten folgende Parameter (zumindest in der von uns verwendeten Überladung):

  1. ConnectionString - Verbindung zur Datenbank
  2. Name der Gespeicherten Prozedur
  3. Abfrage-Parameter als Array von Objekten

Unsere Methoden sehen also aus wie folgt:

#region DataProvider Implementation
      public override IDataReader GetContacts(int moduleID)
      {
         return (IDataReader)SqlHelper.ExecuteReader(this.ConnectionString, 
this.GetFullyQualifiedName("ContactsGet"), new object[] { moduleID }); } public override IDataReader GetContact(int contactID) { return (IDataReader)SqlHelper.ExecuteReader(this.ConnectionString,
this.GetFullyQualifiedName("ContactGet"), new object[] { contactID }); } public override int AddContact(int moduleID, string firstname, string lastname, string emailAddress) { return Convert.ToInt32(SqlHelper.ExecuteScalar(this.ConnectionString,
this.GetFullyQualifiedName("ContactInsert"), new object[] { moduleID, firstname, lastname, emailAddress })); } public override void ChangeContact(int contactID, string firstname, string lastname, string emailAddress) { SqlHelper.ExecuteNonQuery(this.ConnectionString, this.GetFullyQualifiedName("ContactUpdate"),
new object[] { contactID, firstname, lastname, emailAddress }); } public override void DropContact(int contactID) { SqlHelper.ExecuteNonQuery(this.ConnectionString, this.GetFullyQualifiedName("ContactDelete"), new object[] { contactID }); } #endregion

Damit wäre der SQL-Datenprovider und somit unsere Datenzugriffsschicht fertiggestellt.

Der SQL-Datenprovider (VB.Net)

Um nun konkret auf unser eingesetztes Datenbanksystem (SQL Server) zugreifen zu können, benötigen wir eine Implementierung unserer abstrakten Datenprovider-Klasse für dieses System. In unserem Ordner DnnUgDe_Contacts fügen wir also ein neues Element hinzu:

Neues Element hinzufügen - SqlDataProvider.vb

Das Element ist wieder eine Klasse, als Sprache wählen wir VisualBasic und nennen das Element SqlDataProvider.vb. Klicken wir auf Hinzufügen, um den Vorgang abzuschließen.

Auch hier packen wir die Klasse zunächst in einen Namespace (sinnvollerweise in den gleichen wie den abstrakten Datenprovider), und geben an, dass die Klasse die DataProvider-Klasse implementiert. Den Konstruktor werden wir benötigen, also lassen wir ihn wie er ist stehen. Unser Code sieht nun aus wie folgt:

Namespace DnnUgDe.DNN.Modules.VB.Contacts.Data
   Public Class SqlDataProvider
      Inherits DataProvider

   End Class
End Namespace

Lassen wir uns nicht von der blauen Wellenlinie unter dem Klassennamen irritieren: Dieser zeigt uns an, dass die in der abstrakten Klasse mit MustOverride gekennzeichneten Methoden noch nicht implementiert sind. Das werden wir demnächst erledigen...

Bevor wir unseren Klassenkonstruktor schreiben benötigen wir noch einige Eigenschaften und Methoden. Zunächst einmal definieren wir eine Modulqualifizierer-Konstante, um uns bei der Definition der abstrakten Methoden später die ständige Schreibarbeit des Präfixes DnnUgDe_ zu ersparen - Faulheit siegt. Dann benötigen wir eine einzelne Referenz auf die Provider-Konfiguration, um unseren Connection-String, Objektqualifizierer, Datenbankbesitzer usw. aus der web.config auszulesen. Die (private) Methode GetFullyQualifiedName() setzt aus diesen Informationen den Namen der Gespeicherten Prozedur zusammen. Was wir noch benötigten, hätten wir Datenfelder, die NULL-Werte erlauben, ist eine Methode, die Null-Werte behandelt. Nachdem das (noch) nicht der Fall ist kommen wir später darauf zurück.

#Region "Private Members"
      Private Const MODULE_QUALIFIER As String = "DnnUgDe_"
      Private _providerConfiguration As ProviderConfiguration = Nothing
      Private _connectionString As String
      Private _providerPath As String
      Private _objectQualifier As String
      Private _databaseOwner As String
#End Region

#Region "Public Properties"
      ReadOnly Property ConnectionString() As String
         Get
            Return _connectionString
         End Get
      End Property

      ReadOnly Property ProviderPath() As String
         Get
            Return _providerPath
         End Get
      End Property

      ReadOnly Property ObjectQualifier() As String
         Get
            Return _objectQualifier
         End Get
      End Property

      ReadOnly Property DatabaseOwner() As String
         Get
            Return _databaseOwner
         End Get
      End Property
#End Region

#Region "Protected Properties"
      Protected ReadOnly Property ProviderConfiguration() As ProviderConfiguration
         Get
            If (_providerConfiguration Is Nothing) Then
               _providerConfiguration = ProviderConfiguration.GetProviderConfiguration("data")
            End If
            Return _providerConfiguration
         End Get
      End Property
#End Region

#Region "Private Methods"
      Private Function GetFullyQualifiedName(ByVal _objectName As String) As String
         GetFullyQualifiedName = Me.DatabaseOwner & Me.ObjectQualifier & MODULE_QUALIFIER
      End Function
#End Region

Die Klasse ProviderConfiguration liegt im Namespace DotNetNuke.Framework.Providers - wir müssen den Verweis darauf wie im Beispiel vorher an den Anfang der Datei schreiben:

Imports DotNetNuke.Framework.Providers;

Nun können wir also unseren Klassenkonstruktor implementieren. Der Konstruktor liest Attribute aus dem Standardprovider unserer Installation aus und ordnet diese unseren vorhin definierten Eigenschaften zu. Dazu kommt noch, dass sicherheitshalber ein Unterstrich an den Objektqualifizierer angehängt wird (wenn dieser nicht vorhanden ist) und ebenso ein Punkt an den Datenbankbesitzer.

#Region "Constructor"
      Public Sub New()
         Dim _provider As Provider = CType(_providerConfiguration.Providers(_providerConfiguration.DefaultProvider), Provider)

         If ((Not (String.IsNullOrEmpty(_provider.Attributes("connectionStringName")))) _
AndAlso (Not (String.IsNullOrEmpty(ConfigurationManager.AppSettings(_provider.Attributes("connectionStringName")))))) Then _connectionString = ConfigurationManager.AppSettings(_provider.Attributes("connectionStringName")) Else _connectionString = _provider.Attributes("connectionString") End If _providerPath = _provider.Attributes("providerPath") _objectQualifier = _provider.Attributes("objectQualifier") If ((Not (String.IsNullOrEmpty(_objectQualifier))) AndAlso (Not (_objectQualifier.EndsWith("_")))) Then _objectQualifier = _objectQualifier & "_" End If _databaseOwner = _provider.Attributes("databaseOwner") If ((Not (String.IsNullOrEmpty(_databaseOwner))) AndAlso (Not (_databaseOwner.EndsWith(".")))) Then _databaseOwner = _databaseOwner & "." End If End Sub #End Region

Was uns noch fehlt ist die Implementierung unserer abstrakten Klassen. In den Microsoft Application Blocks finden wir Methoden, um auf Gespeicherte Prozeduren des SQL Servers zugreifen zu können, die sogenannten SqlHelper-Methoden - und genau diese wollen wir hier verwenden. Dazu benötigen wir den Namespace Microsoft.ApplicationBlocks.Data, den wir am Beginn unserer Datei importieren:

Imports Microsoft.ApplicationBlocks.Data

Die Methoden, die wir verwenden, sind:

  • ExecuteReader - führt eine Datenabfrage aus und gibt das Ergebnis der Abfrage als SqlDataReader zurück - um zu einem IDataReader zu kommen müssen wir für das Rückgabeobjekt ein Typecasting durchführen.
  • ExecuteScalar - führt eine Abfrage mit einem skalaren Ergebnis aus und gibt dieses als Objekt zurück - um zu einem Integer zu kommen müssen wir für das Rückgabeobjekt ein Typecasting durchführen..
  • ExecuteNonQuery - führt eine Aktionsabfrage aus und gibt die Anzahl der betroffenen Datensätze zurück (was uns hier nicht weiter interessiert).

Alle diese Methoden erwarten folgende Parameter (zumindest in der von uns verwendeten Überladung):

  1. ConnectionString - Verbindung zur Datenbank
  2. Name der Gespeicherten Prozedur
  3. Abfrage-Parameter als Array von Objekten

Unsere Methoden sehen also aus wie folgt:

#Region "DataProvider Implementation"
      Public Overrides Function GetContacts(ByVal ModuleId As Integer) As IDataReader
         Return CType(SqlHelper.ExecuteReader(Me.ConnectionString, Me.GetFullyQualifiedName("ContactsGet"), _
New Object() {ModuleId}), IDataReader) End Function Public Overrides Function GetContact(ByVal ContactId As Integer) As IDataReader Return CType(SqlHelper.ExecuteReader(Me.ConnectionString, Me.GetFullyQualifiedName("ContactGet"), _
New Object() {ContactId}), IDataReader) End Function Public Overrides Function AddContact(ByVal ModuleId As Integer, ByVal Firstname As String, _
´ ByVal LastName As String, ByVal EmailAddress As String) As Integer´
Return CType(SqlHelper.ExecuteScalar(Me.ConnectionString, Me.GetFullyQualifiedName("ContactInsert"), _
New Object() {ModuleId, Firstname, LastName, EmailAddress}), Integer) End Function Public Overrides Sub ChangeContact(ByVal ContactId As Integer, ByVal Firstname As String, _
ByVal LastName As String, ByVal EmailAddress As String) SqlHelper.ExecuteNonQuery(Me.ConnectionString, Me.GetFullyQualifiedName("ContactUpdate"), _
New Object() {ContactId, Firstname, LastName, EmailAddress}) End Sub Public Overrides Sub DropContact(ByVal ContactId As Integer) SqlHelper.ExecuteNonQuery(Me.ConnectionString, Me.GetFullyQualifiedName("ContactDelete"), _
New Object() {ContactId}) End Sub #End Region

Damit wäre der SQL-Datenprovider und somit unsere Datenzugriffsschicht fertiggestellt.

C#-Quellcode herunterladen

VB.Net-Quellcode herunterladen

(PS: Ob sich der nächste Schritt noch diese Woche fertigstellen kann will ich jetzt nicht versprechen. Nächste Woche bin ich jedenfalls im Urlaub, daher kommt Schritt 3 spätestens in der 1. Aprilwoche - sofern ich Kalabrien überlebe :-))

Zu diesem Beitrag liegen noch keine Kommentare vor.