Il pattern MVVM è di moda e mi piace. E’ un modo pulito per scrivere codice, buona separazione tra presentation e logiche di business. Non credo alla favola del ViewModel riusabile in contesti diversi ma preferisco pensare che il MVVM mi instiga a produrre codice altamente testabile (ovviamente intendo testing automatico). L’effort? a volte si raggiunge velocemente il livello limite della complessità. Cosa uso? Al momento sto usando Prism anche se sono consapevole che il tool ad oggi più usato è MVVM Light Toolkit. Ci sono cose che mi piacciono di Prism, ci sono cose che mi piacciono di MVVM Light Toolkit. Politicamente paga di più proporre Prism in quanto tecnologia Microsoft, e si sa che ricorrere a terze parti non è mai visto di buon occhio se non c’è reale necessità. E’ anche vero che Microsoft fà le pentole e mai i coperchi e, a differenza di quanto correttamente fatto da Laurent Bugnion, non sono presenti template di progetto in Visual Studio per Prism.
Ora cambiamo apparentemente discorso e parliamo di Asp.net MVC, altro framework decisamente interessante. Si lo sò, sono stato sempre titubante verso questo framework… a tratti lo giudicavo un tornare indietro in quanto mi si chiedeva di rinunciare alla comodità dei controlli server side e no adaptive rendering. Poi però aspetti che il framework maturi e ti rendi conto che il codice è molto fluido, che garantisce il testing e che il framework incapsula una serie di utility, che i controlli server side di Asp.net WebForm in fondo chi se ne frega. E l’adaptive rendering?! beh son sincero è un’altra favola a cui non ho mai davvero creduto. Da un certo punto di vista credo di poter dire che Asp.net MVC potrebbe rischiare di complicarci la vita costrigendoci a creare ViewModel mostruosi se non lo si usa completamente… cosa intendo? Le Partial View. Sono decisamente scelte ottime, che IMHO non è possibile ignorare – come non si ignorano gli UserControl per le WebForm.
Vediamo un modo con cui uso le Partial View (probabilmente nulla di nuovo/innovativo). Sia una View in cui devo gestire la selezione della regione tramite combo. Il model avrà una proprietà relativa alla regione selezionata e dovrà avere una proprietà con cui si espone una lista di regioni con cui caricare la sorgente della combo. Seguendo questa logica nel caso servisse la combo delle regioni in altri contesti l’unica centralizzazione che posso pensare è una utility che incapsuala il recupero della lista delle regioni e al più un extended method da attacare a HtmlHelper per il rendering. In ogni caso costringerei ogni model nei vari contesti (e le rispettive action nel controller) ad essere consapevoli del tecnicismo scelto con cui l’utente selezionerà la regione. Usando le partial view posso pensare di avere qualcosa di più riusabile e che non impatti su model e action dei vari contesti. La mia partial view si occupa unicamente di renderizzare la combo e sarà associata ad una action nel controller che si occupa esclusivamente di recuperare la lista delle regioni con cui popolarla. Risultato è codice più snello e riusabile. Inoltre posso scegliere di cambiare la politica di selezione delle regioni (esempio popup invece delle combo) con impatto zero.
a) La action nel controller.
public ActionResult RegioneSelector(object id, string name = "regione", string dataValueField = "Identificativo")
{
CatalogoEntities ctx = new CatalogoEntities();
var regioni = ctx.RegioneSet.OrderBy(r => r.NomeRegione);
return View(new SelectorViewModel()
{
DataSource = regioni,
SelectedValue = id,
Name = name,
DataValueField = dataValueField,
});
}
b) Partial view
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyApp.UI.WebApp.Models.SelectorViewModel>" %>
<%: Html.DropDownList(Model.Name, new SelectList(Model.DataSource, Model.DataValueField, "NomeRegione", Model.SelectedValue), "-- Qualsiasi regione –")%>
c) Inserimento della Partial View nella View.
<div class="editor-field">
<% Html.RenderAction(actionName: "RegioneSelector", routeValues: new
{
id = Model.IDRegione,
name = "IDRegione",
dataValueField = "Id",
}); %>
<%: Html.ValidationMessageFor(model => model.IDRegione) %>
</div>
Ritorniamo ora a MVVM di Silverlight. Come mi muovo se devo fare la stessa cosa? In realtà ho trovato poca documentazione per realizzare qualcosa di simile nel contesto MVVM. Credo sarebbe troppo riduttivo dire di usare le region di Prism o sfrutta il messaging perchè i problemi reali sono relativi al binding tra il ViewModel della View e oggetti della Partial View etc. In ogni caso con Silverlight ho cercato di volare più basso e risolvere un piccolo –IMHO noioso – problema. Ho la solita selezione della regione tramite combo. La regione selezionata è in bind con la proprietà Regione di un entità Produttore esposto dal ViewModel; la proprietà ItemsSource della combo dovrebbe essere in bind con una collezione di regioni esposta direttamente dal ViewModel… ovviamente il DataContext delle combo è Produttore. La nostra combo è ad esempio inserita in un DataForm. In rete ho trovato un intessante progetto che permette di navigare l’albero dei controlli per gestire al meglio il RelativeSource data la mancanza del FindAncestor: “RelativeSource Binding with FindAncestor mode in Silverlight”. Sebbene il progetto funziona bene sinceramente non potevo essere davvero soddisfatto perchè la mia domanda era: ma possibile che per ogni esigenza tecnica (uso delle combo per selezione regione) devo obbligare ogni mio ViewModel ad adattarsi e nello specifico esporre (e recuperare) la lista delle regioni? La scelta è stata quella di usare un oggetto data source custom. Segue la spiegazione usando il codice (premetto che nel mio progetto uso Wcf RIA Services e Prism).
a) Registro il domaincontext nel Bootstrapper (RegisterBootstrapperProvidedTypes). Ogni oggetto del sistema dovrà insistere sulla stessa istanza del DomainContext.
this.Container.ComposeExportedValue<CatalogoDomainContext>(new CatalogoDomainContext());
b) Nella funzione di inizializzazione del modulo (vedi IModule di Prism) precarico le EntitySet del DomainContext che mi serviranno per le lookup (ovviamente il precaricamento è una scelta personale)
c) Definisco l’oggetto CommonDataSources. Grazie al ServiceLocator mi assicuro di usare esattamente lo stesso DomainContext che userà il ViewModel della View.
public class CommonDataSources: NotificationObject
{
public CatalogoDomainContext DomainContext { get; set; }
public CommonDataSources()
{
if (!DesignerProperties.IsInDesignTool)
{
var service = ServiceLocator.Current.GetAllInstances(typeof(CatalogoDomainContext)).FirstOrDefault();
this.DomainContext = (CatalogoDomainContext)service;
}
}
...
private IEnumerable regioneSet;
public IEnumerable RegioneSet
{
get
{
if (this.regioneSet == null)
{
this.regioneSet = this.DomainContext.Regiones.OrderBy(r => r.NomeRegione);
}
return this.regioneSet;
}
}
...
}
d) Aggiungo il CommonDataSources nelle risorse dello UserControl (la View).
<UserControl.Resources>
<catalogo:CommonDataSources x:Key="CommonDataSources" />
</UserControl.Resources>
e) Collego la combo usando un pratico StaticResource
<toolkit:DataField Label="Regione">
<ComboBox DisplayMemberPath="NomeRegione"
ItemsSource="{Binding RegioneSet, Source={StaticResource CommonDataSources}}"
SelectedItem="{Binding Regione, Mode=TwoWay}">
</ComboBox>
</toolkit:DataField>
Quindi, sono soddisfatto del risultato ottenuto perchè: (1) CommonDataSources è testabile. (2) Il ViewModel non è costretto a esporre la collezione di regione per caricare la combo (3) ho risolto senza uso di codice terze parti la mancanza del FindAncestor tra le modalità di binding di Silverlight.
oO0( che dite? ho scoperto la solita acqua calda? come al solito per i bisogni... usate pure i commenti)
posted @ mercoledì 13 aprile 2011 19:07