ASP.NET MVC: ottimizzazione della UI con le Partial View

di Francesco Abbruzzese, in ASP.NET MVC,

Uno dei capisaldi del paradigma MVC è la separazione di responsabilità tra il Controller, che specifica cosa mostrare, e la View che specifica come mostrarlo. Se ci atteniamo a questa semplice regola, otteniamo un'alta affidabilità e manutenibilità della nostra applicazione a condizione di utilizzare un progetto di testing automatico da lanciare dopo ogni modifica che apportiamo al codice: è proprio il risultato del testing automatico che ci garantisce che le modifiche non hanno alterato l'affidabilità del software. Naturalmente, il codice presente nelle View deve essere il più semplice possibile perché le View non si riescono facilmente a sottoporre a testing automatico. L'ideale è comporre le View semplicemente mettendo insieme dei mattoncini preconfezionati, ed è proprio a questo che servono gli Helper e le Partial View di ASP.NET MVC.

Se manteniamo la separazione di ruoli tra Controller e View o Partial View, ci troviamo sia mattoncini di UI che Action Method di Controller riutilizzabili sia all'interno della stessa applicazione che in altre applicazioni. In questo modo aumentiamo la manutenibilità e diminuiamo i costi di sviluppo: non è raro un rapporto di costi di 1/3 tra un'applicazione ben modularizzata e una che lo è poco.

Sfortunatamente non è sempre facile adattare dei mattoncini UI, siano essi Helper o Partial View al View Model prodotto da un Controller, per cui alcuni programmatori spesso cedono alla tentazione di modificare il Controller per adattarlo alle esigenze della View. Questo è sicuramente un errore da evitare, ma è comunque sintomo di un problema esistente.

Un primo esempio per capire il problema

Facciamo un esempio semplice. Supponiamo di avere una proprietà di tipo Datetime nel nostro View Model e supponiamo di volerla mostrare utilizzando sei DropDownList differenti (anno, mese, giorno, ora, minuti e secondi). Inoltre, supponiamo anche di voler collocare data e tempo in luoghi distanti della View. Come possiamo fare?

Sicuramente possiamo costruirci due Helper o due Partial View che ci risolvono il problema di mostrare la nostra data in questo modo. Però, se vogliamo che questi campi siano modificabili dall'utente, come facciamo a dire al Model Binder di rimetterli insieme nel DateTime iniziale?

Possiamo costruirci il nostro Model Binder, ma poi, dove lo mettiamo? Se lo usiamo come Model Binder di default facciamo di questo modo di rappresentare le date una sorta di "standard" per tutta l'applicazione. Se, d'altro canto, lo usiamo all'interno del nostro controller, rompiamo l'indipendenza tra Controller e View, legando univocamente il nostro Controller a quel modo di rappresentare la data.

Potremmo anche semplicemente mettere il DateTime in un campo Hidden e poi fare tutto il lavoro necessario a spezzarlo in sei campi e a ricomporlo in JavaScript. Riempire, però, una View di codice Javascript non fa certo bene alla testabilità e poi, diciamoci la verità, anche se usiamo JQuery, la produttività in JavaScript è molto più bassa che in .NET.

L'ideale sarebbe crearci una semplice classe in .NET, in cui specifichiamo semplicemente come si divide la data nelle sue componenti e come la si ricompone, e poi dire in qualche modo alla nostra View di usare questa classe, sia prima di mostrare la data, sia successivamente durante la fase di Model Binding. Sarebbero solo poche linee di codice da scrivere e sarebbero anche testabili facilmente.

La problematica che abbiamo illustrato con questo esempio molto semplice è ciò che rende complicata la costruzione in MVC di controlli sofisticati come quelli presenti in ASP.NET WebForms. Questa difficoltà nello sviluppo di controlli per MVC ha portato all'implementazione della libreria MVC Controls Toolkit, che è disponibile all'interno del sito Codeplex di Microsoft. La libreria in oggetto è un toolkit per implementare facilmente controlli basati sia su Partial View che su Helper, e contiene al suo interno un interessante "suite" di controlli già implementati pronti all'uso, come, ad esempio, un sofisticato DataGrid.

In quest'articolo, dopo aver analizzato alcune delle tecniche principali per modularizzare ed ottimizzare le UI in ASP.NET MVC, descriveremo come la libreria MVC Controls Toolkit possa risolvere la problematica esposta in precedenza, rendendo realmente molto facile la costruzione di controlli MVC anche più complessi di quelli presenti in ASP.NET WebForms, senza peraltro venir meno ai principi fondamentali che sono alla base del pattern MVC.

Le Partial View

Le Partial View sono delle View particolari, che possiamo utilizzare come mattoncini per costruire View più complesse. Per crearle, è sufficiente selezionare la checkbox Partial View del wizard che in Visual Studio ci guida alla creazione di una nuova View. Come le View normali, le Partial View possono essere tipizzate e quindi avere un loro View Model.

Dopo aver creato la nostra Partial View, possiamo usarla in una View principale o in altre Partial View, passandogli il View Model su cui operare in uno dei seguenti modi:

  • direttamente: in questo caso la View principale passa una classe alla Partial View;
  • specificando il nome di un Controller e di un Action Method da invocare: in questo caso è l'Action Method del Controller a passare il View Model alla Partial View, invocando il metodo PartialView invece dell'usuale metodo View.

Ci conviene usare la prima delle due tecniche per mostrare una parte del View Model della View per il quale abbiamo definito un aspetto standard in tutta o in una parte dell'applicazione. Ad esempio, la Partial View potrebbe essere utilizzata per mostrare i campi relativi all'indirizzo di un cliente (rappresentato da un'ipotetica classe Client), come segue:

<%=Html.Partial("Address", Model.OfficeAddress) %>

In questo caso il motore MVC cerca un file di nome Address.ascx prima nella stessa directory della View principale, e poi nella directory Shared. Questo meccanismo di ricerca permette di personalizzare la Partial View in base al contesto di utilizzo. Quindi, possiamo avere un file Address.ascx nella directory Shared da utilizzare normalmente per tutti gli indirizzi, e poi, nel caso in cui una View principale abbia bisogno di una sua versione modificata, inserirne un altro specifico per quella View nella sua stessa directory.

L'overload di Partial scelto nell'esempio precedente non aggiunge alcun prefisso ai nomi dei campi contenuti nel file, per cui, se la nostra classe Client oltre ad aver un campo OfficeAddress avesse anche un campo PrivateAddress, vi sarebbe un conflitto di nomi e la nostra View non funzionerebbe! Se abbiamo di questi problemi, dobbiamo usare l'overload che permette di specificare l'intero ViewDataDictionary della Partial View. Sfortunatamente, se creiamo manualmente un nuovo dizionario, dobbiamo anche sobbarcarci l'onere di copiarci dentro gli eventuali errori di validazione creati durante il POST della View principale:

ViewDataDictionary<Address> dataDictionary =
  new ViewDataDictionary<Address>(Model.OfficeAddress);

dataDictionary.TemplateInfo.HtmlFieldPrefix =
  Html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName("Address"); 

MyHelper.CopyRelevantErrors(dataDictionary.ModelState,
  Html.ViewData.ModelState, dataDictionary.TemplateInfo.HtmlFieldPrefix);

La prima istruzione crea il ViewDataDictionary tipizzato da passare alla Partial View e gli passa l'oggetto di tipo Address che vogliamo visualizzare nelle Partial View. La seconda istruzione gli passa il giusto prefisso e infine la funzione CopyRelevantErrors che ci siamo definiti nella classe statica MyHelper copia dal dizionario degli errori della View principale soltanto gli errori di pertinenza della Partial View, in altre parole quelli che hanno lo stesso prefisso associato alla Partial View che, nel nostro caso, è "OfficeAddress".

3 pagine in totale: 1 2 3
Contenuti dell'articolo

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