Různé možnosti validace dat v Silverlight

Jan Holan       16. 12. 2011       Silverlight       6105 zobrazení

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, InvalidUnfocusedInvalidFocused 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;
            }
        }
    }
}

Validation

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).

  • Tato možnost je jediná dostupná již v Silverlight 3.
  • Při bindingu musí být nastaveno ValidatesOnExceptions=True (výchozí hodnota je False).
  • K aktualizaci a validaci dojde, když TextBox ztratí focus.  Pokud chceme aktualizovat ručně, lze to provést voláním:
    BindingExpression binding = TextBox1.GetBindingExpression(TextBox.TextProperty);
    binding.UpdateSource();
    Pokud naopak potřebujeme automatickou aktualizaci na LostFocus vypnout, nastavíme v binding UpdateSourceTrigger=Explicit.
  • To že se v tomto řešení v setru vyhazují exception může být někdy nevýhodné, např. v debug, kdy je běh přerušován, nebo je zobrazováno mnoho exception informací.
  • Protože je chybný vstup signalizován pomoci výjimky, nedostane se zpravidla chybná hodnota do datového objektu. Při použití converteru nebo při nekompatibilním datovém typu je výjimka vyhozena dokonce ještě dříve než dojde k volání setru. To může být problém v některých situacích, např. pokud je to takto použité v MVVM modelu, tak patřičný ViewModel neví o stavu, že uživatel zadal špatná data. Většinou se to pak obchází zavedením jiných (dummy) vlastností, které pak umožňují např. null nebo prázdné hodnoty a umějí z toho odvodit, že je zadán špatný vstup. Ve složitějších řešení to pak může vést na ”messy” kód.
  • A další problém může být s tím, že validace probíhá pro jednotlivé vlastnosti samostatně a nezávisle. Nelze jednoduše vyvolat validaci všech controlů najednou.

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...

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback