Sincronizzare Oggetti e Controlli Web/Windows In Un Colpo Solo
Posted by Ricibald on 14th August 2008
L’utilizzo dei controlli Web/Windows prevede due fasi:
- il trasferimento dei dati dall’oggetto ai controlli corrispondenti
- il trasferimento dei dati dai controlli all’oggetto corrispondente
Come dire:
void loadPage(TextBox txt_nome, Persona persona) {
txt_nome.Text = persona.Nome;
}
void submitInfo(TextBox txt_nome, Persona persona) {
persona.Nome = txt_nome.Text;
}
Questo è il classico approccio, ma, come tutte le cose meccaniche che si fanno in informatica, nasconde vari problemi:
- il tempo: scrivo le stesse cose due volte. Prima ad andare e poi a tornare…
- la consistenza: qualche proprietà potrei dimenticarla e l’oggetto potrebbe non essere aggiornato o potrebbe essere popolato con il valore sbagliato
La cosa ideale sarebbe avere un controllo Web/Windows che consentisse di realizzare il bind a due vie (le due fasi le fa il controllo per noi):
void bind(TextBoxBinded txt_nome, Persona persona) {
txt_nome.Bind(persona.Nome);
}
Questa “sarebbe” la cosa ideale, ma non è praticabile: come conosciamo il set della proprietà “Nome”? In questo modo non possiamo:
Come passare per parametro i metodi get/set di una proprietà o di un campo, in modo da ottenere il bind a due vie?
Dopo ricerche su internet, miei ragionamenti e varie ed eventuali, le soluzioni possibili sono le seguenti:
- Approccio basato su Reflection: la soluzione più immediata, ma molto fragile: per ottenere un oggetto Property dobbiamo passare il nome della proprietà. Tale nome è una semplice stringa: non è pertanto tipizzato in alcun modo ed eventuali modifiche alla proprietà non ci verrebbero segnalate dal compilatore… Con questo approccio ogni modifica alla struttura ad oggetti diventerebbe letteralmente un incubo…
- Ogni proprietà implementa un interfaccia specifica: ad esempio IProperty<T>, dove T è il tipo della proprietà. Tale interfaccia avrà metodi get e set che il controllo Web/Windows riconosce. Il metodo bind accetterà quindi solo oggetti di tipo IProperty. L’approccio ha un ulteriore vantaggio: l’interfaccia può essere pensata come wrapper per contenere metainformazioni. Non solo quindi get o set, ma anche informazioni come editabilità, visibilità, nome, …
Per abilitarla si devono modificare tutte le proprietà in modo che il tipo non sia ad es. String, ma IProperty<String>: questo però provoca una modifica nell’interfaccia della proprietà, e tutti i metodi che prima utilizzavano ad es. persona.Nome.ToUpper non funzioneranno più.
Una soluzione eccellente stà nel realizzare degli operatori di conversione impliciti da IProperty<String> a String e nell’implementare le stesse identiche funzioni di String in un suo proxy: PropertyString, che implementa IProperty<String> e dichiara le stesse funzioni di String, rigirando tali funzioni al valore String che contiene.
Con questo approccio si possono trattare a tutti gli effetti gli IProperty<T> come se fossero effettivamente dei T: i client ne possono essere inconsapevoli…
Si hanno però svantaggi seguenti:- si deve aver accesso alla modifica delle proprietà delle classi. Se della struttura ad oggetti non abbiamo il controllo l’unica soluzione che ci rimane per continuare ad usare il nostro approccio è realizzare un Adapter. Una soluzione piuttosto noiosa…
- si devono implementare tutte le classi che emulano i tipi primitivi: PropertyString, PropertyInt, PropertyBool, … è anche vero che una volta fatto diventa patrimonio vostro e rimarrà lì con voi…
- Approccio basato su Reflection Tipizzata Fortemente: una splendida soluzione basata su LambdaExpression e Generics proposta da Nick Butler. Il segreto è utilizzare i Generics per dichiarare tramite LambdaExpression la proprietà da “bindare”, più o meno in modo simile alla OrderBy di LINQ:
PropertyInfo prop = typeof(Persona).GetProperty("Nome"); // VECCHIO APPROCCIO: NON TIPIZZATO PropertyInfo prop = Instance<Persona>.GetProperty( o => o.Nome);Dove avremo:
public static class Instance<TClass> { public static PropertyInfo GetProperty<T>( Expression<Func<TClass, T>> m ) { LambdaExpression lambda = m; Expression bodyExpr = lambda.Body; MemberExpression memberExpr = (MemberExpression)bodyExpr; MemberInfo memberInfo = memberExpr.Member; PropertyInfo propInfo = (PropertyInfo)memberInfo; return propInfo; } }Nonostante l’eleganza dell’approccio (specie se combinato con extension method (si veda l’implementazione di TextBoxFor nell’articolo di MSDN), questo ha un punto in svantaggio rispetto al precedente: consente di inserire solo metadati statici tramite attributi. Viceversa, il vantaggio rispetto alla precedente è che non è necessario modificare nulla del codice preesistente e non bisogna implementare i proxy per ogni tipo primitivo
Insomma, ora che sapete tutto manca “solo” di scrivere le vostre versioni custom dei controlli che consentono il bind (consiglio un Decorator…)
Posted in .net | No Comments »