Data Annotations für valide DTOs

Wer viel mit DTOs arbeitet, der sollte sich in jedem Fall Data Annotations aus dem .NET Framework anschauen. Ich habe diese kürzlich wieder für ein DTO zur Speicherung der Database Configuration verwendet. Das DTO sieht wie folgt aus:

   1: public abstract class DatabaseConfig

   2: {

3: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte den

Instanznamen der Datenbank angeben.")]

   4:     public string InstanceName { get; set; }

   5:  

6: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte den

Datenbanknamen angeben.")]

   7:     public string DatabaseName { get; set; }

   8:  

   9:     public string UserName { get; set; }

  10:  

  11:     public string Password { get; set; }

  12:  

13: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte die

Einstellung für IntegratedSecurity setzen.")]

  14:     public bool IntegratedSecurity { get; set; }

  15: }

 

Dabei handelt es sich nur um ein sehr einfach Beispiel. Es ist problemlos auch möglich Ranges für Zahlen anzugeben, String-Längen zu definieren oder gar ganz eigene Annotations zu bauen, um z.B. Emailadressen zu validieren.

Was jetzt noch fehlt ist eine Implementierung eines Validator. Ich habe das Interface wie folgt definiert:

   1: public interface IValidator<in TIn>

   2: {

   3:     /// <summary>

   4:     /// Liefert die Fehlermeldungen zu allen ungültigen Werden als String zurück

   5:     /// </summary>

   6:     /// <param name="input"></param>

   7:     /// <returns></returns>

   8:     string ToString(TIn input);

   9:  

  10:     /// <summary>

  11:     /// Liefert eine Auflistung aller ungültigen Werte

  12:     /// </summary>

  13:     /// <param name="input"></param>

  14:     /// <returns></returns>

  15:     ICollection<string> GetMessages(TIn input);

  16:  

  17:     /// <summary>

  18:     /// Prüft, ob das zu validierende Objekt gültig ist

  19:     /// </summary>

  20:     /// <param name="input">Das zu validierende Objekt</param>

  21:     /// <returns>true, wenn valide</returns>

  22:     bool IsValid(TIn input);

  23: }

 

Die Implementierung speziell für die Validierung von Data Annotations (das Interface lässt sich schließlich auch noch für andere Validierungen verwenden) sieht so aus:

   1: public class DataAnnotationsValidator<TIn> : IValidator<TIn>

   2: {

   3:     public string ToString(TIn input)

   4:     {

   5:         var invalidProperties = new StringBuilder();

   6:         foreach (var errorMessage in GetErrors(input))

   7:         {

   8:             invalidProperties.AppendLine(errorMessage);

   9:         }

  10:  

  11:         return invalidProperties.ToString();

  12:     }

  13:  

  14:     public ICollection<string> GetMessages(TIn input)

  15:     {

  16:         return GetErrors(input).ToList();

  17:     }

  18:  

  19:     public bool IsValid(TIn instance)

  20:     {

  21:         var invalidProperties = from prop in typeof (TIn).GetProperties()

22: from attribute in prop.GetCustomAttributes(false).

OfType<ValidationAttribute>()

  23:                                 where !attribute.IsValid(prop.GetValue(instance, null))

  24:                                 select attribute;

  25:  

  26:         return invalidProperties.Count() == 0;

  27:     }

  28:  

  29:     private static IEnumerable<string> GetErrors(TIn instance)

  30:     {

  31:         return from prop in typeof(TIn).GetProperties()

  32:                from attribute in prop.GetCustomAttributes(false).OfType<ValidationAttribute>()

  33:                where !attribute.IsValid(prop.GetValue(instance, null))

  34:                select attribute.FormatErrorMessage(prop.Name);

  35:     }

  36: }

 

Und schon in der Bibel steht geschrieben: Entwickle testgetrieben. Deswegen noch zum Abschluss meine Specs (alles in eine Klasse mit dem Namen DataAnnotationsValidatorSpecs.cs einfügen), geschrieben mit dem Framework Machine.Specifications (gibt es auch als NuGet-Package: install-package Machine.Specifications):

   1: public class DataAnnotationsValidatorTestBase

   2: {

   3:     protected static DtoTestClass Config = new DtoTestClass();

   4:  

   5:     public class DtoTestClass

   6:     {

7: [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte den

Datenbanknamen angeben.")]

   8:         public string Database { get; set; }

   9:         public string Server { get; set; }

  10:         public string User { get; set; }

  11:     }

  12: }

  13:  

  14: public class When_an_invalid_object_is_passed : DataAnnotationsValidatorTestBase

  15: {

  16:     Establish context = () =>

  17:     {

  18:         _validator = new DataAnnotationsValidator<DtoTestClass>();

  19:         Config.Database = string.Empty;

  20:  

  21:     };

  22:  

  23:     private Because of = () => _isValid = _validator.IsValid(Config);

  24:  

  25:     private It should_evaluate_it_as_invalid = () => _isValid.ShouldBeFalse();

  26:  

  27:     private static DataAnnotationsValidator<DtoTestClass> _validator;

  28:     private static bool _isValid = true;

  29: }

  30:  

  31: public class When_an_valid_object_is_passed : DataAnnotationsValidatorTestBase

  32: {

  33:     Establish context = () =>

  34:     {

  35:         _validator = new DataAnnotationsValidator<DtoTestClass>();

  36:         Config.Database = "bac";

  37:  

  38:     };

  39:  

  40:     private Because of = () => _isValid = _validator.IsValid(Config);

  41:  

  42:     private It should_evaluate_it_as_valid = () => _isValid.ShouldBeTrue();

  43:  

  44:     private static DataAnnotationsValidator<DtoTestClass> _validator;

  45:     private static bool _isValid = false;

  46: }

  47:  

  48: public class When_an_object_has_no_annotations

  49: {

  50:     internal class test

  51:     {

  52:  

  53:     }

  54:  

  55:     Establish context = () =>

  56:     {

  57:         _validator = new DataAnnotationsValidator<test>();

  58:  

  59:     };

  60:  

  61:     private Because of = () => _isValid = _validator.IsValid(new test());

  62:  

  63:     private It should_evaluate_it_as_valid = () => _isValid.ShouldBeTrue();

  64:  

  65:     private static DataAnnotationsValidator<test> _validator;

  66:     private static bool _isValid = false;

  67: }

  68:  

69: public class When_an_object_with_one_invalid_property_is_passed :

DataAnnotationsValidatorTestBase

  70: {

  71:     Establish context = () =>

  72:     {

  73:         _validator = new DataAnnotationsValidator<DtoTestClass>();

  74:         Config.Database = string.Empty;

  75:  

  76:     };

  77:  

  78:     private Because of = () => _messages = _validator.GetMessages(Config);

  79:  

  80:     private It should_evaluate_exactly_this_property_as_invalid = () =>

  81:     {

  82:         (_messages.Count == 1).ShouldBeTrue();

  83:         _messages.Aggregate(string.Empty, (current, message) => current + message)

  84:             .Contains("Datenbanknamen").ShouldBeTrue();

  85:     };

  86:  

  87:     private static DataAnnotationsValidator<DtoTestClass> _validator;

  88:     private static ICollection<string> _messages;

  89: }

Mit Tag(s) versehen: , ,

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: