posts - 466, comments - 1536, trackbacks - 139

mercoledì 1 ottobre 2008

MVP Award 2009

Con immenso piacere ricevo l'email da parte di Microsoft con la nomina all'Award di Most Valuable Professional per l'anno 2009, sempre nella categoria Developer Security.

I miei ringraziamenti vanno al Lead ed amico Alessandro Teglia e a tutta l'organizzazione MVP, specie a quelli che  hanno sopportato (e continueranno a sopportarmi smile_regular) le mie colorite rimostranze durante l'ultimo Summit in materia di MSDN Forums e non solo.

È il mio sesto anno consecutivo e sono estremamente felice di questo nuovo premio che mi ha dato la possibilità di migliorare le mie conoscenze, essere in contatto con i team di sviluppo di vari prodotti, partecipare a conferenze esclusive come l'MVP Summit, avere accesso a numerose preview di prodotti e far arrivare i miei sfoghi per le cose che non mi piacciono direttamente alle persone che le hanno decise o implementate.

Ho cercato di condividere tutto questo con le community da cui ho ricevuto attestati di stima, nuove amicizie e preziosi scambi di opinioni tecniche che sono fondamentali per migliorarsi. Perciò un caloroso grazie anche a tutte le community.

Infine i complimenti agli altri rinnovati e un grosso benvenuto a tutti i nuovi MVP entrati.... lasciate ogni speranza ...smile_wink

posted @ mercoledì 1 ottobre 2008 18.36 | Feedback (8) |

venerdì 19 settembre 2008

TechEd 2008 Developers - Ask The Expert

Come l'anno scorso io, Mauro, Davide e Rosalba siamo nello staff della ATE al TechEd 2008 developers. Siamo fiduciosi che altri verranno aggiunti alla lista come l'anno scorso. Però ci manca il buon Lorenzo, sommo signore di tutti i party, dinner, etc. etc. smile_regular

 

Con tutte le novità che stanno per arrivare, credo che sarà un TechEd molto speciale.... see you there!!!

posted @ venerdì 19 settembre 2008 9.23 | Feedback (2) |

martedì 9 settembre 2008

Oslo e il super-secret project di Don Box

