Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
ValidationRules und Attribute zur Validierung
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 7533
Herkunft: Waidring

Themenstarter:

ValidationRules und Attribute zur Validierung

beantworten | zitieren | melden

Hallo,

Anmerkung: im weiteren Verlauf des Threads gibt es dann Infos zu Aktualisierungen und Beispiel für deren Verwendung.

Wer mit Daten arbeitet muss diese (früher od. später) auch validieren, sprich prüfen ob sie dem erwarteten Format entsprechen, innerhalb des erwarteten Wertebereichs liegen, etc. Hierzu bietet das .net Framework die IDataErrorInfo-Schnittstelle die vorzugsweise in Datenbindungs-Szenarien verwendet wird.

Jeder der damit schon mal gearbeitet hat und den trivialen Ansatz verfolgte wird sich vllt. auch überlegt haben ob es nicht doch eine elegantere Möglichkeit als im jeweiligen Eigenschafts-Setter eine Prüfung durchzuführen wie es die MSDN im IDataErrorInfo Beispiel von Silverlight zeigt oder durch ein if/switch auf dem Eigenschaftsnamen im Indexer von IDataErrorInfo wie es WPF IDataErrorInfo and Databinding zeigt. (Beide Links sind zwar für WPF bzw. Silverlight, für WinForms gilt das aber analog).

In [Artikel] Attribute zur Prüfung von Properties verwenden hat herbivore schon eine Möglichkeit gezeigt wie das Problem eleganter und v.a. deklarativer gelöst werden kann. Hier stelle ich eine Komponente vor, die ebenfalls mit Attributen zur Validierung arbeitet, im Hintegrund aber ganz anders aufgebaut ist.

Die zentrale Klasse für die Verwendung ist DomainObject von dem, wie der Name suggerieren mag, jedes Domänen-Objekt ableiten muss. Eine Beispiel-Klasse schaut dann wie folgt aus:


public class Customer : DomainObject
{
    [StringNotBlank]
    public string Name { get; set; }
    //---------------------------------------------------------------------
    [Email]
    public string Email { get; set; }
}
Sie besitzt zwei (automatische) Eigenschaften die mit Attributen für die Validierung versehen wurde. Da DomainObject IDataErrorInfo implementiert ist diese Klasse in einem Datenbindungszenario schon einsetztbar (abgesehen von INotifyPropertyChanged). Wenn der Einsatz außerhalb von Datenbindung vorgesehen ist, dann kann in den Attributen optional angeben werden (mit ThrowOnInvalid = true), ob ein Laufzeitfehler bei negativer Validierung geworfen wird.

Vordefiniert sind ein paar Attribute:
  • StringLengthAttribute -> Zeichenfolgenlänge innerhalb min/max
  • StringNotNullAttribute -> Zeichenfolge darf nicht null sein
  • StringNotBlankAttribute -> Zeichenfolge darf nicht null od. leer sein
  • RangeAttribute -> int muss innerhalb eines bestimmten Intervalls (inklusiv) sein
  • GreaterOrEqualZeroAttribute -> int muss positiv inkl. 0 sein
  • RegexAttribute -> validierte eine Zeichenfolge gegen das angegeben Muster
  • EmailAttribute -> validiert ob eine Zeichenfolge einer Email-Adresse entspricht

Im Hintergrund sieht das Ganze so aus wie im angehängten Bild. Für die Verwendung mit den Attributen gibts die Klassen die von ValidationAttribute erben. Die Klassen, die von ValidationRule erben, erledigen die eigentliche Arbeit und können auch selbt, also ohne die Verwendung von Attributen, verwendet werden. Dies kann z.B. angewandt werden wenn die ValidierungsRegeln aus der Konfigurations-Datei od. einer Datenbank geladen werden (das sich aber auch mit eigenen Attributen erreichen lässt).

Eine Wiedergabe des Codes erspare ich mir hier, da zum einem der Code angehängt ist und zum anderen zeige ich lieber wie die bestehenden Klassen verwendet und erweitert werden können (um eigene Attribute und Regeln erstellen zu können).


Verwenden von Validierungsregeln außerhalb von DomainObject:
Die Validierungsregeln, jene Klassen die von ValidationRule erben, können auch ganz ohne DomainObject verwendet werden. Nachfolgend ein Beispiel das die Verwendung der konkreten Klasse DelegateValidationRule<T> zeigt, wobei T der Typparameter für die zu validierende Eigenschaft ist.
Für das Beispiel wird folgende Klasse angenommen:


public class Person
{
    public int Age { get; set; }
}


[Test]
[TestCase(true, ExpectedException = typeof(Exception))]
[TestCase(false)]
public void IsValid_ThrowOnInvalid_ThrowsExceptionWhenSet(bool throwOnException)
{
    Person p = new Person { Age = -1 };

    DelegateValidationRule<int> rule = new DelegateValidationRule<int>(
        this.IntValidate,
        () => p.Age);

    rule.ThrowOnInvalid = throwOnException;

    Assert.IsFalse(rule.IsValid("Age"));
}
//---------------------------------------------------------------------
private bool IntValidate(int value, out string msg)
{
    msg = "Wert muss ≥ 0 sein.";
    return value ≥ 0;
}
Die Klasse DelegateValidationRule<T> verlangt einen Delegaten der auf eine Methode zeigt welche die eigentliche Validierung durchführt und eine Lambda-Expression welche die zu validierende Eigenschaft des Objekts wählt. Die Methode für die eigentliche Validierung - im Beispiel IntValidate - kann auch eine angepasste Zeichenfolge als out-Parameter zurückgegeben um den User eine genaue Beschreibung zu geben was falsch ist bzw. was dazu geführt hat dass die Validierung fehl geschlagen ist.


Verwenden von Validierungsregeln mit DomainObject:
Die Valdierungsregeln können mit einer von DomainObject abgeleiteten Klasse verwendet werden indem die CreateRules-Methode überschrieben wird.


public class User : DomainObject
{
    public string Email { get; set; }
    //---------------------------------------------------------------------
    protected override List<ValidationRule> CreateRules()
    {
        EmailValidationRule emailRule = new EmailValidationRule(() => this.Email);

        List<ValidationRule> rules = base.CreateRules();
        rules.Add(emailRule);
        return rules;
    }
}
base.CreateRules lädt automatisch alle Reglen, die als Attribute für die Properties des Domänen-Objects angegeben sind, und liefert diese als Liste. Möchte man nicht nur einzelne Regeln ergänzen, sondern alle Regeln selbst erstellen, muss man anstatt mit base.CreateRules mit new List<ValidationRule>() eine leere Liste erstellen.

Üblicherweise braucht man CreateRules gar nicht zu überschreiben, sondern wird alle Regelen einfach deklarativ durch die Attribute vorgeben. Mit dem Überschreiben von CreareRules wird es jedoch möglich, die Regeln stattdessen oder ergänzend zur Laufzeit z.B. aus der Konfiguration oder einer Datenbank laden.


Verwenden von Attributen für die Validierung:
Das ist sicherlich die eleganteste und einfachste Art und Weise eine Validierung für eine Eigenschaft zu erstellen.


public class User : DomainObject
{
    [StringLength(1, 100)]
    public string FullName { get; set; }

    [StringLength(1, 100)]
    public string UserName { get; set; }

    [Range(1, 150)]
    public int Age { get; set; }

    [Email]
    public string Email { get; set; }
}
Sonst ist kein weiterer Benutzer-Code notwendig um die Validierungen zu erstellen. Die Attribute werden durch das Erben von DomainObject automatisch als Regeln zur Validierung verwendet.
Als Fehler-Meldungen, wenn die Validierung einer Eigenschaft fehlschlägt, werden je nach Eingabe passende Texte lokalisiert (derzeit Deutsch und Englisch) ausgegeben. Beispielsweise wenn der Wert der Age-Eigenschaft 160 ist dann wird angepasst "Der Wert 160 ist größer als der Maximalwert 150." vom IDataErrorInfo.Indexer["Age"] zurückgegeben.

Es ist auch möglich einen eigenen konstanten Standardtext für Validierung bereit zustellen, der dann verwendet wird wenn die Validierung fehlschlägt.


public class User : DomainObject
{
    [Range(1, 150, Message = "Die eingegeben Zahl liegt nicht im Bereich von 1-150.")]
    public int Age { get; set; }
}

Wenn gewünscht ist dass ein Laufzeitfehler erzeugt wird so kann dies ebenfalls in der Deklaration des Attributes angegeben werden.


public class User : DomainObject
{
    [Range(1, 150, ThrowOnInvalid = true)]
    public int Age { get; set; }
}

Die Message- und ThrowOnInvalid-Eigenschaft können auch kombiniert werden und gelten für alle Attribute die von ValidationAttribute erben, also auch für alle die standardmäßig dabei sind.


Erstellen eines eigenen Attributes für die Validierung:
Auf eine Einführung in Attribute wird verzichtet und stattdessen auf [Artikel] Attributbasierte Programmierung verwiesen.
Als Beispiel zeige ich wie ein Attribut für Regex-Validierung implementiert werden kann.


[AttributeUsage(AttributeTargets.Property)]
public class RegexAttribute : ValidationAttribute
{
    private readonly string       _pattern;
    private readonly RegexOptions _regexOptions;
    //---------------------------------------------------------------------
    public RegexAttribute(string pattern, RegexOptions regexOptions)
    {
        _pattern      = pattern;
        _regexOptions = regexOptions;
    }
    //---------------------------------------------------------------------
    protected override ValidationRule CreateRule(
        string   propertyName,
        Delegate propertySelector)
    {
        return new RegexValidationRule(
            _pattern,
            propertyName,
            (Func<string>)propertySelector)
            {
                RegexOptions   = _regexOptions,
                Message        = this.Message,
                ThrowOnInvalid = this.ThrowOnInvalid
            };
    }
}
Der Konstruktor sollte soweit klar sein. In der überschriebenen Methode CreateRule "wird das Attribut zur ValidationRule". In diesem Fall gibt es schon die RegexValidationRule (die andere bereits oben vorgestellte ist die DelegateValidationRule<T>) und von dieser wird eine Instanz erstellt und die nötigen Informationen mitgegeben. Wichtig dabei ist dass der Delegate propertySelector in den konkreten Typ der Func<T> gecastet wird - bei Regex macht nichts anderes als string Sinn ;-) Auch hier ist nicht mehr Benutzer-Code notwendig, den Rest erledigt die Infrastruktur.

Basierend vom RegexAttribute ist es dann sehr einfach weitere Regex-basierte Regeln zu erstellen. Z.B. für die Email-Validierung:


[AttributeUsage(AttributeTargets.Property)]
public sealed class EmailAttribute : RegexAttribute
{
    public EmailAttribute()
        : base(@"[A-Z0-9._%+-][email protected][A-Z0-9.-]+\.[A-Z]{2,4}", RegexOptions.IgnoreCase)
    {
        this.Message = Strings.EmailIsNotValid;
    }
}
Das reicht schon und eine neue Regel existiert. Ich find das cool :-) Durch die Angabe der Message im Konstruktor ist es auch möglich, lokalisierte Meldungen zurückzugeben, wenn die Validierung fehlschlägt.
Über den Pattern für die Email könnte man diskutieren, aber bitte nicht hier in diesem Thema.


Erstellen einer eigenen ValidationRule:
Abschließend zeige ich noch wie eigene Validierungsregelen durch Ableiten von ValidationRule od. besser von ValidationRule<T> erstellt werden können. Gezeigt wird dies anhand der RegexValidationRule.


public class RegexValidationRule : ValidationRule<string>
{
    public RegexOptions RegexOptions { get; set; }
    protected string    Pattern      { get; private set; }
    public string       Message      { get; set; }
    //---------------------------------------------------------------------
    public RegexValidationRule(
        string pattern,
        string propertyName,
        Func<string> propertySelector)
        : base(propertyName, propertySelector)
    {
        this.Pattern = "^" + pattern + "$";
    }
    //---------------------------------------------------------------------
    protected override bool Validate(string value, out string message)
    {
        message = this.Message ?? Strings.RegexNoMatch;
        return value != null && Regex.IsMatch(value, this.Pattern, this.RegexOptions);
    }
}
Im Konstruktor wird der entsprechende Konstruktor der abstrakten Basisklasse aufgerufen und die weiteren regelspezifischen Felder/Eigenschaften gesetzt (hier: die Pattern-Eigenschaft). Weiters ist die abstrakte Valdiate-Methode zu überschreiben. Diese gibt ein bool zurück, der angibt ob die Validierung positiv/negativ war, und besitzt ein out-Argument mit dem eine spezifische Meldung zurückgegeben wird. Im obigen Code wird der Wert für message unabhängig vom Ergebnis der Validierung gesetzt. Das ist kein Problem, denn die Infrastruktur verwendet den Wert von message nur dann, wenn die Validierung negativ ausgefallen ist.

Für weitere Beispiele verweise ich auf die Unit-Tests (im Anhang des nächsten Beitrags).


Ganz besonders bedanke ich mich bei herbivore für das Begutachten des ersten Entwurfs der Komponente.
Wenn ihr eigene Attribute erstellt, würde ich mich freuen, wenn diese hier gepostet werden können - vllt. kann jemand anders diese dann ja auch gebrauchen.


mfG Gü

Schlagwörter: Validierung, validieren, IDataErrorInfo, Attribute, Speedski, DomainObject
Attachments
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 7533
Herkunft: Waidring

Themenstarter:

beantworten | zitieren | melden

Anhang:
Angehängt die Visual Studio 2010-Solution mit dem ganzen Quellcode, inkl. Unit-Tests, Demos für WinForms* und WPF und einer CHM-Doku. Folgende Binaries liegen vorkompiliert dabei:
  • gfoidl.ValidationRules.dll -> .net 4.0
  • gfoidl.ValidationRules.35.dll -> .net 3.5
  • gfoidl.ValidationRules.Silverlight.dll -> Silverlight 4
Bei allen Assemblies ist die entsprechende XML-Kommentardatei dabei, so dass Intellisense auch damit umgehen kann.

Aktuelle Version: 1.0.19.0

* da ich schon lange nix mehr mit WinForms gemacht habe kann sein dass es noch eleganter geht.


Downloadzähler (da er beim Aktualisieren zurückgesetzt wird) = 15 + 8 + 13
CRC-32: AAD2EE84
MD-5: E660F830FAB7D329EA86723AB6163113
Attachments
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
Sarc
myCSharp.de - Member



Dabei seit:
Beiträge: 426

beantworten | zitieren | melden

Hallo,

sieht nett aus. Aber wieso hast Du dich gegen die vorhandene Funktionalität aus System.ComponentModel.DataAnnotations entschieden?
Es ist zwar primär für Asp.NET MVC ausgelegt, aber lässt sich auch anderweitig nutzen. Schön ist auch, dass die Validierungssachen in Meta-Klassen ausgelagert werden können, z.b wie im Beispiel hier: RegularExpressionAttribute
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 7533
Herkunft: Waidring

Themenstarter:

beantworten | zitieren | melden

Hallo Sarc,

ich hab das zwar vorher kurz angeschaut, aber es gab für mich zuviele offene Fragen so dass ich es lieber gleich selbst gemacht habe.

Hier ein paar Punkte - ohne zwingende Reihenfolge:
  • Internationalisierung ist nicht möglich - da die Attribute zur Entwicklungszeit festgelegt werden und somit auch die ErrorMessage. Bei meiner Lösung kann die Message zur Laufzeit lokalisiert erstellt werden.
  • Ich hab keine Ahnung wer dort die Validierung durchführt und wahrscheinlich existiert dann eine Abhängigkeit zu ASP.NET Dynamic Data. Diese Abhängigkeit will ich vermeiden. Bei meiner Lösung gibt es keine Abhängigkeit (außer jenen .net-Assemblys die man ohnehin braucht ;-)).
  • Erweitern lässt sich die andere Lösung auch nicht (so gut). ZB wenn Grenzwerte für eine Regel aus einer Datenbank geladen werden müssen. Bei meiner Lösung kein Problem -> abgeleitete Klasse erstellen die das erledigt.
  • In meine Lösung brauch ich mich nicht mehr einarbeiten, in die andere schon :-)
Das sind ein paar Punkte warum ich das selbst gemacht habe.


mfG Gü
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
malignate
myCSharp.de - Member

Avatar #avatar-3206.png


Dabei seit:
Beiträge: 751

beantworten | zitieren | melden

Hi,

zu System.DataAnnotations:

1. Die Data Annotations werden afaik auch von WPF und Silverlight direkt unterstützt.

2. Lokalisierung ist möglich, siehe ErrorMessageResourceType und ErrorMessageResourceName

3. Man kann eigene Attribute machen -> Erweiterbar

4. Validiert wird mit Hilfe des DataValidator's (selbe Assembly):


ValidationContext validationContext = new ValidationContext(this, null, null);

List<ValidationResult> validationResults = new List<ValidationResult>();

Validator.TryValidateObject(this, validationContext, validationResults, true);
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15618
Herkunft: BW

beantworten | zitieren | melden

Hi Gü,

klasse Klasse, die Du da hast ;-)

Zwei Anmerkung
RangeValidator: Angeben, ob die beiden Zahlen noch zum Range dazu gehören, oder nicht.
MustBePositive ergibt sich aus 0, int.Max ;-)


Zitat von malignate
3. Man kann eigene Attribute machen -> Erweiterbar

Hinzu kommt, dass nicht nur der inhaltiche Wert kontrolliert werden kann, sondern es ist im Zusammenhang mit IValidatableObject auch die logische Validierung möglich.
Wenn man nun noch ein bisschen OOD betreibt, kann man sein Objekt auch manuell - ohne die Automatisierung von MVC - validieren.
Alternativ alles über IValidatableObject prüfen lassen, dass ist sogar deutlich schneller als der Weg über die Attribute ;-)
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 7533
Herkunft: Waidring

Themenstarter:

beantworten | zitieren | melden

Hallo zusammen,

ich hab ein paar Sachen noch geändert:
  • StringNotNullAttribute hinzugefügt das prüft ob ein String null ist od. nicht
  • MustBePositiveAttribute umbenannt in GreaterOrEqualZeroAttribute da es sprechender ist
  • Beschreibung korrigiert (Kommas, etc.)
ist oben editiert.


Hallo Abt,

@RangeValidator: habs oben auch hinzugeschrieben. Danke. In der Parameter-Info für den Konstruktor war es aber schon dabei.
@MustBePositive: klar, das ist Wiederverwendung :-)


Hallo zusammen (nochmal),

Dass mit ASP.net MVC und für .net 4.0 dort Unterstützung mit aufgenommen wurde kann ein nicht "ASP.net MVCler" ja nicht unbedingt wissen. Außerdem ist das erst aber .net 4.0 vorhanden während meine Lösung auch für .net 3.5 möglich ist bzw. wenn im Code die paar Linq-Passagen durch foreach ersetzt werden auch ab .net 2.0. Daher sollten wir das Thema hier in Komponenten nicht weiter vertiefen.



mfG Gü
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 7533
Herkunft: Waidring

Themenstarter:

beantworten | zitieren | melden

Hallo zusammen,

ich hab noch weitere Attribute hinzugefügt:
  • NotNullAttribute -> die Referenz darf nicht null sein
  • InAttribute -> der Wert muss In der Prüfmenge enthalten sein, ähnlich wie bei SQL der IN-Ausdruck

Ich denke das NotNullAttribute sollte klar sein - daher zeig ich kein Beispiel für die Verwendung.


InAttribute:
Das InAttribute verwendet intern die InValidationRule und als Prüfmenge kann jedes IEnumerable<T> verwendet werden.

Beispiel mit direkt im Attribut angegeben Werten:


public class MyClass : DomainObject
{
    // Der Eigenschaftswert darf nur diese Werte annehmen:
    [In(1, 3, 5, 7, 9, 10)]
    public int MyIntProperty { get; set; }

    // Der Eigenschaftswert darf nur diese Werte annehmen:
    [In("FAQ", "Artikel", "Buchempfehlungen", "Buchshop")]
    public string myCSharpKnowledgeBase { get; set; }
}

Da Attribute in .net nicht generisch sein dürfen hab ich nur int und string mit einem typisierten Konstruktor versehen. Für alle anderen Typen gibt es eine weitere Überladung die ich an einem Beispiel zeigen will.
Nehmen wir hierzu an es gibt eine Adresse-Klasse mit einer PLZ-Eigenschaft. Diese Eigenschaft soll gegen zulässige PLZ-Werte, die aus einer Datenbank geladen werden, validiert werden.


public class Adresse : DomainObject
{
    [StringNotBlank]
    public string Ort { get; set; }

    [In(typeof(AdressenRepository), "LadePLZ")]
    public string PLZ { get; set; }
}
Hier gibt das InAttribute den Typ an der die "LadePLZ"-Methode hat. Diese Methode kann public od. private sein, wichtig ist nur dass es eine Instanzmethode in dem angegegen Typ ist und dass der Rückgabewert IEnumerable<T> ist.

Hab oben den Anhang getauscht. Dieses Update ist abwärtskompatibel mit der vorigen Version.

mfG Gü
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 7533
Herkunft: Waidring

Themenstarter:

beantworten | zitieren | melden

Hallo zusammen,

ein paar Attribute, von den ich finde dass sie hilfreich sein können, hab ich noch ergänzt. Hinzugekommen sind:
  • Enum- und Flags-Unterstützung beim InAttribute. Es können auch Werte ausgeschlossen werden.
  • Beim InAttribute kann optional ein IEqualityComparer<T> angegeben werden.
  • NotInAttribute für Blacklist-Szenarien hinzugefügt.
  • DateRangeAttribute hinzugefügt zum Validieren von Datum-Bereichen.
  • Die Meldungen bei negativer Validierung konstruktiver und lösungsorientierter gestaltet (wenn sie es nicht schon waren).
  • IsPrimeAttribute hinzugefügt das prüft ob eine Ganzzahl (int) eine Primzahl ist oder nicht.
  • Pro Eigenschaft können jetzt auch mehrere Attribute angegeben werden, so dass mehrer Regeln für eine Eigenschaft verwendet werden können.


InAttribute mit Enums:
Für die Demonstration nehmen wir folgende Enumeration an:


public enum MyTestEnum
{
    One,
    Two,
    Three
}
Nun ist es möglich eine Domänen-Klasse wie folgt zu deklarieren:


public class EnumTestObject : DomainObject
{
    [In(typeof(MyTestEnum))]
    public int Number { get; set; }

    [In(typeof(MyTestEnum))]
    public string Name { get; set; }

    [In(typeof(MyTestEnum), Without = (int)MyTestEnum.Two)]
    public int EnumWithout { get; set; }
}
Wie zu sehen ist kann bei Enumerationen das InAttribute auf Eigenschaften vom Typ int und string gesetzt werden. Bei int muss der Zahlenwert der Eigenschaft einer Konstante in der Enumeration entsprechen, bei einer string Eigenschaft muss der Wert einem Namen in der Enumeration entsprechen.
D.h. die int-Eigenschaft Number aus der Beispielklasse darf nur die Werte {0, 1, 2} annehmen, während die string-Eigenschaft Name die Werte {"One", "Two", "Three"} annehmen darf.

Für die Eigenschaft EnumWithout wird festgelegt dass jeder Wert außer MyTestEnum.Two angenommen werden darf.


InAttribute mit Flags-Enum:
Es sei das folgende Flag angenommen:


[Flags]
public enum MyFlags
{
    None = 0,
    One  = 1,
    Two  = 2,
    Four = 4
}
Dann kann eine Klasse wie folgt deklariert werden:


public class FlagsTestObject : DomainObject
{
    [In(typeof(MyFlags))]
    public int FlaggedNumber { get; set; }

    [In(typeof(MyFlags), Without = (int)(MyFlags.One | MyFlags.Two))]
    public int FlagsWithout { get; set; }

    [In(typeof(MyFlags), Without = (int)MyFlags.None)]
    public int FlagsWithoutNone { get; set; }
}
Die Verwendung ist ident zur Verwendung mit "normalen" Enums. Wie bei der Eigenschaft FlagsWithout zu sehen ist gibt es auch Unterstützung für mit OR (|) kombinierte Flag-Werte.


InAttribute und IEqualityComparer<T>:
Die InValidationRule<T> unterstützte schon seit der letzten Version die Angabe eines IEqualityComparer<T>, ab nun wird dies auch vom InAttribute unterstützt.
Als Beispiel wird gezeigt wie eine String-Eigenschaft ohne Berücksichtung von Groß-/Kleinschreibung validiert werden kann:


public class StringTestObject : DomainObject
{
    [In("Anton", "Berta", "Cäsar", "usw.", EqualityComparerType = typeof(StringEqualityComparer))]
    public string Name { get; set; }
}
//---------------------------------------------------------------------
private class StringEqualityComparer : EqualityComparer<string>
{
    public override bool Equals(string x, string y)
    {
        return x.ToLower() == y.ToLower();
    }
    //-----------------------------------------------------------------
    public override int GetHashCode(string obj)
    {
        return obj.ToLower().GetHashCode();
    }
}
Die Klasse, welche IEqualityComparer<T> bzw. besser EqualityComparer<T> implementiert, kann sowohl public als auch private sein (nur internal geht nicht ;-)).


NotInAttribute:
Das NotInAttribute ist von der Verwendung ähnlich dem InAttribute, nur dass keine Enums-/Flags-Unterstützung vorhanden ist. Dies deshalb da sich der Großteil der praktischen Fälle bereits mit Without beim InAttribute abdecken lässt. Da die Verwendung jener vom InAttribute gleicht verzichte ich hier auf ein Beispiel.
Mit dem NotInAttribute darf der Wert nicht in der angegeben IEnumerable<T> enthalten sein, so dass sich "Blacklists" umsetzen lassen.

Damit das NotInAttribute umgesetzt werden konnte wurde die InValidationRule<T> um die TreatAsBlacklist-Eigenschaft erweitert.


DateRangeAttribute:
Da es oft praktisch den zulässigen Bereich den ein Datum annehmen kann einzuschränken gibts dafür jetzt auch ein Attribut. Dabei sind folgende Einschränkungen möglich:
  • MinDate -> untere inklusive Datumsgrenze
  • MaxDate -> obere inklusive Datumsgrenze
  • OnlyInPast -> das Datum muss in der Vergangenheit (exklusive jetzt) liegen
  • OnlyInFuture -> das Datum muss in der Zukunft (exklusive jetzt) liegen
  • MinDate und MaxDate -> Datumsbereich mit jeweils inklusiven Grenzen
  • MinDate und OnlyInPast -> das inklusive Mindestdatum und das Datum muss in der Vergangenheit liegen
  • MaxDate und OnlyInFuture -> das Datum muss in der Zukunft liegen und ist nach oben (inklusiv) begrenzt
Nachfolgend eine Klasse welche die Verwendung zeigt:


public class MyTestObject : DomainObject
{
    [DateRange(MinDate = "1982-01-01")]
    public DateTime DateForMinTest { get; set; }

    [DateRange(MaxDate = "2011-01-01")]
    public DateTime DateForMaxTest { get; set; }

    [DateRange(OnlyInPast = true)]
    public DateTime DateForOnlyPastTest { get; set; }

    [DateRange(OnlyInFuture = true)]
    public DateTime DateForOnlyFutureTest { get; set; }

    [DateRange(MinDate = "2000-01-01", MaxDate = "2010-01-01")]
    public DateTime DateForMinMaxTest { get; set; }

    [DateRange(MinDate = "1982-01-01", OnlyInPast = true)]
    public DateTime DateForMinAndOnlyInPastTest { get; set; }

    [DateRange(MaxDate = "2100,1,1", OnlyInFuture = true)]
    public DateTime DateForMaxAndOnlyInFutureTest { get; set; }
}
Wie zu sehen ist muss das Datum als String angegeben werden, da Attribute nur die Angabe von Literalen erlauben.
Wird eine nicht unterstützte Kombination angegeben so wird eine InvalidOperationException geworfen.


IsPrimeAttribute:
Dieses Attribute prüft ob die Ganzzahl (höchstwahrscheinlich) eine Primzahl ist. Das höchstwahrscheinlich kommt daher da eine Monte-Carlo-Methode für den Primzahl-Test basierend auf dem "kleinen Fermat" verwendet wird.
Die Verwendung ist recht aufwändig :-)


public class MyTestObject : DomainObject
{
    [IsPrime]
    public int Number { get; set; }
}


Kombination von Attributen:
Ab der jetzigen Version können auch Attribute kombiniert werden. Z.B.:


public class IntTestObject : DomainObject
{
    [IsPrime]
    [In(1, 3, 5, 7, 9)]
    public int Number { get; set; }

    [IsPrime, In(1, 3, 5, 7, 9)]
    public int AnotherNumber { get; set; }
}
Es werden 2 Möglichkeiten zur Angabe gezeigt. In Number werden die Attribute explizit übereinander angegeben, während in AnotherNumber die Attribute als "Einzeiler" angegeben werden. Beides führt zum selben Ergebnis.

Solange es der Compiler erlaubt* sind beliebige Attribut-Kombinationen möglich. Über die Sinnhaftigkeit möglicher Kombinationen muss sich der Verwenden selbst Gedanken machen.

* der Compiler gestattet nicht 2x dasselbe Attribute anzugeben. So ist es z.B. nicht möglich im obigen Beispiel 2x das InAttribute anzugeben.



Da ich jetzt finde dass die wichtigsten Bereiche abgedeckt sind werde ich den Release-Zyklus massiv verlangsamen und nur mehr Aktualisierungen posten wenn sie wirklich essentiell sind. Sollten jedoch Bugs enthalten (wovon ich nicht ausgehe ;-)) werde ich diese natürlich sofort beheben.


Hab oben den Anhang getauscht. Dieses Update ist abwärtskompatibel mit der vorigen Version.


mfG Gü
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers
itstata
myCSharp.de - Member



Dabei seit:
Beiträge: 306
Herkunft: Rostock

beantworten | zitieren | melden

Sorry, aber meine Ansicht nach hast du hier das Rad neu erfunden (siehe Post von malignate).
private Nachricht | Beiträge des Benutzers
gfoidl
myCSharp.de - Team

Avatar #avatar-2894.jpg


Dabei seit:
Beiträge: 7533
Herkunft: Waidring

Themenstarter:

beantworten | zitieren | melden

Hallo itstata,

hast du auch alles gelesen, insbesondere meine graue Antwort in ValidationRules und Attribute zur Validierung?

Und mein Rad ist runder als alle anderen Räder


mfG Gü
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
private Nachricht | Beiträge des Benutzers

Moderationshinweis von herbivore (14.06.2012 - 11:26:22):

... womit das Thema Not-invented-here-Syndrom als abschließend behandelt angesehen werden sollte. Jeder kann und muss für sich selbst entscheiden, ob der die die Klassen aus dem .NET-Framework oder von gfoidl verwenden will.

bCoderer
myCSharp.de - Member



Dabei seit:
Beiträge: 101

beantworten | zitieren | melden

Servas Gü,

ich sag nur DANKE!!!
Genau das was ich benötige - echt perfekt!

Habe nämlich etwas gesucht mit Lokalisierung - und zwar aus einer DB und nicht mit den Ressource Files...

Wünsche noch einen schönen Tag,
mit besten Grüßen
Norbert
private Nachricht | Beiträge des Benutzers