Assembly System.ComponentModel.DataAnnotations.dll , která je standardně součástí .NET Frameworku, obsahuje atributy určené pro deklaraci validačních podmínek členských prvků (vlastností) objektů na úrovni business logiky nebo např. na úrovni datového entity modelu. Tyto atributy jsou pak využitelné ve spojení s nějakou další technologií nebo infrastrukturou (*) například pro provádění validace uživatelských vstupů formuláře aplikace. V tomto příspěvku si ale ukážeme jak je možné stejná metadata využít pro libovolný objekt a jak provádět jeho validaci přímo bez návaznosti na jinou technologii.
Budete mít tedy definovaný nějaký objekt, jehož vlastnosti mohou odpovídat například konfiguraci nebo parametrům implementovaného logického procesu. Před spuštěním daného procesu budeme ale chtít provést validaci, že je konfigurace správná nebo parametry správně nastaveny. Pro deklaraci vlastních validačních podmínek s výhodou použijeme právě data annotations a proto bude třeba dořešit, jak spustit obecnou validaci, která metadata použije. Ještě radši upozorním na důležitý fakt, že veškeré kontroly se budou spouštět pro již existující instanci objektu tj. z hlediska existence objektu se připouští validní i nevalidní instance. To je rozdíl oproti případu, kdy by se validace prováděli už při vytváření objektu samotného v konstruktoru (**).
Jako příklad můžeme použít scénář z předchozího článku. Například definice třídy ImportCsv doplněná o validační podmínky by pak mohla vypadat takto:
using System;
using System.ComponentModel.DataAnnotations;
public class ImportCsv : Import
{
#region member varible and default property initialization
[Required]
public string InputFilename { get; set; }
[Required]
public string WorkingFolder { get; set; }
[Required]
public string ArchiveFolder { get; set; }
public bool IgnoreFirstRow { get; set; }
#endregion
#region action methods
public override void Initialize()
{
//Zde již chceme mít objekt ověřen
//Inicializace filewatcheru
Console.WriteLine("ImportCsv: InputFilename=\"{0}\", WorkingFolder=\"{1}\", ArchiveFolder=\"{2}\", IgnoreFirstRow={3}", this.InputFilename, this.WorkingFolder, this.ArchiveFolder, this.IgnoreFirstRow);
}
public override void Dispose(bool disposing)
{
//Uvolnění objektu
}
#endregion
}
Zde budeme tedy chtít pouze kontrolovat, že jsou nastaveny povinné parametry InputFilename, WorkingFolder a ArchiveFolder, ale obecně nám nic nebrání použít i podmínky další. Kontrola by se měla provést před voláním metody Initialize. Obdobně je možné změnit i třídy Backup a ImportDb.
A jak tedy spustit validaci všech členů objektu? Pro tento účel existuje v již zmiňované assembly DataAnnotations třída Validator. Pozor ale, že tato třída byla doplněna až od .NET Frameworku verze 4.0 tj. starší verze této assembly tuto podporu v sobě nemá.
Základní použití třídy Validator vypadá následovně:
Validator.ValidateObject(obj, new ValidationContext(obj, null, null), true);
Uvedené volání provede validaci všech “odekorovaných” vlastností objektu obj. Pozor, že reference na objekt je zde předávána dvakrát. Při volání jsou nejprve kontrolovány všechny atributy RequiredAttribute a až poté všechny ostatní atributy. Volání vyhodí ValidationException odpovídající první validační chybě (pokud nastala). Výchozí text výjimky lze případně změnit jeho uvedením přímo v daném atributu a to buď přímo nebo odkazem na resource.
Pro rozšíření naší infrastruktury z minula doplníme toto volání v konstruktoru třídy ModuleManager před volání inicializaci jednotlivých modulů:
var module = (IModule)System.Windows.Markup.XamlReader.Parse(el.ToString());
//Validace konfigurace modulu
Validator.ValidateObject(module, new ValidationContext(module, null, null), true);
module.Initialize();
A ještě alespoň stručně, jaké atributy resp. podmínky lze tímto způsobem na objektech validovat. Všechny validační atributy dědí ze základní třídy ValidationAttribute a přímo definují logiku pro validaci dané hodnoty. Patří mezi ně hlavně tyto:
(*) Příkladem může být například ASP.NET Dynamic Data, ASP.NET MVC, WCF RIA Servicesa další technologie nebo validační frameworky.
(**) Obecně je první způsob nevýhodný v tom, že možný nevalidní stav instance musíme v celé implementaci objektu obsloužit a také, že objekt nemůže být immutable. Druhý způsob je zase nevýhodný v tom, že musíme veškeré nastavení předávat již do volání konstruktoru (což může vést na větší počet přetížení konstruktoru a tím na nepřehledné API). Jedním ze způsobů, jak lze nevýhody obou těchto přístupů řešit, je použít oba současně tj. zavést pomocnou builder třídu (ta použije první způsob) pro konstrukci třídy druhé - finálního objektu (ta použije způsob druhý).