Zkontrolovat správný vstup od uživatele je docela důležitá část nejen business aplikací. Ačkoliv v Silverlightu je pro validaci dat připravená podpora, není tato problematika úplně jednoduchá. V tomto příspěvku se podíváme na jednotlivé možnosti jak zajistit, aby Silverlight control provedl ověření vstupu (Data Validation) a zobrazil uživateli požadovanou chybovou zprávu.
Zabudované Silverlight controly automaticky podporují zobrazení stavu validace (Valid, InvalidUnfocused a InvalidFocused ValidationStates). Většinou to bývá, že při špatném vstupu dat je zobrazen červený okraj a pokud má daný control focus, je zobrazen speciální tooltip text s konkrétní zprávou o chybě. Validace se provádí při Bindingu z vlastnosti controlu do zdroje, např. pro control TextBox vypadá XAML takto:
<TextBox Text="{Binding Value, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" />
Zde je vlastnost Text obousměrným bindingem připojena k vlastnosti Value datového prvku. Na tomto příkladu si předvedeme jednotlivé možnosti, jak v datovém objektu provést ověření vstupu.
Exceptions
První možností je provádět validaci vyhozením výjimky (Exceptions) v Setru datové vlastnosti (případně v converteru). Chybová zpráva se v tomto případě převezme z Message výjimky a zobrazí uživateli. Datový objekt s naší vlastností Value může pro tento případ vypadat takto:
public class DataObject
{
private string m_Value;
public string Value
{
get { return m_Value; }
set
{
if (string.IsNullOrEmpty(value))
{
throw new Exception("Value is required!");
}
if (value.Length < 2 || value.Length > 20)
{
throw new Exception("Value must be between 2 and 20 characters long!");
}
if (m_Value != value)
{
m_Value = value;
}
}
}
}
Pozn.: Zapnutím NotifyOnValidationError je vyvolávána událost BindingValidationError, pak je možné např. také automaticky chybovou zprávu zobrazit i v controlu ValidationSummary (z System.Windows.Controls.Data.Input.dll assembly).
Další příklad řešení validace pomoci exceptions je zde.
Data Annotations
Druhým řešením je použití validace pomoci anotace datové třídy. To se provádí pomoci atributů, které jsou definované v namespace System.ComponentModel.DataAnnotations v assembly System.ComponentModel.DataAnnotations.dll. Jednotlivé atributy určují požadované omezení na jednotlivá pole jako je: Required, StringLength, Range, RegularExpression apod. Ve vlastnosti se pak vyvolá validace pomoci metody ValidateProperty objektu Validator. Kód našeho datového objektu pak může vypadat např. takto:
public class DataObject
{
private string m_Value;
[Display(Name = "Value property")]
[Required(ErrorMessage = "{0} is required!")]
[StringLength(20, ErrorMessage = "{0} Value must be between {2} and {1} characters long!", MinimumLength = 2)]
public string Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Value" });
m_Value = value;
}
}
}
}
- Při bindingu musí být nastaveno ValidatesOnExceptions=True.
- Validace pomoci data annotations také využívá pro signalizaci chybného stavu vyhození výjimky, jako výše uvedené první řešení. Metoda Validator.ValidateProperty totiž provádí na základě atributů ověření hodnoty a v případě, že hodnota nesplňuje tyto podmínky, vyvolá výjimku ValidationException. Důsledkem jsou podobné nevýhody jako u prvního řešení.
- Výhoda je ale, že pro standartní validace je toto velice jednoduchý způsob jak validace pomoci atributů definovat. Navíc se jedná o standartní .NET způsob, který se využívá i v jiných technologií jako např. ASP.NET DynamicData, MVC, Entity framework apod.
- Tento způsob je využíván v technologii Silverlight RIA Services, kde jsou objekty odvozené od základních tříd Entity nebo ComplexObject a vnitřně volají tuto validaci.
- Navíc zde máme možnost pomoci atributu Display definovat i název jiný než pouze jméno vlastnosti, ten se pak automaticky zobrazuje např. ve ValidationSummary controlu.
- Atributy podporují chybové hlášení definovat i na základě Resource a tím je možné je lokalizovat.
- Pokud jsou objekty generované (např. Entity frameworkem), je možné validační atributy dopsat do jiné třídy tvz. Metadata třídy. Ta se pak atributem MetadataType přiřadí k původní třídě, validační atributy se pak načítají z Metadata třídy.
- Data Annotations podporuje i vytvoření custom atributu pro vlastní validace, většinou je pak ale jednodušší provést tuto validaci jiným způsobem.
- Ve složitějších scénářích můžeme u vlastností použít pouze definování podmínek atributy (nevolat v nich ValidateProperty) a validaci tak provádět v kombinaci s jinou metodou validace.
- Validator dále obsahuje i metody ValidateObject a TryValidateObject, které je možné využít pro vyvolání validace na celý datový objekt, o tom více na tomto blogu již zde.
Některé příklady Data Annotations zde nebo zde.
IDataErrorInfo
Tato metoda spočívá v tom, že v datové třídě implementujeme rozhraní IDataErrorInfo (z namespace System.ComponentModel). Jedná se o rozhraní v .NET Frameworku známe už od verze 1.1. Obsahuje vlastnost Error, ta se ale v Silverlight nevyužívá. Dále interface obsahuje indexér this[string propertyName], který podle jména vlastnosti může vrátit chybovou zprávu validace nebo null. Základní implementace může vypadat takto:
public class DataObject : IDataErrorInfo
{
private string m_Value;
[Display(Name = "Value property")]
public string Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
m_Value = value;
}
}
}
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get
{
if (propertyName == "Value")
{
if (string.IsNullOrEmpty(this.Value))
{
return "Value is required!";
}
if (this.Value.Length < 2 || this.Value.Length > 20)
{
return "Value must be between 2 and 20 characters long!";
}
}
return null;
}
}
#endregion
}
Zde se o validaci nestará seter vlastností, ale přímo popsaný indexér. Tím je ale kód validace oddělen od kódu vlastnosti. To může být nevýhodné, proto uvedu ještě jednu implementaci. Vytvoříme pomocnou třídu PropertyValidator, která bude aktuálně držet seznam chybových zpráv. S její pomocí pak datový objekt bude vypadat takto:
public class DataObject : IDataErrorInfo
{
#region member varible and default property initialization
private PropertyValidator Validator = new PropertyValidator();
private string m_Value;
#endregion
#region property getters/setters
public string Value
{
get { return m_Value; }
set
{
Validator.Validate("Value", () => string.IsNullOrEmpty(value), "Value is required!");
Validator.Validate("Value", () => !string.IsNullOrEmpty(value) && value.Length < 2, "Value must be least 2 characters in length!");
if (m_Value != value)
{
m_Value = value;
}
}
}
#endregion
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get { return Validator.GetErrors(propertyName); }
}
#endregion
}
- Při bindingu musí být nastaveno ValidatesOnDataErrors=True (místo ValidatesOnExceptions).
- V Silverlight se binding enginem nikdy nevyvolá vlastnost IDataErrorInfo.Error.
- Tento mechanizmus v Silverlight 4 nepodporuje BindingGroup jako jsou ve WPF.
- Výhodou na rozdíl od předchozích metod je to, že zde nedochází k vyvolání žádné Exception.
- Pořád nelze jednoduše vyvolat validaci všech controlů najednou, ale už lze jednoduše provádět validace, které jsou závislé na více vlastnostech najednou (např. ověření nového hesla oproti potvrzení hesla).
- Lze pro jednu vlastnost provést více různých validací a nastavit více chybových zpráv (více řádků) viz druhý příklad se třídou PropertyValidator . Můžeme tak např. informovat, že zadané heslo je krátké a zároveň neobsahuje požadovanou číslici apod.
Příklady některých dalších implementaci jsou zde, zde , zde nebo zde.
INotifyDataErrorInfo
Silverlight 4 obsahuje také nový interface INotifyDataErrorInfo (namespace System.ComponentModel), který je navíc přizpůsoben tomu, aby bylo možné validace provádět i na serveru přes asynchronní volání. Interface obsahuje vlastnost HasErrors, která informuje o tom že jsou v datovém objektu nějaké chyby validace, ty se pak pro jednotlivé vlastnosti vrací metodou GetErrors. Posledním prvkem interface je událost ErrorsChanged, která informuje o změně chybového stavu některé vlastnosti. Implementace třídy našeho datového objektu s použitím INotifyDataErrorInfo interface může vypadá takto:
public class DataObject : INotifyDataErrorInfo
{
#region member varible and default property initialization
private Dictionary<string, List<string>> m_PropertyErrors = new Dictionary<string, List<string>>();
private string m_Value;
#endregion
#region action methods
public string Value
{
get { return m_Value; }
set
{
var errors = new List<string>();
if (string.IsNullOrEmpty(value))
{
errors.Add("Value is required!");
}
if (value != null && value.Length < 2)
{
errors.Add("Value must be least 2 characters in length!");
}
SetErrors("Value", errors);
if (m_Value != value)
{
m_Value = value;
}
}
}
#endregion
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (m_PropertyErrors.ContainsKey(propertyName))
{
return m_PropertyErrors[propertyName];
}
return null;
}
public bool HasErrors
{
get { return m_PropertyErrors.Count > 0; }
}
#endregion
#region private member functions
private void SetErrors(string propertyName, List<string> errors)
{
if (m_PropertyErrors.ContainsKey(propertyName))
{
if (Enumerable.SequenceEqual(m_PropertyErrors[propertyName], errors))
{
return;
}
m_PropertyErrors.Remove(propertyName);
}
else if (errors == null || errors.Count == 0)
{
return;
}
m_PropertyErrors.Add(propertyName, errors);
OnErrorsChanged(propertyName);
}
private void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
}
#endregion
}
Pozn.: Tato implementace by se dala zobecnit do nějaké base třídy, to ale už ponechám na čtenáři.
- Při bindingu musí být nastaveno ValidatesOnNotifyDataErrors=True, true je výchozí hodnota.
- Díky události ErrorsChanged je umožněno provádět asynchronní volání na server a validaci tak provádět na serveru (server-side validation).
- Tento interface je zatím pouze v Silverlight, do velkého frameworku nám přibude s async až ve verzi 4.5.
- Metoda GetErrors již plně podporuje vrácení více chybových zpráv pro jednu vlastnost (vrací IEnumerable). Vlastní editační control zobrazí pouze jednu chybovou zprávu, ale ValidationSummary control zobrazí chyby všechny.
- Lze provádět validace celého objektu tj. validace, které jsou závislé na více vlastností najednou.
- Lze provést validaci celého objektu a vyvoláním události GetErrors informovat najednou jednotlivé controly.
Jiný příklad této metody validace je např. zde.
Tím jsme si ukázali všechny možné způsoby vyvolání validace a zobrazení chybové zprávy uživateli. Osobně pro složitější případy doporučuji použít ten poslední způsob pomoci nového interface INotifyDataErrorInfo, případně ho lze i zkombinovat s deklarací některých jednodušších podmínek pomoci Data Annotations.
Ještě další odkazy na jiné články o validaci v Silverlight:
http://www.thejoyofcode.com/Silverlight_Validation_and_ViewModel.aspx
http://outcoldman.ru/en/blog/show/259
http://www.silverlight.net/learn/data-networking/validation/implementing-data-validation-in-silverlight-with...