Geschäftslogik zentral verwalten

Veröffentlicht: 03. Feb 2004 | Aktualisiert: 29. Jun 2004
Von Rockford Lhotka

Rockford Lhotka zeigt, wie Sie Delegaten des .NET Framework zum Erstellen eines einfachen Regelmoduls verwenden, um die Validierungslogik in Ihrer Anwendung zu zentralisieren.

* * *

Auf dieser Seite

Implementieren eines Regel-Managers Implementieren eines Regel-Managers
Schlussfolgerung Schlussfolgerung

Ich bin zu der kühnen Überzeugung gelangt, dass einige Aspekte der Newton'schen Physik auf die Softwareentwicklung zutreffen. Denken Sie beispielsweise an das Gesetz der Energieerhaltung. Es besagt, dass Energie nicht erzeugt oder vernichtet wird. Dieses Gesetz kann man auch auf die Geschäftslogik in einer Anwendung übertragen und einen Geschäftslogikerhaltungs-Satz aufstellen:

Geschäftslogik in einer Anwendung kann nicht erzeugt oder vernichtet werden.

Für Software gilt tragischerweise, dass Geschäftslogik sehr wohl dupliziert werden kann. Bei der duplizierten Geschäftslogik handelt es sich natürlich nicht um neue Geschäftslogik, sondern nur um eine (möglicherweise mit Fehlern behaftete) Kopie der tatsächlichen Geschäftslogik.
Denken Sie an eine normale Anwendung mit verschiedenen Formularen, die die gleichen Kundendaten verarbeiten. Die Geschäftslogik für die Kundendaten ist in allen Fällen identisch. Da wir diese Daten aber in zwei verschiedenen Formularen verwenden, duplizieren wir oft die Logik von Formular zu Formular.

Geschaeftslogik-Manager_01.gif

Abbildung 1. Duplizierte Geschäftslogik

Es ist klar, dass die verschiedenen Kopien der Geschäftslogik mit der Zeit voneinander abweichen. Wir erhalten schließlich eine fehlerhafte und schwer zu pflegende Anwendung.
Geschäftsobjekte stellen einen Weg dar, die Duplizierung der Logik zu verhindern. Wir können die Geschäftslogik und die Kundendaten in einem einzelnen Objekt zusammenfassen und dieses Objekt für beide Formulare verwenden. Die Geschäftslogik liegt jetzt an zentraler Stelle vor, anstatt in den Formularen.

Geschaeftslogik-Manager_02.gif

Abbildung 2. Geschäftslogik wird in einem Objekt zentralisiert

Die Zentralisierung der Geschäftslogik in einem Objekt ist ein großer Schritt vorwärts, aber noch keine abschließende Lösung. Auch innerhalb der Klasse, die das Objekt definiert, duplizieren wir Geschäftslogik häufig.
Wenn zum Beispiel eine Geschäftsregel besagt, dass ein Feld über einen Wert verfügen muss, dann berücksichtigen wir diese Regel wahrscheinlich im Property Set-Code, im Konstruktor des Objekts, in dem Code, in dem wir die Objektdaten von der Datenbank laden, und möglicherweise auch in dem Code, mit dem wir die Daten wieder in der Datenbank speichern.

Geschaeftslogik-Manager_03.gif

Abbildung 3. Duplizierte Geschäftslogik innerhalb eines Objekts

Wir duplizieren den Code sogar mehrmals, wenn er in einer einzelnen Klasse enthalten ist. Die Anwendung wird dadurch natürlich komplexer und aufwändiger zu pflegen.
Wir müssen auf jeden Fall einen Weg finden, um die Validierungslogik für die Daten tatsächlich zu zentralisieren, ganz gleich, ob wir nun Geschäftsobjekte verwenden oder einen traditionelleren Weg auf der Grundlage von ADO.NET-Objekten wählen.

Geschaeftslogik-Manager_04.gif

Abbildung 4. Zentralisierte Geschäftslogik in einem Regel-Manager

Die Idee eines "Regel-Managers" oder "Regelmoduls" ist nicht neu. Seit langem existieren viele Formen von Regelmodulen. In diesem Artikel will ich kein stabiles, marktfähiges Regelmodul erstellen. Der Code zum Download zu diesem Artikel enthält aber einen Regel-Manager, der eine grundlegende, wieder verwendbare Komponente implementiert, die viele der wichtigsten Funktionen zum Zentralisieren von Datenvalidierungscode bereitstellt. Ich erläutere in diesem Artikel die wichtigsten Punkte der Implementierung, da interessante Technologien des .NET Framework verwendet werden, zum Beispiel Delegaten und HybridDictionary-Objekte.

Implementieren eines Regel-Managers

Zunächst müssen wir uns klarmachen, dass Gültigkeitsregeln durch Schreiben von Code implementiert werden. Selbst wenn wir ein Schema einsetzen, das die Regeln durch Metadaten beschreibt (beispielsweise XML), wird die eigentliche Verarbeitung der Regel vom Code übernommen.
Außerdem wird eine Regel entweder eingehalten oder nicht eingehalten; wir haben es daher mit einer Boole'schen Bedingung zu tun. Sobald eine Regel überprüft wurde, wird True oder False zurückgegeben, um anzugeben, ob die Bedingung erfüllt wurde.
Wir können im Grunde also feststellen, dass eine Regel eine Methode ist, die die Validierung implementiert und True oder False zurückgibt.
Zum Implementieren dieser Methode benötigen wir mindestens die wichtigsten Informationen:

  • Zugriff auf das Objekt, das die Daten enthält (häufig ein DataTable-Objekt oder ein Geschäftsobjekt).

  • Den Namen des Felds, der Eigenschaft oder Spalte, das bzw. die den zu überprüfenden Wert enthält.

Möglicherweise benötigen wir noch weitere Informationen. Sehen wir uns einige häufige Szenarios an:

  • Szenario 1: ein erforderlicher Textwert. Zur Durchführung dieser Art von Regel benötigen wir Zugriff auf den Wert, der zu prüfen ist. Der Wert ist in einem Objekt enthalten. Wir können die Daten also abrufen und validieren, wenn wir auf das Objekt zugreifen können, das den Wert enthält, und den Namen des entsprechenden Felds, der Eigenschaft oder Spalte kennen.

  • Szenario 2: ein Wert, der nur erforderlich ist, wenn ein anderer Wert vorhanden ist. Zur Durchführung dieser Art von Regel benötigen wir nicht nur Zugriff auf den zu prüfenden Wert selbst, sondern auch noch auf einen weiteren Wert. Wenn wir dieses Szenario ein wenig erweitern, könnten wir feststellen, dass der Code auf mehrere andere Werte zugreifen können muss. Wenn wir auf das Objekt zugreifen können, das die Daten enthält, können wir glücklicherweise Code schreiben, um alle Werte abzurufen, die wir zum Validieren der Daten benötigen.

  • Szenario 3: ein Wert, der nur benötigt wird, wenn Daten in anderen Zeilen oder Objekten eine Bedingung erfüllen. Diese Art Regel kann sehr komplex sein. Wir benötigen nicht nur Zugriff auf den Datenwert selbst, sondern auch Zugriff auf beliebige andere Daten (wie andere Zeilen in einem DataTable, ein anderes DataTable oder andere Geschäftsobjekte). Hierfür muss der Code für die Regel zusammen mit dem Rest des Anwendungscodes ausgeführt werden, damit er auf alle diese Werte zugreifen kann.

Definieren von Methoden für Regeln

Um diese Szenarios zu berücksichtigen, können wir die Parameter so definieren, dass sie von jeder Methode für Regeln übergeben werden. In .NET kann ein vordefinierter Satz Methodenparameter als Delegat deklariert werden. Wir schreiben also folgenden Code:

Public Delegate Function RuleHandler( _ 
  ByVal target As Object, ByVal e As RuleArgs) As Boolean

Wir folgen hierbei der gleichen grundlegenden Parameterkonvention, die von Windows Forms und Web Forms für Ereignisse verwendet wird, mit einem Objekt und einem Argument als Parameter.
Zielparameter ist das Objekt, das die zu prüfenden Daten enthält. Normalerweise handelt es sich um ein DataRow-Objekt oder ein Geschäftsobjekt.
Der zweite Parameter ist vom Typ RuleArgs. Dieser Parameter entspricht den in Windows Forms und Web Forms verwendeten EventArgs insofern, als er einen Mechanismus bereitstellt, mit dem wir weitere beliebige Daten übergeben können, die möglicherweise von der Methode benötigt werden, die unsere Validierung implementiert. Dieser Parameter muss mindestens den Namen des Felds, der Eigenschaft oder Spalte angeben, in dem bzw. der die Daten enthalten sind (den vollständigen Code finden Sie im Downolad-Beispiel zu diesem Artikel):

<Serializable()> _ 
Public Class RuleArgs 
  Private mPropertyName As String 
  Public ReadOnly Property PropertyName() As String 
 Get 
   Return mPropertyName 
 End Get 
  End Property 
  ' ... 
End Class

Wenn wir andere Daten an bestimmte Regeln übergeben müssen (zum Beispiel wie in Szenario 3), können wir von RuleArgs vererben, um einen komplexeren Satz Parameter zu erstellen. Auf die gleiche Weise übergeben wir komplexere Daten an ein Ereignis mit EventArgs, so dass wir einem bewährten Muster folgen.
Als Endergebnis können wir eine Methode wie im Folgenden implementieren:

  Public Shared Function TextRequired( _ 
 ByVal target As Object, ByVal e As RuleArgs) As Boolean 
 If Len(CallByName(target, e.PropertyName, CallType.Get)) = 0 Then 
   Return False 
 Else 
   Return True 
 End If 
  End Function

Durch die hier verwendete CallByName-Methode können wir den Feld- oder Eigenschaftswert dynamisch auf Grundlage des Namens abrufen. Dazu verwenden wir im Hintergrund Spiegelung. Diese können wir mit gleichem Ergebnis auch manuell aufrufen. (Weitere Informationen zu dieser Technologie finden Sie in meinem englischsprachigen Artikel Mirror, Mirror)
Da Spiegelung verglichen mit dem direktem Zugriff auf ein Feld oder eine Eigenschaft langsamer ist, ist dieser Ansatz nicht immer geeignet. Trotzdem können wir durch die Verwendung von CallByName oder Spiegelung eine generische Regelmethode erstellen, die für jede Texteigenschaft ausgeführt werden kann, um die Gültigkeit des Werts zu prüfen.
Die Alternative hierzu ist, den Feld- oder Eigenschaftennamen in den Code für die Regel zu schreiben:

  Public Shared Function NameRequired( _ 
 ByVal target As Object, ByVal e As RuleArgs) As Boolean 
 Dim obj As Customer = DirectCast(target, Customer) 
 If Len(obj.Name) = 0 Then 
   Return False 
 Else 
   Return True 
 End If 
  End Function

Diese Implementierung ist zwar weniger flexibel, dafür aber schneller; statt Spiegelung zu verwenden, führen wir nur einen normalen Aufruf an die Eigenschaft aus.
Beim Implementieren von Geschäftsobjekten haben wir sogar eine weitere Option. Wir können die Regelmethode in die Geschäftsklasse einbetten. In diesem Fall hat die Methode Zugriff auf die Instanzenvariablen, die die Klasse enthält:

  Private Function NameRequired( _ 
 ByVal target As Object, ByVal e As RuleArgs) As Boolean 
 If Len(mName) = 0 Then 
   Return False 
 Else 
   Return True 
 End If 
  End Function

Da wir direkt auf Feldebene auf die Daten zugreifen, ist diese Möglichkeit sogar schneller. Die Regelmethode muss bei dieser Option innerhalb der Geschäftsklasse selbst enthalten sein.

Der Regel-Manager

Wir können jetzt Methoden zur Verarbeitung von Regeln definieren und implementieren. Wir müssen nun noch die Regeln mit unseren Daten verbinden. Hier kommt der Regel-Manager ins Spiel.
Wir integrieren in unsere Anwendung für jeden Satz Daten (DataTable, Geschäftsobjekt usw.) ein RulesManager-Objekt. Dieses Objekt verfolgt die Regeln, die auf diese Daten zutreffen, und ruft diese Regeln auf Anforderung auf.
Das RulesManager-Objekt verfolgt außerdem, welche Regeln derzeit nicht eingehalten werden. Auf diese Weise erfahren wir vom RulesManager immer, ob unsere Daten gültig sind. Wenn die Daten ungültig sind, können wir die Gründe hierfür erfahren.

Geschaeftslogik-Manager_05.gif

Abbildung 5. Beziehung zwischen "RulesManager"-Objekt, Daten, Regeln und verletzten Regeln

Denken Sie daran, dass unsere Regeln letztendlich Methoden sind und die Liste von Regeln im RulesManager eine Liste dieser Methoden ist. Für die Implementierung verwenden wir Delegaten. Ein Delegat ist im Wesentlichen ein Verweis auf eine Methode. Wenn wir für eine Methode einen Delegaten haben, können wir diesen zum Aufrufen der Methode verwenden. Wir führen also eine Liste von Delegaten, die auf die Methoden zum Implementieren der Regeln für die Daten verweisen.
Bei einem komplexen Satz Daten ist es recht wahrscheinlich, dass es viele Regeln gibt. Anstatt alle Regeln auf einmal auszuführen, möchten wir lieber nur diejenigen ausführen, die auf eine bestimmte Untergruppe der Daten zutreffen. Das häufigste Szenario ist, dass wir nur die Regeln für einen bestimmten Eigenschaftswert prüfen möchten.
Wir speichern dazu die Regeln basierend auf einem Regelname-Wert innerhalb des RulesManager-Objekts. Das bedeutet, dass wir beim Hinzufügen einer Regel zum RulesManager nicht nur die Adresse der Methode bereitstellen, die die Regel implementiert, sondern auch den Namen der Regel. Auf diese Weise können wir mehreren Regeln den gleichen Regelnamen zuordnen. Zum Beispiel:

Dim rules As New RulesManager() 
rules.AddRule(AddressOf RequiredField, "Name") 
rules.AddRule(AddressOf MaxLengthField, "Name") 
rules.AddRule(AddressOf CheckFieldFormat, "Name")

Wir setzen voraus, dass wir über die Methoden RequiredField, MaxLengthField und CheckFieldFormat verfügen, die alle Geschäftsregeln prüfen. Diesen Methoden haben wir jetzt drei Regeln mit bestimmten Namen zugeordnet. Wir können die Methoden anschließend mithilfe des RulesManager-Objekts aufrufen:

rules.CheckRules("Name")

Alternativ können alle Regeln, die mit den Daten verbunden sind, vom RulesManager-Objekt geprüft werden:

rules.CheckRules()

Zum Speichern der Regeln verwenden wir eine Hashtabelle mit ArrayLists, so dass wir die Regeln schnell namentlich suchen können. Als Schlüssel der Hashtabelle dient der Regelname. Die Tabelle enthält für jeden Regelnamen ein ArrayList-Objekt, das alle Regeln für diesen speziellen Regelnamen enthält. In unserem Beispiel oben enthält die Hashtabelle nur einen Eintrag: das ArrayList-Objekt "Name". Dieses enthält alle drei Regeln, die wir hinzugefügt haben.

Geschaeftslogik-Manager_06.gif

Abbildung 6. Die Hashtabelle enthält ein "ArrayList" mit den Regeln

Dies ist ein gängiges Entwurfsmuster für die Verarbeitung von Daten, wenn mehrere Datenelemente unter einem gemeinsamen Schlüsselwert gespeichert werden. Die Daten werden bei minimalem Overhead mit hoher Geschwindigkeit abgerufen.
Wir möchten den Overhead weiter reduzieren und verwenden daher jetzt keine Hashtabelle, sondern ein System.Collections.Specialized.HybridDictionary-Objekt.

  Private mRulesList As HybridDictionary

Hashtable-Objekte sind für große Mengen von Daten gut geeignet, nutzen aber viel Arbeitsspeicher. Bei einer kleineren Datengruppe können wir ein einfacheres Auflistungsobjekt verwenden, um weniger Arbeitsspeicher zu verbrauchen und Daten dennoch gleich schnell zu suchen. Das HybridDictionary-Objekt nutzt für kleinere Datengruppen ein einfacheres ListDictionary, für größere Datengruppen wechselt es zu einer Hashtabelle.
Wenn wir also nur Regeln für eine kleine Anzahl von Regelnamen hinzufügen, verwendet HybridDictionary intern ein ListDictionary, wenn wir dagegen viele Regelnamen verwenden, wechselt es zu einer schnelleren, aber arbeitspeicherintensiveren Hashtabelle.
Wenn wir dem RulesManager eine Regel hinzufügen möchten, suchen wir in der Hashtabelle den richtigen Eintrag, stellen sicher, dass dieser ein ArrayList enthält, und fügen die Regel hinzu. Zum Beispiel:

  Public Sub AddRule( _ 
 ByVal handler As RuleHandler, ByVal ruleName As String) 
 ' get the ArrayList (if any) from the Hashtable 
 Dim list As ArrayList = CType(RulesList.Item(ruleName), ArrayList) 
 If list Is Nothing Then 
   ' there is no list for this name - create one 
   list = New ArrayList 
   RulesList.Add(ruleName, list) 
 End If 
 ' we have the list, add our new rule 
 list.Add(New Rule(handler, ruleName, RuleArgs.Empty)) 
  End Sub

Auf diese Weise können wir einen Satz Regeln definieren, der auf ein Datenobjekt angewendet wird.

Überprüfen der Regeln

Mit der CheckRules-Methode können wir die Regeln nur für einen Regelnamen für ein Zieldatenobjekt überprüfen (die vollständige Implementierung finden Sie im Codebeispiel zum Download).

  Public Sub CheckRules(ByVal target As Object, ByVal ruleName As String) 
 ' get the ArrayList (if any) from the Hashtable 
 Dim list As ArrayList = CType(RulesList.Item(ruleName), ArrayList) 
 If list Is Nothing Then Exit Sub 
 For Each rule As Rule In list 
   If rule.Invoke(target) Then 
  If mBrokenList.Contains(rule.ToString) Then 
 ' rule was broken but is no longer, so do 
 ' appropriate work 
  End If 
   Else 
  If Not mBrokenList.Contains(rule.ToString) Then 
 ' rule was not broken but now is, so do 
 ' appropriate work 
  End If 
   End If 
 Next 
  End Sub

Wir rufen zuerst das ArrayList-Objekt aus der Hashtabelle ab. Wir setzten voraus, dass ein ArrayList vorhanden ist, und durchlaufen alle Regeln darin, indem wir jede durch ihren Delegaten aufrufen.
Um alle Regeln für unsere Daten zu überprüfen, verwenden wir eine kleine Variation zu diesem Thema:

  Public Sub CheckRules(ByVal target As Object) 
 mBrokenList.Clear() 
 For Each de As DictionaryEntry In RulesList 
   Dim list As ArrayList 
   list = CType(de.Value, ArrayList) 
   For Each rule As Rule In list 
  If rule.Invoke(target) Then 
 If mBrokenList.Contains(rule.ToString) Then 
   ' rule was broken but is no longer, so do 
   ' appropriate work 
 End If 
  Else 
 If Not mBrokenList.Contains(rule.ToString) Then 
   ' rule was not broken but now is, so do 
   ' appropriate work 
 End If 
  End If 
   Next 
 Next 
  End Sub

In diesem Fall durchlaufen wir alle Einträge in der Hashtabelle. Jeder Eintrag ist ein ArrayList-Objekt. Wir durchlaufen also anschließend alle Einträge in diesen Objekten, wobei wir alle Regeln der Reihe nach aufrufen.

Speichern von Regelverletzungen

Wenn eine Regel verletzt wird, müssen wir die Details zu dieser Regel speichern. Wie wir weiter unten noch sehen werden, kann die Benutzeroberfläche diese Daten anfordern und dem Benutzer bei Bedarf anzeigen. Zum Speichern von Übersichtsinformationen zu den verletzten Regeln definieren wir eine BrokenRuleArgs-Klasse. Die BrokenRuleArgs-Klasse enthält folgende Informationen zur Regel:

  • Regelname

  • Eigenschaftenname

  • Textbeschreibung der verletzten Regel

Es gibt verschiedene Arten von Datenobjekten. Wir können RulesManager verwenden, um Regeln für ein einfaches Geschäftsobjekt, beispielsweise Customer, einzusetzen. Oder wir verwenden RulesManager, um Regeln auf ein DataTable-Objekt oder eine Auflistung von Objekten anzuwenden. Im zweiten Fall müssen wir pro Zeile verfolgen, welche Regeln verletzt oder eingehalten werden.
Um dies zu erreichen, führen wir eine Liste über die verletzten Regeln für jedes Ziel. Beim Überprüfen von Regeln wird das spezifisch zu überprüfende Objekt als Parameter übergeben. Bei diesem Datenobjekt kann es sich um das einfache Customer-Objekt handeln, oder um ein bestimmtes DataRow-Objekt in einem DataTable.

Geschaeftslogik-Manager_07.gif

Abbildung 7. Speichern einer Liste verletzter Regeln pro Datenobjekt

Wenn wir dieses Schema verwenden, führen wir Folgendes aus, um die Liste verletzter Regeln für ein bestimmtes Zieldatenobjekt zu erhalten:

  Private Function GetBrokenRulesForTarget( _ 
 ByVal target As Object) As HybridDictionary 
 If mTargetList.Contains(target) Then 
   Return CType(mTargetList(target), HybridDictionary) 
 Else 
   Dim brokenList As New HybridDictionary 
   mTargetList.Add(target, brokenList) 
   Return brokenList 
 End If 
  End Function

Besteht zu dem Zielobjekt eine Liste verletzter Regeln, geben wir sie zurück. Ist keine Liste vorhanden, erstellen wir diese und fügen sie unserer Auflistung hinzu. Wenn wir zum Beispiel Geschäftsregeln überprüfen möchten, rufen wir zuerst die entsprechende Liste verletzter Regeln für das Zielobjekt ab:

  Public Sub CheckRules(ByVal target As Object, ByVal ruleName As String) 
 ' get the ArrayList (if any) from the Hashtable 
 Dim list As ArrayList = CType(RulesList.Item(ruleName), ArrayList) 
 If list Is Nothing Then Exit Sub 
 ' get and/or create the list of broken rules 
 ' for this target 
 Dim brokenList As HybridDictionary = GetBrokenRulesForTarget(target) 
 For Each rule As Rule In list 
 ' . . .

Mit dieser Vorgehensweise können wir die verletzten Regeln für ein einfaches Geschäftsobjekt oder für jede Zeile in einem DataTable auflisten.

Generieren von Regelbeschreibungen

Zu diesem Zeitpunkt können wir Regelmethoden implementieren, organisieren und sie nach Bedarf aufrufen. Wir müssen nun noch einen Weg finden, für verletzte Regeln benutzerfreundliche Textbeschreibungen bereitzustellen. In der BrokenRuleArgs-Klasse kann dieser Beschreibungswert gespeichert werden, aber wir müssen die Textbeschreibung an sich zur Verfügung stellen.
Jede Regelmethode setzt eine Regel durch. Daher ist es sinnvoll, die Regelbeschreibung der Methode zuzuordnen. Wir verwenden hierzu ein benutzerdefiniertes Attribut und ordnen der Regelmethode eine Beschreibung zu:

  <Description("Name is required")> _ 
  Public Shared Function NameRequired( _ 
 ByVal target As Object, ByVal e As RuleArgs) As Boolean

Dieses Attribut wird von der DescriptionAttribute-Klasse definiert:

<AttributeUsage(AttributeTargets.Method)> _ 
Public Class DescriptionAttribute 
  Inherits Attribute 
  Private mText As String = "" 
  Public Sub New(ByVal description As String) 
 mText = description 
  End Sub 
  Public Overrides Function ToString() As String 
 Return mText 
  End Function 
End Class

Das AttributeUsage-Attribut spezifiziert dabei, dass unser Description-Attribut nur auf Methoden zutrifft. Anschließend können wir Spiegelung verwenden, um das Attributobjekt von jeder Methode mit einem Code wie in diesem Beispiel abzurufen:

 Private Function GetDescription( _ 
   ByVal handler As RuleHandler) As String 
   Dim attrib() As Object = _ 
  handler.Method.GetCustomAttributes( _ 
  GetType(DescriptionAttribute), False) 
   If attrib.Length > 0 Then 
  Return attrib(0).ToString 
   Else 
  Return "{2}.{0}:<no description>" 
   End If 
 End Function

Die Method-Eigenschaft eines Delegatenobjekts gibt ein MethodInfo-Objekt zurück, das die Methode beschreibt, auf die der Delegat verweist. Die GetCustomAttributes-Methode gibt ein Array zurück, das alle passenden Attribute der Methode enthält. Wenn in dem Array ein Description-Attribut zurückgegeben wird, verwenden wir dessen ToString-Methode, um den Textwert zu erhalten. Ist kein passendes Attribut vorhanden, generieren wir eine Standardbeschreibung für die Regel.
Wie wir gesehen haben, sind einige Regelmethoden sehr spezifisch, während andere generische Methoden CallByName oder Spiegelung verwenden. Für eine sinnvolle Beschreibung müssen wir den Text für diese generischen Regeln anpassen. Wir verwenden dafür ein Tokenersetzungsschema mit den folgenden Token:

{0}

Regelname

{1}

Eigenschaftenname

{2}

Name des Datenobjekttyps

{3}

ToString-Wert des Datenobjekts


.NET Framework bietet erfreulicherweise mit der String.Format()-Methode direkte Unterstützung für diese Tokenersetzung. Daher können wir den Text des <Description()>-Attributs nehmen und folgendermaßen formatieren:

  Return String.Format(mDescription, _ 
 RuleName, _ 
 RuleArgs.PropertyName, _ 
 TypeName(target), _ 
 target.ToString)

Als Ergebnis können wir Regelmethoden wie diese deklarieren:

  <Description("{0} is required")> _ 
  Public Shared Function TextRequired( _ 
 ByVal target As Object, ByVal e As RuleArgs) As Boolean

Wenn wir als Regelnamen einen benutzerfreundlichen Wert (wie Name) voraussetzen, wird eine benutzerfreundliche Beschreibung der Regel generiert.
Wird in einer der CheckRules-Methoden eine Regelverletzung erkannt, rufen wir den Beschreibungstext ab und formatieren ihn, so dass die Benutzeroberfläche ihn dem Benutzer anzeigen kann.

Offenlegen von Regelverletzungen

Wie legen wir jetzt für die Benutzeroberfläche offen, dass Regeln verletzt bzw. nicht verletzt wurden? Wir verwenden hierfür sowohl Ereignisse als auch Methoden.
Durch die RuleBroken- und RuleNotBroken-Ereignisse können wir die Benutzeroberfläche informieren, sobald sich der Status der Regeln ändert.

  Public Event RuleBroken( _ 
 ByVal target As Object, ByVal rule As BrokenRuleArgs) 
  Public Event RuleNotBroken( _ 
 ByVal target As Object, ByVal rule As BrokenRuleArgs)

Diese Ereignisse können außerdem Parameterdaten mit Informationen für die Benutzeroberfläche übergeben; welche Regel verletzt bzw. nicht verletzt wurde, sowie die Beschreibung dieser Regel.
Aufgrund dieser Informationen kann der Benutzeroberflächen-Code die Anzeige für den Benutzer ändern und ihm visuelle Hinweise zum Ablauf geben. Sie können das ErrorProvider-Steuerelement verwenden, um solche Hilfen zu implementieren, Farben von Steuerelementen ändern oder beliebige andere Elemente verändern, die der Entwickler der Benutzeroberfläche auswählt.
Ein Ereignismodell ist zwar praktisch, aber die Benutzeroberfläche kann auch Zugriff auf die vollständige Liste der derzeit verletzten Regeln benötigen, zum Beispiel um dem Benutzer die Liste mit allen verletzten Regeln auf einmal anzuzeigen.
Wir legen einige Methoden offen, um diese Anzeige zu unterstützen. Die erste gibt ein Array von String-Werten zurück, die die Beschreibungen aller verletzten Regeln enthalten.

  Public Function GetBrokenRuleDescriptions() As String() 
 Dim out(mBrokenList.Count - 1) As String 
 Dim index As Integer 
 For Each de As DictionaryEntry In mBrokenList 
   out(index) = de.Value.ToString 
   index += 1 
 Next 
 Return out 
  End Function

Im RulesManager enthält ein HybridDictionary-Objekt eine Liste der verletzten Regeln. Wie bereits erläutert, stellt ein BrokenRulesArgs-Objekt jede verletzte Regel dar. Auf diese Weise können wir alle wichtigen Informationen zu jeder verletzten Regel in einem einfachen Auflistungsobjekt aufführen.
Wenn wir nur die Beschreibungen zurückgeben möchten, durchlaufen wir die Liste der verletzten Regeln und kopieren jede Regelbeschreibung in das Array. Das resultierende Array wird dann an den aufrufenden Code zurückgegeben.
Falls die Benutzeroberfläche alle Details benötigt, stellen wir auch eine Methode zur Verfügung, die ein ArrayList bereitstellt, das alle zurzeit verletzten Regeln enthält:

  Public Function GetBrokenRules() As ArrayList 
 Dim out As New ArrayList 
 For Each de As DictionaryEntry In mBrokenList 
   out.Add(de.Value) 
 Next 
 Return out 
  End Function

Das ArrayList, das sich ergibt, enthält für jede aktuell verletzte Regel ein BrokenRuleArgs-Objekt. Der aufrufende Code kann auf den Regelnamen, den Eigenschaften-Namen und die Beschreibung jeder Regel nach Bedarf zugreifen.

Schlussfolgerung

Verwalten und Zentralisieren von Geschäftslogik, insbesondere von Validierungscode, ist in den meisten Anwendungen von großer Bedeutung. Erstellen und Pflegen von Software wird durch zentralisierte Geschäftslogik einfacher.
Regelmodule stellen eine Methode zum Zentralisieren von Geschäftsregeln innerhalb einer Anwendung dar. Der Regel-Manager dieses Artikels ist ein einfaches, aber effektives Beispiel eines Regelmoduls für Gültigkeitsregeln. Diese Regeln können auf praktisch jedes Datenobjekt angewendet werden, einschließlich DataTable-Objekte und Geschäftsobjekte.
Hoffentlich können Sie die RulesManager-Klasse verwenden oder ändern, um sie für Ihre Umgebung anpassen. Die Zentralisierung der Validierungslogik unterstützt Sie auf jeden Fall dabei, Ihre Software zu verbessern.


Anzeigen: