DarioSantarelli.Blog("UgiDotNet");

<sharing mode=”On” users=”*” />
posts - 176, comments - 105, trackbacks - 3

My Links

News


This is my personal blog. These postings are provided "AS IS" with no warranties, and confer no rights.




Tag Cloud

Archives

Post Categories

My English Blog

[Silverlight 5] ICustomTypeProvider

La beta di Silverlight 5 ha introdotto una nuova interfaccia ICustomTypeProvider che permette di effettuare il binding tra elementi della UI ed oggetti la cui struttura non è nota a compile-time. Nello specifico, l’esigenza che questa interfaccia soddisfa è evidente quando si lavora con dati provenienti da fonti eterogenee (database, istanze XML, oggetti JSON, dati binari, csv etc.) che si desiderano presentare senza ricompilare ogni volta che viene aggiunto/rimosso un attributo, una colonna o un campo dalla sorgente dati. In questi contesti si può valutare di rendere flessibile il meccanismo di binding permettendo di aggiungere/rimuovere proprietà a runtime lasciando al motore di databinding il compito di determinarne il tipo senza obbligare lo sviluppatore a ri-compilare/ri-deployare l’applicazione ogni volta che si necessita di una modifica al model. L’interfaccia ICustomTypeProvider è così definita:

public
 interface ICustomTypeProvider
{
    Type GetCustomType();
}

Quando la si implementa, viene chiesto di ritornare un oggetto di tipo System.Type. Oggi l’engine di Silverlight 5 controlla in fase di binding se l’oggetto che stiamo passando implementa o meno l’interfaccia ICustomTypeProvider per capire se usare i metadati del nostro tipo custom piuttosto che del fallback System.Type.
Bisogna dire che l’implementazione di questa interfaccia richiede un certo sforzo iniziale, poiché implica la creazione di un tipo custom in grado di gestire ad esempio un “repository” di proprietà (PropertyInfo) che possiamo aggiungere/rimuovere dinamicamente. Inoltre, non dobbiamo dimenticarci della solita INotifyPropertyChanged utile per il corretto funzionamento del binding e di INotifyDataErrorInfo per la validazione. I benefici che otteniamo sono tuttavia notevoli rispetto alle versioni precedenti di Silverlight, dove non è possibile derivare Type o PropertyInfo per questi scopi.

Esempio di utilizzo
A titolo di esempio, proviamo a definire una classe DictionaryObject che, come il nome fa intendere, espone a mo’ di dictionary l’elenco delle sue proprietà in modo tale che possono essere aggiunte e rimosse a runtime. L’obiettivo è rendere “binding-friendly” queste coppie chiave-valore!

public class DictionaryObject : ICustomTypeProvider, INotifyPropertyChanged
{
    private Dictionary<string, object> _properties = null;

    public event PropertyChangedEventHandler PropertyChanged;

    public object this[string propertyName]
    {
        get { return _properties.ContainsKey(propertyName) ? _properties[propertyName] : null; }
        set
        {
            AddProperty(propertyName);
            if (_properties[propertyName] != value)
            {
                _properties[propertyName] = value;
                OnPropertyChanged(propertyName);
            }
        }
    }

    public DictionaryObject()
    { 
        _properties = new Dictionary<string, object>(); 
    }

    public bool AddProperty(string name) 
    { 
        return AddProperty(name, null); 
    }

    public bool AddProperty(string name, object value)
    {
        if (!_properties.ContainsKey(name))
        {
            _properties.Add(name, value);
            return true;
        }
        else return false;
    }

    public bool RemoveProperty(string name)
    {
        if (_properties.ContainsKey(name))
        {
            _properties.Remove(name);
            return true;
        }
        else return false;
    }

    public Type GetCustomType() 
    { 
        return new DictionaryObjectType(_properties);
    }

    protected void OnPropertyChanged(string key)
    {
        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(key));
    }
}

Affinché il runtime di Silverlight tratti le coppie chiave-valore alla stregua di proprietà vere e proprie di un generico oggetto del CLR, il trucco sta nel definire un tipo custom in grado di fornire al volo tutti i metadati che servono in fase di binding. Riportiamo solo le righe di codice rilevanti…

public class DictionaryObjectType : Type
{
   private Type _proxyType = typeof(DictionaryObject);
   private Dictionary<string, object> _propertiesDictionary = null;
  
  
   public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
   {
      PropertyInfo[] properties = _proxyType.GetProperties(bindingAttr);

      if (BindingFlags.Instance == (bindingAttr & BindingFlags.Instance) &&
          BindingFlags.Public == (bindingAttr & BindingFlags.Public))
          {
             return GetDynamicProperties().ToArray();
          }

      return properties;
   }
  
  
   private List<PropertyInfo> GetDynamicProperties()
   {
     List<PropertyInfo> properties = new List<PropertyInfo>();

     foreach (string key in _propertiesDictionary.Keys)
     {                
        if (_propertiesDictionary[key] != null
// null values will be ignored!
        {
            properties.Add(new DictionaryObjectPropertyInfo(_propertiesDictionary[key].GetType(), typeof(DictionaryObject), key));
        }
     }

     return properties;
   }
}

Per gli oggetti che implementano l’interfaccia ICustomTypeProvider, abbiamo la possibilità di intercettare le richieste di informazioni di reflection che provengono dal motore di databinding e fornire metadati personalizzati. Nel nostro esempio, per comunicare al runtime come fare la get e la set sulle proprietà di un DictionaryObject, abbiamo bisogno di ereditare anche PropertyInfo.

public class DictionaryObjectPropertyInfo : PropertyInfo
{
    private Type _propertyType;
    private Type _declaringType;
    private string _name;

    public DictionaryObjectPropertyInfo(Type propertyType, Type declaringType, string propertyName)
    {
        _propertyType = propertyType;
        _declaringType = declaringType;
        _name = propertyName;
    }
 
    ...
   
    public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
    {
        DictionaryObject dictionaryObject = (DictionaryObject)obj;            
        return dictionaryObject[Name];
    }
      
    public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
    {
        DictionaryObject dictionaryObject = (DictionaryObject)obj;
        dictionaryObject[Name] = value;
    }
}

OK non abbiamo bisogno di altro! Proviamo a utilizzare concretamente la classe DictionaryObject. Creiamo una view con una DataGrid le cui colonne vengono autogenerate. L’obiettivo è di rendere il databinding bidirezionale e consistente rispetto al tipo CLR di ciascuna proprietà esposta.

<UserControl x:Class="ICustomTypeProviderSample.MainPage"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
   xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
    <StackPanel x:Name="LayoutRoot" HorizontalAlignment="Center" Margin="20">
        <sdk:DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Items}" />       
    </StackPanel>
</UserControl
>

Nel viewmodel definiamo le proprietà degli items come coppie chiave-valore.

public class MainPageViewModel : INotifyPropertyChanged
{
    ...

    public ObservableCollection<DictionaryObject> Items { get; private set; }        

    public MainPageViewModel()
    {            
        Items = new ObservableCollection<DictionaryObject>();
            
       
DictionaryObject newItem = new DictionaryObject();        
        newItem["ID"] = 1;
        newItem["Name"] = "Item 1";
        newItem["UnitPrice"] = 5.3;
        newItem["Created"] = DateTime.Now;    
    
        Items.Add(newItem);

        ...
   }
}  

La view risultante è coerente rispetto ai tipi delle proprietà. L’oggetto DictionaryObject implementa l’interfaccia INotifyPropertyChanged (e preferibilmente INotifyDataErrorInfo) quindi la nostra interazione con le celle della DataGrid si riflette direttamente sul valore delle proprietà coinvolte nel databinding.

 

L’esempio mostrato è molto banale ma fa intendere quanti miglioramenti possono essere concepiti. Il codice completo di questo esempio è scaricabile al seguente link.

Ma non esisteva qualcosa del genere anche in WPF? 
A chi possiede familiarità con l’interfaccia ICustomTypeDescriptor di WPF (la quale risolve lo stesso problema di ICustomTypeProvider di Silverlight 5… i nomi addirittura sono più o meno gli stessi!) verrà subito in mente la domanda “Perché non hanno aggiunto direttamente l’interfaccia ICustomTypeDescriptor”?
Leggendo la documentazione MSDN non sono riuscito ad estrarre una risposta chiara. Sembrerebbe che il motivo (per ora almeno) sia dovuto al fatto che ICustomTypeDescriptor richiede una sua gerarchia di classi  (TypeDescriptor, EventDescriptor, etc.) che di fatto duplica la gerarchia di reflection, il che incrementerebbe la dimensione del pacchetto Silverlight da installare client-side.

E il DLR?
Questa feature in realtà non ha nulla a che vedere con il DLR. Oggetti del DLR come ExpandoObject o DynamicObject (o qualunque implementazione dell’interfaccia IDynamicMetaObjectProvider) non apportano alcun tipo di informazione sulle loro proprietà. Il motore del databinding ha invece bisogno di queste informazioni per effettuare correttamente conversioni di tipo per tutto ciò che non sia una semplice stringa. Ad esempio, se abbiamo una TextBox in binding con una proprietà DateTime di un oggetto, quando inseriamo un nuovo valore nella TextBox l’engine valuta il tipo della proprietà e converte il testo contenuto nella TextBox da stringa a DateTime e viceversa. Se invece si effettua il binding su una proprietà di un ExpandoObject, il valore della proprietà sarà convertito semplicemente a stringa e non possono essere fatte valutazioni di binding. Insomma, in WPF è possibile associare elementi del DLR agli elementi della UI, ma non avviene nessuna conversione di tipo… tutto ciò su cui possiamo lavorare sono solo stringhe.
In definitiva, mentre il DLR è conveniente quando si interagisce con altri linguaggi o altre piattaforme, non è consigliabile per il databinding e l’interazione con la UI.

Riferimenti
Segnalo i due interessanti articoli da cui ho preso spunto:

Print | posted on martedì 17 maggio 2011 02:51 | Filed Under [ Silverlight ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET