Qualche ragionamento su System.Reflection ed Interop con Outlook
In queste sere mi sto divertendo, se così si può dire, con la creazione di un plug-in per Microsoft Outlook 2007. Lo scopo è quello di importare nel folder Contacts di Outlook un’archivio di anagrafica, suddiviso in diverse categorie. Per farla breve vi faccio vedere solo due tabelle che illustrano il contenuto dell’anagrafica di cui sto parlando.
La tabella Tipi contiene la definizione delle categorie. Il campo “Tipo†contiene il nome della categoria (Cantanti, Gruppi, Discoteche, Pub, Feste in Piazza, etc.). Il campo “Lettera†contiene un singolo carattere (C, G, D, P, F, etc.) che indica con quale lettera è identificata una certa categoria; tale campo serve per andare in JOIN sulla tabella Anagrafica. In questo modo ogni record di Anagrafica appartiene ad una ed una sola categoria della tabella Tipi. Le relazioni mostrate nell’immagine qui sopra credo siano abbastanza chiare. Non accetto critiche sul database: l’ho ereditato io 10 anni fa su Access 2.0 e oggi gira in produzione su Access 2003 dopo tutti questi anni. Mi dovrei vergognare per questo? Forse sì, forse no, ma così è e così me lo prendo. 🙂
Comunque, lo scopo è quello di elaborare ogni record di Anagrafica e di creare un’istanza di Microsoft.Office.Interop.Outlook.ContactItem, popolandone tutte le proprietà pubbliche, e poi di salvarlo dentro Outlook 2007. Ecco uno stralcio di C# per dare l’idea:
1 foreach (MyDataSet.AnagraficaRow row in dataset.Anagrafica.Rows) 2 { 3 if (ContactAdding != null) ContactAdding(this, new ContactEventArgs(string.Empty)); 4 Outlook.ContactItem item = ContactServices.CreateContact(row); 5 6 if (item != null) 7 { 8 item.Save(); 9 if (ContactAdded != null) ContactAdded(this, new ContactEventArgs(item.FullName)); 10 } 11 }
Dato che si tratta di un prototipo mi sono semplificato la vita con un DataSet tipizzato. Ciclo su tutte le righe presenti nella DataTable. Per ogni riga sollevo due eventi ContactAdding e ContactAdded (ContactAdding e ContactAdded sono due public event, ContactEventArgs è una classe derivata da EventArgs). Ma il succo è la chiamata al metodo statico CreateContact, metodo che prende in input un’istanza di AnagraficaRow e restituisce un’istanza di ContactItem.
Tecnicamente parlando, possiamo implementare questo metodo in tanti modi diversi. Io ne ho pensati ed implementati due:
- assegno una ad una le proprietà pubbliche di ContactItem valorizzandole con i campi esposti da AnagraficaRow
- uso Reflection per fare qualcosa di più ingegnoso e gestibile nel prossimo futuro
Per il punto (1) il codice dovrebbe essere una cosa simile a questa:
1 ContactItem item = (ContactItem)folder.Items.Add(OlItemType.olContactItem); 2 if (!row.IsDenominazioneNull()) item.FullName = row.Denominazione; 3 if (!row.IsTitolareNull()) item.ManagerName = row.Titolare; 4 ... 5 ...
Alla riga (1) creo un’istanza di ContactItem, poi valorizzo le proprietà FullName, ManagerName e così via, per le decine di campi di cui necessitate (le mie sono 18). Noioso e davvero poco manutenibile.
Il punto 2 è più interessante, perchè tramite Reflection possiamo creare dinamicamente un’istanza di ContactItem. Creiamo una classe ContactFieldImport così implementata:
1: public class ContactFieldImport
2: {
3: public string Property { get; private set; }
4: public string Field { get; private set; }
5:
6: public ContactFieldImport(string property, string field)
7: {
8: Property = property;
9: Field = field;
10: }
11: }
Ogni istanza di ContactFieldImport rappresenta una coppia di Property/Field. Possiamo così definire una List<ContactFieldImport>:
1: List<ContactFieldImport> props = new List<ContactFieldImport>();
2: props.Add(new ContactFieldImport("FullName", "Denominazione"));
3: props.Add(new ContactFieldImport("ManagerName", "Titolare"));
4: props.Add(new ContactFieldImport("HomeAddress", "Indirizzo"));
5: props.Add(new ContactFieldImport("HomeAddressPostalCode", "CAP"));
Ogni coppia definisce come popolare un oggetto ContactItem. Prendo il valore del campo (Field) e con esso valorizzo la proprietà pubblica (Property) di ContactItem. A questo punto il gioco è semplice.
1: foreach (ContactFieldImport cfi in props)
2: {
3: PropertyInfo pi = typeof(_ContactItem).GetProperty(cfi.Property);
4:
5: if (pi != null && pi.CanWrite && !string.IsNullOrEmpty(row[cfi.Field].ToString()))
6: {
7: pi.SetValue(item, row[cfi.Field], null);
8: }
9: }
Itero su tutti gli elementi contenuti nella lista props. Per ciascuno degli elementi, ottengo un riferimento alla PropertyInfo puntata da cfi.Property. Se la PropertyInfo è valida, può essere scritta (CanWrite == true) e se l’AnagraficaRow corrente è una stringa valida, allora imposto la proprietà dell’istanza di ContactItem (riga 7).
Dal punto di vista prestazionale, il ciclo for…each qui sopra è molto più veloce rispetto al primo metodo, quello che cioè che settava le proprietà pubbliche di ContactItem una ad una, senza una sorta di automatismo. Non so darvi con esattezza l’ordine di grandezza, ma ho provato ad importare con tutti e due i metodi qualcosa come 1.400 anagrafiche e tramite Reflection le cose erano molto migliori (< 1 minuto).
Il risultato finale visto in Outlook 2007 è il seguente:
Dentro Contatti ho creato un folder Winshow Planning (nome del progetto), all’interno del quale ho creato tre subfolders (Agenti, Artisti e Luoghi). In ciascuno dei tre ho finalmente creato tanti ulteriori subfolders (presi dal database). Ogni folder contiene i contatti assegnati ad ogni categoria.
‘Sto post è davvero troppo lungo e prolisso. Se avete domande, sono qua a disposizione. 😉