Nella home page della PDC (http://www.microsoftpdc.com/) a cui io ed altri amici MVP parteciperemo ad Ottobre è comparsa la foto degli inseparabili Don Box e Chris Anderson.

In molti si chiedevano da tempo a cosa stesse lavorando Don Box dopo WCF. Ecco che gli abstract della PDC rivelano l'arcano che peraltro dal TechEd 2007, quando sentii parlare di Oslo per la prima volta, si cominciava ad intuire.

Le sessioni sono: "Oslo, The Language":

"Oslo" provides a language for creating schemas, queries, views, and values. Learn the key features of the language, including its type system, instance construction, and query. Understand supporting runtime features such as dynamic construction and compilation, SQL generation, and deployment. Learn to author content for the "Oslo" repository and understand how to programmatically construct and process the content.

Poi "Building Testual DSLs":

This session shows you how to build your own Domain Specific Language using the "Oslo" SDK and how to apply your DSL to create an interactive text editing experience

Da sottolineare la presenza di Martin Gaudgin (che lavora fin dall'epoca COM con Don) e Chris Sells in "Repository and Schemas".

Ovviamente la PDC non è solo questo e molti degli abstract non sono neppure stati pubblicati perché la relativa tecnologia è ancora sotto NDA... in pratica partecipiamo sulla fiducia smile_regular.

Adesso devo solo escogitare il modo di essere presente in tutte le sessioni contemporaneamente. Non so cosa ho prenotato a fare l'albergo smile_tongue.

Ah come scommessa penso proprio che quel il language di OSLO sarà XAML based ... ovvio, no?

posted @ martedì 9 settembre 2008 13.09 | Feedback (8) |

mercoledì 3 settembre 2008

La convivenza tra Visual C++ 2008 SP1 e il Windows SDK

La versione attuale del Windows SDK (6.0.6001.18000.367-KRMSDK_EN) sovrascrive e compromette molti dei file di include di Visual C++.

Questo bug del setup del Windows SDK in pratica esegue un downgrade di quei files. Il risultato più evidente è che dopo la SP1 di Visual Studio 2008 non sono più disponibili le classi del nuovo standard TR1 come shared_ptr, weak_ptr, ed altre.

La soluzione suggerita dal team di Visual C++ è di eseguire una repair di Visual Studio 2008 e ri-applicare la Service Pack 1.

Speriamo che presto i signori del Windows SDK creino un nuovo SDK che eviti questo clamoroso disastro.

posted @ mercoledì 3 settembre 2008 10.38 | Feedback (2) |

martedì 26 agosto 2008

Chi scrive codice con Visual C++ cominci a dare l'addio ai puntatori (se non l'ha già fatto)

Festeggiamo la Service Pack 1 di Visual C++ 2008 perché ha rilasciato l'implementazione delle TR1 (Technical Report 1 della commissione ISO C++) la maggior parte delle quali è in sostanza l'integrazione della celeberrima libreria boost la cui integrazione nel compilatore VC++ era possibile ma non proprio una passeggiata.

Adesso non ci sono più scuse, se ancora con auto_ptr (uno smart pointer che sembrava più che altro un fool pointer) le cose erano difficili, adesso finalmente shared_ptr fa dimenticare cosa sia un puntatore.

NON sto dicendo che VC++ cambi nulla del linguaggio, anzi, implementa sempre di più quelli che sono gli standard ISO. Sto invece dicendo che grazie a queste nuove librerie, e senza installare nulla di extra, è possibile scrivere codice decisamente più pulito e realmente object oriented.

Al linguaggio C++ usato come superset del C ho sempre detto 'no grazie'.

Dimenticare i puntatori non è una penalizzazione ma un importante salto di qualità che possiamo far fare ai nostri listati. Ogni volta che usiamo un puntatore ci leghiamo indissolubilmente a una specifica zona di memoria e quindi all'hardware sottostante. Future tecnologie come transactional memory saranno inutilizzabili con listati che fanno pesante uso di puntatori.

Prendiamo ad esempio una funzione che internamente chiama una API Win32 che trasforma un SID in nome dominio e utente.

Nonostante l'uso di classi, la funzione lavora in modo C-Style. La prima chiamata a LookupAccountSid serve a farsi dire quanto devono essere grossi i buffer allocati e la seconda recupera i dati, avendo preventivamente allocato un buffer adeguato. Il problema è sempre il solito, le malefiche delete[].

Inoltre la funzione, che funge da Factory, restituisce una nuova istanza come puntatore, lasciando al chiamante il compito di ricordarsi di disallocare la memoria.

static Account* GetAccountFromSid(PSID Sid)
{
    Account *pAccount = new Account();
    pAccount->_Sid = Sid;

    LPWSTR Name, Domain;
    DWORD NameLen = 0, DomainLen = 0;
    SID_NAME_USE eUse = SidTypeUnknown;
    LookupAccountSid(NULL, Sid, NULL, &NameLen, NULL, &DomainLen, &eUse);
    if(NameLen == 0)
    {
        pAccount->_LastError = ::GetLastError();
        return NULL;
    }
    Name = (LPWSTR)new BYTE[NameLen * 2];
    Domain = (LPWSTR)new BYTE[DomainLen * 2];
    if(!LookupAccountSid(NULL, Sid, Name, &NameLen, Domain, &DomainLen, &eUse))
    {
        pAccount->_LastError = ::GetLastError();
        delete [] Name;
        delete [] Domain;
        return NULL;
    }

    pAccount->_Name = Name;
    pAccount->_Domain = Domain;
    delete [] Name;
    delete [] Domain;

    return pAccount;
}

Una implementazione alternativa che fa uso di shared_ptr è la seguente (Account e SecurityIdentifier sono la stessa classe che ha subito refactoring):

static SecurityIdentifierPtr GetFromSid(PSID Sid)
{
    SecurityIdentifierPtr spSecurityIdentifier(new SecurityIdentifier());
    spSecurityIdentifier->StoreSid(Sid);

    DWORD NameLen = 0, DomainLen = 0;
    SID_NAME_USE eUse = SidTypeUnknown;
    LookupAccountSid(NULL, Sid, NULL, &NameLen, NULL, &DomainLen, &eUse);
    if(NameLen == 0)
    {
        spSecurityIdentifier->_LastError = ::GetLastError();
        return SecurityIdentifierPtr();
    }

    shared_ptr<WCHAR> spName(new WCHAR[NameLen]);
    shared_ptr<WCHAR> spDomain(new WCHAR[DomainLen]);
    if(!LookupAccountSid(NULL, Sid, spName.get(), &NameLen, spDomain.get(), &DomainLen, &eUse))
    {
        spSecurityIdentifier->_LastError = ::GetLastError();
        return spSecurityIdentifier;
    }

    spSecurityIdentifier->_Name = spName.get();
    spSecurityIdentifier->_Domain = spDomain.get();
    spSecurityIdentifier->_SidType = eUse;
    return spSecurityIdentifier;
}
 

Il puntatore al buffer interno è allocato e mantenuto all'interno dello smart pointer e spName.get() permette il passaggio del puntatore senza mai il rischio di subire un memory leak.

Inoltre anche il passaggio dello smart pointer della istanza creata con il metodo Factory al chiamante viene fatta con lo smart pointer SecurityIdentifierPtr così definito:

class SecurityIdentifier;    // forward declaration
typedef shared_ptr<SecurityIdentifier> SecurityIdentifierPtr;

class SecurityIdentifier
{
...

Cioè abbiamo definito SecurityIdentifier come un nuovo tipo che equivale a shared_ptr<SecurityIdentifier>

Lo smart pointer verrà distrutto alla fine del metodo e ricostruito in uno nuovo del chiamante:

SecurityIdentifierPtr spsid = SecurityIdentifier::GetFromSid(Info.Sid);

 

Che dire, sembra codice tanto pulito quanto quello di C#! ... ma con tutti i vantaggi di C++ :-)

Una volta costruite le classi C++ native, non rimane che wrapparle con una dll C++/CLI e usare il tutto da C#. E con questo ci possiamo dimenticare di quelle cervellotiche dichiarazioni PInvoke.

 

Dulcis in fundo, shared_ptr può anche essere usato dentro i container come list e vector delle STL, creando così liste di puntatori che disallocano correttamente gli oggetti puntati (e quindi senza i memory leak che sarebbero derivato dall'uso di questi container con auto_ptr).

class Aces : public list<AcePtr> {};

 

Fino a prima del Feature Pack (adesso assorbito dalla SP1 di VS2008) mi ero arrangiato con smart pointer casalinghi e liste di puntatori casalinghe, etc. Ma ora è decisamente tutto più pulito e le novità delle TR1 sono ovviamente molte molte di più e tutte da scoprire.

posted @ martedì 26 agosto 2008 0.23 | Feedback (14) |

martedì 5 agosto 2008

Allergici alle traduzioni italiane? Dite la vostra!

Chi mi conosce sa bene quanto io sia allergico a certe traduzioni. È come se un americano si mettesse a tradurre tutti i termini specialistici botanici che sono in lingua latina. Allo stesso modo l'informatica è di dominio della lingua inglese e trovo azzardato tradurre termini che non hanno una ovvia e diretta corrispondenza nelle altre lingue.

Da un po' di tempo Licia Corbolante, esperta in terminologia di Microsoft Italia e preziosa alleata nel limitare le traduzioni azzardate in Italiano, ha  iniziato a bloggare. Dico preziosa alleata perché le riconosco di aver sempre cercato il giusto compromesso tra l'esigenza di tradurre per chi non è pratico e l'esigenza di non tradurre per evitare ancora più confusione.

Proprio oggi l'amico Cristian mi segnala che Licia ha lanciato l'iniziativa ha lanciato l'iniziativa del terminology community forum in cui le community possono dire la loro circa la scelta dei termini.

No more excuses! Fatevi sotto e dite la vostra se la scelta di un termine tradotto non vi piace.

posted @ martedì 5 agosto 2008 0.37 | Feedback (6) |

martedì 29 luglio 2008

Troppo POCO ... il troppo stroppia

Non posso che concordare sul discorso Persistence Ignorance fatto pìù volte da Andrea sull'entity framework.

Con altrettanta convinzione affermo però che la completa ignoranza dall'infrastruttura è utopica in scenari reali:

  • INotifiyPropertyChanged /  INotifiyPropertyChanging ha un costo neppure tanto basso quando è implementato negli oggetti a causa del raising degli eventi. Nella RafCollection avevo scritto INotifiyPropertyChanging ben prima che il framework la introducesse ma sono tornato indietro sfruttando la INotifiyPropertyChanged con parametro null per motivi di performance.
    Se penso di usare addirittura un proxy, i valori di performance si alzano vertiginosamente.
  • IEditableObject è inerentemente compito dell'entity in quanto gestisce la transazionalità dello stato dell'entity stessa. Questa interfaccia garantisce la coerenza dello stato dell'oggetto quando vengono effettuate modifiche a più proprietà.
  • Cloning. Nessuna interfaccia qui per il noto articolo di Brad Abrams. Ma non c'è dubbio che il clone di un oggetto è molto più efficiente se fatto su membri *privati*.
    Esempio: Il datetime mantiene un intero a 64 bit. Se io per clonarlo copio tutte le proprietà (giorno, mese, anno, ...) invece di duplicare il valore a 64 bit sto facendo un pessimo lavoro.
    Ricordiamoci che non tutte le entity sono fatte di fields 1:1 con le proprietà e una strategia come POCO la si implementa per tutte le entity o nessuna.
  • ISerializable / IXmlSerializable. E qui faccio veramente fatica a vedere un proxy che possa risolvermi questa situazione in modo anche lontanamente decente. Il serializzatore Xml tra le altre cose genera al volo con CodeDom il codice per generare una serializzazione performante.
    Io mi sono scritto un serializzatore Xml che fa le stesse cose di quello standard ma le performance sono ben lontane da quelle ottenute grazie a CodeDom.

Poi arriviamo a motivazioni più pratiche. Il costo di creazione e manutenzione dei proxy e del codice che serve a fare quelle stesse cose (ma dall'esterno dell'entity) non è poco: maggiore complessità e ciclo di vita più lungo del software.

BTW, come ho detto ai Community Days, mi stupisce che i framework IoC e il neonato MEF lavorino ancora così tanto di reflection invece di sana generazione di codice adhoc a *design* time.

E una volta che fosse possibile trovare il compromesso e arrivare finalmente ad una totale agnosticità del domain model, che ci guadagno? È tutto scritto in C#, dipende tutto dal framework, da un CLR all'altro certe classi potrebbero diventare obsolete, ...

Insomma *IMHO* bisogna arrendersi al fatto che tutto quello che scriviamo è legato alla tecnologie che usa. Questo non toglie che questo vincolo possa permetterci il cross-platform (vedi Mono) ma non certo l'universalità.

Urka, una volta che non sono daccordo con Il Presidente, adesso rischio dei perdere i gradi smile_tongue

posted @ martedì 29 luglio 2008 13.56 | Feedback (5) |

lunedì 28 luglio 2008

Architetture multilayer fatte di programmazione funzionale, provider e servizi

L'ingresso di Linq nel Framework è stato prepotente. I tangibili vantaggi di Linq to Sql e le promesse delle future versioni di Entity Framework hanno scatenato una vera e propria corsa a Linq. E pensare che siamo solo alle prove tecniche di trasmissione dei concetti di programmazione funzionale dentro il Framework ...

Mi è saltata in mente un'idea balzana. Ma voglio prima partire riaffermando alcuni concetti ormai consolidati da case studies e libri di fama:

  • Le architetture multilayer sono più facili da difendere (sicurezza applicativa)
  • L'astrazione con provider è un vantaggio in termini di evoluzione dell'applicazione e di manutenibilità
  • I servizi (stateless) garantiscono un alto margine di scalabilità

Vediamo come sfruttare la programmazione funzionale in una architettura multilayer. Per esempio la UI:

  • tipicamente esegue molte operazioni di selezione di entità caratterizzate da  un certo numero di proprietà [projection]
  • ha la necessità di filtrare i risultati [restriction]
  • spesso deve paginare i risultati perché sono comunque troppi [partitioning]
  • deve anche ordinare i risultati secondo un criterio [ordering]
  • può avere bisogno di raggruppare [grouping] o di aggregare (sum, count, max, ...) [aggregation]
  • ...

Tutto questo ricorda molto gli operatori di Linq smile_regular

Il service layer da parte sua espone già un contratto che permette una astrazione rispetto il data server o comunque le risorse sottostanti. Ma la difficoltà sta proprio nel contratto che il più delle volte è "ad hoc" per la nostra applicazione. Un domani che dovessimo usare un contratto differente, dovremmo necessariamente applicare una di queste soluzioni: (a) cambiare la UI per supportare il nuovo servizio, (b) creare un nuovo servizio che "trasformi" i dati con il nostro formato, (c) usare BizTalk per piallare le differenze.

L'idea balzana è semplice:

  • usare i provider di Linq come "adapter" per lo strato dei servizi
  • i provider useranno gli expression tree (che sono serializzabili) come "linguaggio" per mappare le richieste della UI sul servizio (Expression.Call esegue un determinato metodo)
  • Le Expression.Call possono essere usate anche per le operazioni che non facciano mero retrieval di valori (per esempio insert/update/delete)
  • usare sempre gli expression tree per le restriction (And, Not, Or, etc.). [Questo non esclude di poter avere oggetti di business differenti nel presentation e nel service layer in quanto convertire un expression tree non è complesso.]
  • ...

Il vantaggio immediato è quello di ottenere un presentation veramente sterile, persino dai servizi; ...di avere una architettura a provider "gratis"; ...di avere i preziosi expression tree senza alcuno sforzo; ... di lavorare in modo strong-typed.

Sento già le ambulanze suonare ma io la vedo così...

posted @ lunedì 28 luglio 2008 21.06 | Feedback (6) |

venerdì 25 luglio 2008

[OT] Xobni rocks!

Un po' di tempo fa, quando mi feci riconoscere come utente di Xobni, "Il solito ignoto" mi prese di mira (in modo assolutamente benevolo, si intende). Da tempo lo usavo e avevo il suo logo nella home del mio blog.

Per chi non lo conoscesse Xobni, nome derivato da Inbox al contrario, è un plugin per Outlook che organizza e cerca le email. Non è il solito indexer e, a onor del vero, i risultati della ricerca sono ottimi ma non sono sempre così ben fruibili rispetto ai più noti google search e windows desktop search.

Ho cercato più volte di convincere il nostro super-esperto di Web 2.0 ad usarlo e dalla RTM già include una feature tipica da Web 2.0 che continua a stupirmi per quanto sia fantastica.

Sto parlando dell'integrazione con LinkedIn che fornisce un volto ai mittenti delle email. Seleziono una email, e parte la ricerca della foto, se presente e resa pubblica, presente nel profilo del mittente su LinkedIn.

Semplicemente geniale e così non stupisce che lo stesso Bill Gates abbia definito Xobni come la prossima generazione del social networking.

Adesso spero che arrivi anche l'integrazione con Facebook che sembrerebbe il candidato perfetto.

posted @ venerdì 25 luglio 2008 20.11 | Feedback (4) |

giovedì 17 luglio 2008

Eseguire query su file xlsx (Office OpenXML) via Linq

Dopo il post su come fare query sui file docx, ecco un esempio sugli spreadsheet in formato xlsx.

I discorsi sui Package fatti nel post precedente sono ovviamente identici visto che anche xlsx usa il package OPC. Perciò la versione 1.0 dell'SDK di Office OpenXML continua ad essere di aiuto in quanto contiene i wrapper alle parts specifiche del formato spreadsheet.

I file xlsx sono più strutturati rispetto ad un documento docx. I punti fondamentali sono i seguenti:

  • In ogni xlsx  esiste un workbook che elenca i fogli (sheet) presenti.
  • Ciascuno sheet è rappresentato da un file xml separato all'interno del file xlsx
  • In ogni xlsx esiste una tabella delle stringhe più usate chiamata "shared strings". In sostanza la stringa scritta in una cella può essere scritta direttamente nella cella oppure referenziare (tramite un indice numerico) la stringa scritta dentro la tabella delle shared strings.

Chi userà la mia libreria vorrà almeno queste funzionalità:

  • I metodi per caricare uno sheet sia per numero (indice zero based) che per nome
  • Il metodo per caricare la tabella delle shared strings
  • Tutte le funzioni che cercano del testo dovranno farlo sia nelle celle che nella tabella shared strings

Iniziamo quindi referenziando la "DocumentFormat.OpenXML.dll" e creando una nuova classe con alcune dichiarazioni che useremo più avanti, tra cui i namespace di OOXML usati più frequentemente:

   1: public class ExcelHelper
   2: {
   3:     public static XNamespace NS_ex = XNamespace.Get("http://schemas.openxmlformats.org/spreadsheetml/2006/main");
   4:     public static XNamespace NS_r = XNamespace.Get("http://schemas.openxmlformats.org/officeDocument/2006/relationships");
   5:     private string _FileName;
   6:  
   7:     private RafSheetCollection _RafSheets;
   8:     private IEnumerable<SharedString> _SharedStrings;
   9:     private XElement _CurrentSheet;
  10:     public XElement CurrentSheet
  11:     {
  12:         get
  13:         {
  14:             if (_CurrentSheet == null)
  15:                 LoadSheet(0);
  16:             return _CurrentSheet;
  17:         }
  18:         set { _CurrentSheet = value; }
  19:     }
  20:  
  21:     public ExcelHelper(string FileName)
  22:     {
  23:         _FileName = FileName;
  24:     }
  25:  
  26: ...

In questo momento l'sdk di OpenXML non ci fornisce un metodo per avere l'elenco dei sheet presenti all'interno di un file xlsx. Per questo motivo dichiaro una nuova classe che manterrà queste informazioni:

   1: public class RafSheet
   2: {
   3:     public string Name { get; set; }
   4:     public string SheetId { get; set; }
   5:     public string Id { get; set; }
   6: }

Id rappresenta il relationship id ed è quello più importante. Id non è indispensabile ma lo carico ugualmente visto che il numero di sheet presenti in un file è sempre modesto.

Poiché possono esserci tanti sheet, dichiaro una collection di RafSheet:

   1: public class RafSheetCollection : List<RafSheet>
   2: {
   3:     public RafSheetCollection() {}
   4:  
   5:     public RafSheetCollection(IEnumerable<RafSheet> Sheets)
   6:     {
   7:         foreach (RafSheet Sheet in Sheets)
   8:             this.Add(Sheet);
   9:     }
  10:  
  11:     public RafSheet this[string Name]
  12:     {
  13:         get
  14:         {
  15:             foreach (RafSheet sheet in this)
  16:             {
  17:                 if (string.Compare(sheet.Name, Name, true) == 0)
  18:                     return sheet;
  19:             }
  20:             throw new IndexOutOfRangeException();
  21:         }
  22:         set
  23:         {
  24:             for(int i=0; i<this.Count; i++)
  25:             {
  26:                 RafSheet sheet = this[i];
  27:                 if (string.Compare(sheet.Name, Name, true) == 0)
  28:                 {
  29:                     this[i] = value;
  30:                     return;
  31:                 }
  32:             }
  33:             this.Add(value); // se non lo trova, lo aggiunge
  34:         }
  35:     }
  36: }

A questa collection ho aggiunto due caratteristiche comode:

  • Un costruttore che aggiunge alla propria istanza tutti gli elementi di un'altra collection di sheets
  • Un indexer che è in grado di recuperare un RafSheet per nome (get) o aggiungerlo (set)

A questo punto torno a scrivere i metodi della ExcelHelper. È arrivato il momento di caricare lo sheet desiderato.

   1: public void LoadSheet(int Sheet)
   2: {
   3:     using (SpreadsheetDocument xlPackage = SpreadsheetDocument.Open(_FileName, true))
   4:     {
   5:         WorkbookPart workbook = xlPackage.WorkbookPart;
   6:  
   7:         PopulateRafSheets(workbook);
   8:  
   9:         OpenXmlPart part = workbook.GetPartById(_RafSheets[Sheet].Id);
  10:         LoadSheet(part);
  11:         LoadSharedStrings(workbook);
  12:     }
  13: }
  14:  
  15: public void LoadSheet(string SheetName)
  16: {
  17:     using (SpreadsheetDocument xlPackage = SpreadsheetDocument.Open(_FileName, true))
  18:     {
  19:         WorkbookPart workbook = xlPackage.WorkbookPart;
  20:  
  21:         PopulateRafSheets(workbook);
  22:  
  23:         OpenXmlPart part = workbook.GetPartById(_RafSheets[SheetName].Id);
  24:         LoadSheet(part);
  25:         LoadSharedStrings(workbook);
  26:     }
  27: }

Il primo carica il sheet per indice (zero based) mentre il secondo per nome, proprio grazie alle due classi che ho scritto prima.

Come per il docx la tecnica per accedere ai file xml interni è la medesima:

  • Aprire il package (_Filename è stato popolato nel costruttore di ExcelHelper)
  • Prendere un riferimento alla parte del package che descrive il workbook
  • Popolare la nostra collection con le informazioni degli sheets (vedi sotto)
  • Recuperare la part (il file xml) relativo allo sheet voluto (per nome o indice)
  • Caricare in memoria lo sheet e il file delle shared strings

È arrivato il momento di popolare la collection di RafSheet.

   1: private void PopulateRafSheets(WorkbookPart workbook)
   2: {
   3:     if (_RafSheets != null)
   4:         return;
   5:  
   6:     using (StreamReader streamReader = new StreamReader(workbook.GetStream()))
   7:     {
   8:         using (XmlReader stream = XmlReader.Create(streamReader))
   9:         {
  10:             XElement book = XElement.Load(stream);
  11:  
  12:             var v =
  13:                 from b in book.Descendants(NS_ex + "sheets")
  14:                 from s in b.Descendants(NS_ex + "sheet")
  15:                 select new RafSheet()
  16:                 {
  17:                     Name = s.Attribute(XName.Get("name")).Value,
  18:                     Id = s.Attribute(XName.Get("id", NS_r.NamespaceName)).Value,
  19:                     SheetId = s.Attribute(XName.Get("sheetId")).Value
  20:                 };
  21:  
  22:             _RafSheets = new RafSheetCollection(v);
  23:         }
  24:     }
  25: }

Questo metodo riceve il workbook al cui interno è scritto semplicemente l'elenco degli sheet. Una volta caricato in memoria il contenuto xml del workbook, eseguo una query Linq per recuperare nome, id e sheetId di ciascuno sheet.

L'xml del workbook è fatto così:

   1: <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   2: <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
   3:     xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
   4:     <sheets>
   5:         <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
   6:         <sheet name="Sheet2" sheetId="2" r:id="rId2"/>
   7:         <sheet name="Sheet3" sheetId="3" r:id="rId3"/>
   8:     </sheets>
   9: </workbook>

Resta ancora da caricare la shared string table. Da ricordarsi che le stringhe vengono referenziate per indice (ordine di inserimento) e che non contengono un id. Ancora una volta usiamo Linq per recuperare il testo

   1: private void LoadSharedStrings(WorkbookPart workbook)
   2: {
   3:     if (_SharedStrings != null)
   4:         return;
   5:     
   6:     SharedStringTablePart Shared = workbook.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
   7:     if (Shared == null)
   8:     {
   9:         _SharedStrings = new List<SharedString>(); // lista vuota
  10:         return;
  11:     }
  12:  
  13:     using (StreamReader streamReader = new StreamReader(Shared.GetStream()))
  14:     {
  15:         using (XmlReader stream = XmlReader.Create(streamReader))
  16:         {