Tanto per rinfrescare la memoria, le DataAnnotations sono degli attributi .NET usate per “arricchire” il comportamento di classi e metodi, aggiungendo funzionalità accessorie senza intaccare l’oggetto originario.
Le DataAnnotations sono molto utilizzate e sono davvero un validissimo aiuto al nostro modo di programmare. Ad esempio sono utilizzate per poter validare una proprietà legata ad un campo che l’utente deve inserire. La validazione viene effettuata tramite un oggetto chiamato ValidationAttribute che è una classe astratta e che deve essere ereditata da una classe reale. Molte sono già fornite da .NET, ad esempio RequiredAttribute oppure RangeAttribute.
Adottiamo la validazione in una nostra classe
Vediamo ad esempio questa classe:
public class WeatherForecast
{
public DateTime Date { get; set; }
[Range(18, 80, ErrorMessage = "Temperature must be between 18°C and 22°C.")]
public int TemperatureC { get; set; }
[Required(ErrorMessage = "The field is required")]
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Penso che la classe WeatherForecast la conosciate tutti, fa parte del template di base di Blazor. Modificata in questo modo ci permette di ragionare sulle DataAnnotations. In questo caso ho decorato solo due properties: Summary e TemperatureC.
Per poter utilizzare la validazione, possiamo creare una semplice form di inserimento dati, senza però la necessità di salvarli dato che non è lo scopo di questo articolo.
Creiamo quindi una form per il data entry al posto della pagina FetchData, in questo modo:
@page "/fetchdata"
@using BlazorLocalization.Shared
@inject HttpClient Http
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<EditForm Model=@WeatherForecast>
<DataAnnotationsValidator/>
<div class="form-group">
<label for="Name">Summary</label>
<InputText @bind-Value=WeatherForecast.Summary class="form-control" id="Summary"/>
<ValidationMessage For="() => WeatherForecast.Summary"/>
</div>
<div class="form-group">
<label for="Age">Temperature</label>
<InputNumber @bind-Value=WeatherForecast.TemperatureC class="form-control" id="TemperatureC"/>
<ValidationMessage For="() => WeatherForecast.TemperatureC"/>
</div>
<input type="submit" class="btn btn-primary" value="Save"/>
</EditForm>
@code
{
public WeatherForecast WeatherForecast { get; set; } = new WeatherForecast();
}
Come possiamo notare, ho preso un’istanza della classe WeatherForecast e l’ho data in pasto alla form. Ma per avere una corretta validazione tramite DataAnnotation vanno aggiunte le linee (che potete già vedere nel codice) <DataAnnotationsValidator/>
che di permette di recuperare le DataAnnotations su ogni campo e <ValidationMessage For="() => WeatherForecast.NomeCampo"/>
per poter visualizzare il messaggio.
Se avviamo la nostra applicazione e cercheremo di fare un salvataggio del nostro form riceveremo questa schermata:

Splendido! Abbiamo il nostro messaggio di errore sotto ogni campo non valido.
Dato che ho ripreso il sorgente dell’esempio dell’articolo precedente, possiamo cambiare lingua alla nostra applicazione. Fatto questo, possiamo ripetere l’esperimento: ma dopo aver cliccato sul bottone “Salva” otterremo questa schermata:

Come potete notare, la lingua è correttamente in italiano (frecce rosse) mentre i messaggi che arrivano dalle DataAnnotations (frecce verdi) continuano ad essere in inglese. Come facciamo a localizzarli? Con lo stesso procedimento imparato negli articoli precedenti non otterremo il risultato sperato, quindi i nostri testi non potranno essere tradotti allo stesso modo.
ValidationAttribute
Se andiamo a vedere com’è fatta la classe ValidationAttribute, da cui derivano tutte le classi “attribute” come RangeAttribute e RequiredAttribute, ci accorgeremo di alcune proprietà interesanti:
/// <summary>
/// Gets or sets the resource name (property name) to use as the key for lookups on the resource type.
/// </summary>
/// <value>
/// Use this property to set the name of the property within <see cref="ErrorMessageResourceType" />
/// that will provide a localized error message. Use <see cref="ErrorMessage" /> for non-localized error messages.
/// </value>
public string? ErrorMessageResourceName
{
get => _errorMessageResourceName;
set
{
_errorMessageResourceName = value;
_errorMessageResourceAccessor = null;
CustomErrorMessageSet = true;
}
}
/// <summary>
/// Gets or sets the resource type to use for error message lookups.
/// </summary>
/// <value>
/// Use this property only in conjunction with <see cref="ErrorMessageResourceName" />. They are
/// used together to retrieve localized error messages at runtime.
/// <para>
/// Use <see cref="ErrorMessage" /> instead of this pair if error messages are not localized.
/// </para>
/// </value>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
public Type? ErrorMessageResourceType
{
get => _errorMessageResourceType;
set
{
_errorMessageResourceType = value;
_errorMessageResourceAccessor = null;
CustomErrorMessageSet = true;
}
}
Queste due proprietà sono espressamente create per poter essere utilizzate quando localizziamo il nostro Attribute. Ed ecco che aggiungendo un file di risorse per ogni lingua come abbiamo già fatto in precedenza, possiamo richiamare in automatico il testo in lingua nel modo seguente:
[Required(ErrorMessageResourceName = nameof(Languages.ResourceLanguage.ErrorValidationRequired), ErrorMessageResourceType = typeof(Languages.ResourceLanguage))]
dove Languages.ResourceLanguage.ErrorValidationRequired è la nostra risorsa, e Languages.ResourceLanguage è il namespace principale dove trovare le risorse.
Allo stesso modo potremo localizzare il testo dell’errore per il Range:
[Range(18, 22, ErrorMessageResourceName = nameof(Languages.ResourceLanguage.TemperatureValidationMessageRange), ErrorMessageResourceType = typeof(Languages.ResourceLanguage))]
Ottenendo questo risultato


Come potete vedere tutto funziona alla perfezione.
Manca un’ultima cosa: il range di temperatura è fisso. Se volessi cambiarlo dinamicamente, dovrei aggiungere una ValidationAttribute custom. Ma questo è un altro articolo 😉
Come sempre potrete trovare il codice dell’articolo sul repository GitHub.
Alla prossima!