Usare i file di risorse con ASP.NET Core

di Moreno Gentili, in ASP.NET Core,

Nello script precedente (https://www.aspitalia.com/script/1332/Gestire-Localizzazione-ASP.NET-Core.aspx) abbiamo visto come impostare la Culture della richiesta, in maniera coerente con le preferenze dell'utente. In questo modo, l'applicazione è già predisposta a fornire testi localizzati, con il minimo sforzo richiesto da parte nostra.

Creare i file di risorse

ASP.NET Core dispone di un meccanismo di localizzazione basato su file di risorse, ovvero dei file XML con estensione .resx che vengono compilati su propri assembly nel momento in cui si compila l'applicazione. All'interno di ogni file di risorse si trovano i testi e altri contenuti localizzati per una specifica Culture.

Iniziamo creando nel progetto una directory /Resources. Per aggiungere un file di risorse al suo interno, rechiamoci nel riquadro Solution Explorer di Visual Studio e clicchiamo la directory col tasto destro. Poi, selezioniamo la voce di menu Add -> Add new item -> Resources File.

Aprendo il file di risorse, verrà mostrata una comoda interfaccia grafica per la gestione dei testi localizzati. Come si vede, il file contiene più coppie chiave-valore, eventualmente accompagnate da un commento che può essere usato per spiegare il loro ambito di utilizzo.


In questo esempio abbiamo creato tre file di risorse: uno per ciascuna Culture (en-US, fr e it) che intendiamo supportare nell'applicazione.
Il nome "Shared" non è obbligatorio perché siamo liberi di chiamare i file di risorse come preferiamo, pur mantenendo il suffisso .lingua-regione.resx.

Ovviamente, ciascuno di questi file conterrà le stesse chiavi ma con valori differenti, tradotti nelle rispettive lingue. Ecco un esempio di contenuto XML di un file di risorse.

https://aspit.co/bwk

Come si vede, il file non offre il massimo della leggibilità. Coloro che usano Visual Studio Code, soprattutto su Linux o su Mac, potrebbero avere più difficoltà nel modificare direttamente questi file rispetto a chi ha Visual Studio installato. Microsoft, infatti, non ha ancora fornito uno strumento di gestione multipiattaforma. Una issue è stata aperta su GitHub (https://github.com/aspnet/AspNetCore.Docs/issues/2501) ma, ad oggi, è disponibile solo uno strumento da riga di comando chiamato LocalizationResourceGenerator (https://github.com/hishamco/LocalizationResourceGenerator) realizzato da un membro della community.

Ottenere i testi localizzati

Per recuperare i testi dai file di risorse che abbiamo creato, useremo un servizio offerto da ASP.NET Core. Come prerequisito, andiamo quindi ad abilitarlo per la dependency injection scrivendo quando segue nel metodo ConfigureServices della classe Startup.

services.AddLocalization(options =>
{
  //Indichiamo la cartella in cui si trovano i nostri file resx
  options.ResourcesPath = "Resources";
});

Ora siamo pronti per usare il suddetto servizio, l'IStringLocalizerFactory. Nell'esempio seguente lo riceviamo nell'action di un controller grazie all'attributo [FromServices] ma, in alternativa, potremmo riceverlo come parametro del costruttore di un qualsiasi componente della nostra applicazione.

public IActionResult Index([FromServices] IStringLocalizerFactory localizerFactory)
{
  //Usiamo la factory per ottenere un IStringLocalizer, indicando il nome del file di risorse da cui attingere
  //Il nome "Shared" è quello che avevamo dato ai file di risorse
  //Mentre "NomeAssembly" va sostituito con quello del progetto che stiamo sviluppando
  IStringLocalizer localizer = localizerFactory.Create("Shared", "NomeAssembly");
  
  //A questo punto, usiamo l'IStringLocalizer per ottenere il valore della chiave "Title"
  string title = localizer["Title"];
  
  //E la usiamo qui, ad esempio per impostare il titolo di pagina
  ViewData["Title"] = title;
  
  return View();
}

Creare un servizio di localizzazione riutilizzabile

Il codice che abbiamo scritto nel controller potrebbe essere incapsulato in un nostro servizio personalizzato, così eviteremo di duplicare lo stesso codice in vari punti della nostra applicazione. Quindi creiamo una nuova classe CustomLocalizer.cs con questo contenuto, in una directory che preferiamo (ad esempio in /Models/Services).

public class CustomLocalizer : IStringLocalizer
{
  private readonly IStringLocalizer localizer;
  public CustomLocalizer(IStringLocalizerFactory localizerFactory)
  {
    this.localizer = localizerFactory.Create("Shared", "NomeAssembly");
  }
  
  //Il nostro CustomLocalizer è di fatto un wrapper attorno all'IStringLocalizer creato dalla factory
  public LocalizedString this[string name] => localizer[name];
  public LocalizedString this[string name, params object[] arguments] => localizer[name, arguments];
  public IEnumerable<LocalizedString> 
    GetAllStrings(bool includeParentCultures) => localizer.GetAllStrings(includeParentCultures);
  public IStringLocalizer WithCulture(CultureInfo culture) => localizer.WithCulture(culture);
}

Ora registriamo anche questo nostro servizio per la dependency injection. Quindi torniamo nella classe Startup e nel metodo ConfigureServices aggiungiamo quanto segue.

services.AddTransient<IStringLocalizer, CustomLocalizer>();

Fatto questo, possiamo semplificare così il codice che avevamo scritto nell'action.

public IActionResult Index([FromServices] IStringLocalizer localizer)
{
  string title = localizer["Title"];
  ViewData["Title"] = title;
  return View();
}

Localizzare le data annotation

Le data annotation sono attributi che posizioniamo sulle proprietà dei nostri viewmodel, così che possano verificare la validità dell'input dell'utente e restituirgli dei messaggi di errore in caso di dati non validi. Anche questi messaggi, ovviamente, dovrebbero essere localizzati.
Quindi nella classe Startup aggiungiamo quanto segue nel metodo ConfigureServices.

//AddMvc già lo abbiamo
//Se stiamo usando ASP.NET Core 3.0 o superiore si chiamerebbe AddEndpoints
services.AddMvc()
//Di seguito aggiungiamo questo
.AddDataAnnotationsLocalization(options => {
  options.DataAnnotationLocalizerProvider = (type, factory) =>
  {
    //Qui forniamo il nostro CustomLocalizer per l'ottenimento dei testi localizzati
    return new CustomLocalizer(factory);
  };
});

Ora tutte le data annotation che abbiamo usato nei nostri viewmodel potranno sfruttare il servizio di localizzazione. Impostiamo semplicemente la loro proprietà ErrorMessage su una delle chiavi che abbiamo definito nel file di risorse.

public class SubscribeViewModel
{
  [Required(ErrorMessage = "EmailRequired")] //EmailRequired verrà cercato nel file di risorse
  [EmailAddress(ErrorMessage = "EmailInvalid")] //EmailInvalid verrà cercato nel file di risorse
  public string Email { get; set; }
}

Localizzare una View

Le tecniche che abbiamo visto sono già sufficienti per estrarre testi in lingua da visualizzare all'utente. Tuttavia, se per preferenza personale volessimo ottenere tali testi direttamente da una View Razor, dovremmo anche in questo caso sfruttare la dependency injection per ricevere il servizio IStringLocalizer.

@using Microsoft.Extensions.Localization
@inject IStringLocalizer localizer
<h1>@localizer["Title"]</h1>

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi