Sei sulla pagina 1di 55

UNIVERSITÀ DEGLI STUDI DI TRIESTE

FACOLTÀ DI INGEGNERIA

Dipartimento di Elettrotecnica, Elettronica ed Informatica

Tesi di Laurea Triennale in


Ingegneria Informatica

Sviluppo di un’applicazione per l’esportazione di dati da


una base di dati per la ricerca scientifica

Laureando : Relatore :
Andrea Inglese Chiar.mo P rof. Maurizio Fermeglia

A.A. 2007/2008
Indice
1 Introduzione 2

2 Analisi 3
2.1 Approccio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2 Il trasferimento tra due siti web . . . . . . . . . . . . . . . . . 3
2.3 Requisiti della libreria . . . . . . . . . . . . . . . . . . . . . . 4
2.4 Struttura della libreria . . . . . . . . . . . . . . . . . . . . . . 7
2.4.1 Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4.2 Configurazione . . . . . . . . . . . . . . . . . . . . . . 9
2.4.3 Processore di input per file xml . . . . . . . . . . . . . 11
2.4.4 Processore per la modifica dello schema dei dati . . . 11
2.4.5 Processori basati su Ado.Net . . . . . . . . . . . . . . 12
2.4.6 Processori basati su http . . . . . . . . . . . . . . . . . 13
2.5 Struttura delle soluzioni per il caso specifico . . . . . . . . . . 19
2.5.1 La soluzione desktop application . . . . . . . . . . . . 19
2.5.2 La soluzione web-based e Silverlight . . . . . . . . . . 20

3 Implementazione 23
3.1 Progetto Core . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2 Progetto ConfigUI . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3 Progetto Xml . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.1 RowProcessors . . . . . . . . . . . . . . . . . . . . . . 37
3.4 Processori basati su Ado.Net . . . . . . . . . . . . . . . . . . 38
3.4.1 DbInputProcessor e DbOutputProcessor . . . . . . . . 38
3.4.2 MsSqlServerInputProcessor e MsSqlServerOutputPro-
cessor . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.5 Processori basati su http . . . . . . . . . . . . . . . . . . . . . 41
3.5.1 HttpXmlInputProcessor . . . . . . . . . . . . . . . . . 41
3.5.2 HttpPostOutputProcessor . . . . . . . . . . . . . . . . 42
3.5.3 BrowserFillerOutputProcessor . . . . . . . . . . . . . 44
3.5.4 HttpSubmitterOutputProcessor . . . . . . . . . . . . . 46
3.6 Interfaccia utente WPF . . . . . . . . . . . . . . . . . . . . . 48
3.7 Implementazione della soluzione web-based . . . . . . . . . . 49
3.7.1 Progetto SilverlightCore . . . . . . . . . . . . . . . . . 49
3.7.2 Interfaccia utente web . . . . . . . . . . . . . . . . . . 50

4 Conclusioni 51

5 Riferimenti bibliografici 52

1
1 Introduzione
Il lavoro svolto consiste nella creazione di una libreria general-purpose per
l’esportazione e manipolazione di dati ed una sua applicazione.
Scopo della libreria è quello di costituire un framework per l’assemblaggio
rapido di applicazioni per il trasferimento e la manipolazione di dati. E’ stato
quindi sviluppato anche un tool di configurazione visuale per la libreria.
La libreria è stata quindi utilizzata in uno scenario reale con l’obiettivo
di facilitare il trasferimento di dati dal sito <www.units.it/~datiric>
al sito <www.uni2b.com>. Per il caso specifico sono state sviluppate due
interfacce utente, una basata sul web e un’applicazione desktop.
Vincolo principale del progetto di esportazione dati verso uni2b.com
è la chiusura del sistema. Il sito è stato infatti considerato come non-
modificabile, e tutte le interazioni con esso avvengono attraverso l’interfaccia
utente del sito stesso.
L’intera soluzione è stata sviluppata basandosi su tecnologia .net; frame-
work 3.5 per quanto riguarda la libreria, Silverlight 2.0 per la soluzione
web e Windows Presentation Foundation per quanto riguarda l’applicazione
desktop.

2
2 Analisi
2.1 Approccio
La fase di analisi per determinare i requisiti della libreria e dell’interfac-
cia per la sua applicazione è iniziata dallo studio di un caso particolare,
l’esportazione di dati tra due siti web, per poi generalizzare una soluzione
utilizzabile indipendentemente dalla sorgente e dalla destinazione dei dati.

2.2 Il trasferimento tra due siti web


Nel caso specifico si richiedeva l’esportazione di un set limitiato di dati dal
sito dell’Università di Trieste per la ricerca scentifica al sito web uni2b.com.
Il sito di origine dei dati è realizzato tramite un’applicazione php, con
una restrizione all’accesso basata sull’autenticazione forms e il mantenimento
della sessione tramite cookie. I dati vengono esportati dal sito in formato
xml attraverso una richiesta get o post, all’interno della quale è possibile
specificare l’entità da esportare. I dati da esportare sono riconducibili a due
entità, progetti e strumenti.

Figura 1: Entità esportate da units.it

Il sito di destinazione dei dati utilizza la stessa modalità di autenticazione


e gestione della sessione.
L’applicazione web in questione non è predisposta all’interazione machine-
to-machine, non è quindi previsto un webservice o una qualche altra inter-
faccia standard. Gli utenti del sito possono inviare dei dati attraverso delle
form html. Le form di interesse per l’importazione dei dati sono due, che
permettono di aggiungere un record a due classi di dati, progetti e strumenti.
Poichè come vincolo progettuale il sito non è alterabile, l’applicazione
da sviluppare dovrà interagire con questo attraverso le interfacce utente
sviluppate in html.
Come si può notare dalle differenze delle entità strumenti e progetti tra
i due siti (figure 2 e 1), l’incompletezza dei dati esportati rispetto a quelli
richiesti da uni2b.com e la necessità di porre modifiche o revisione dei dati
stessi richiedono un alto livello di interazione da parte dell’utente. L’inter-
faccia utente dovrà permettere di scegliere l’entità su cui si vuole lavorare

3
Figura 2: Entità esportate da uni2b.com

(progetti e strumenti), la ricerca e selezione del record da trasferire e l’ag-


giunta o correzione dei dati del record da trasferire. Deve inoltre concedere
all’utente la possibilità di confrontare i record esportabili (presenti nel sito
di origine) con quanto già presente all’interno di uni2b.com (visualizzan-
do quindi una apposita pagina del sito). Infine, l’applicazione deve inviare
all’utente un feedback dell’avvenuto inserimento dei dati.

2.3 Requisiti della libreria


La libreria deve quindi rispondere a tre esigenze:

• Importare i dati da una sorgente (ad esempio scaricare i dati xml da


units.it)

• Manipolare i dati (ad esempio mappare le proprietà nell’entità di


origine con le proprietà dell’entità di destinazione)

• Esportare i dati (ad esempio inviare i dati ad uni2b.com)

• permettere, ove richiesto, un’interazione con l’utente.

Figura 3: Formalizzazione del trasferimento di dati

4
Figura 4: Diagramma di flusso del trasferimento di dati

Viene quindi delineata una struttura a pipeline con un nodo iniziale


(la sorgente di dati), uno o più nodi intermedi (per l’elaborazione) e un
nodo finale (la destinazione). Questi tre nodi verranno chiamati processors
(processori) come verrà più avanti evindeziato nello schema uml.
La pipeline deve prevedere una modalità di esecuzione asincrona sia
per aumentare il grado di parallelismo in uno scenario di funzionamen-
to automatico (senza interazione con l’utente) sia per assecondare i tempi
dell’utente in uno scenario interattivo.
L’utilizzo di ogni singolo processore sarà diviso in cinque fasi (si veda la
fig. 5):

• Init - per l’inizializzazione interna del processore

• Prepare - per la preparazione alla lettura dei dati, ad esempio apertura


del file o della connessione con il database

• DataProcess - fase in cui avviene il processo dei dati

• Close - speculare del prepare, ad esempio per la chiusura del file o della
connessione al database.

• Reset - per reimpostare lo stato interno del processore al default in-


iziale.

Inoltre, allo scopo di rendere generica ed estensibile la libreria e di


raggiungere l’obiettivo di rapidità di sviluppo vengono delineati i seguenti
requisiti:

5
User Interface InputProcessorBase RowProcessor OutputProcessorBase

Init:=Init()

Init()
Fase 1
Init()

PrepareProcessor()

ValidateConfiguration()

Prepare()

PrepareProcessor() Fase 2

ValidateConfiguration()

Prepare()

PrepareProcessor()

ValidateConfiguration()

Prepare()

GetDataRow

CanRead

DataRow 1 ReadDataRow
Fase 3
ProcessRow(DataRow 1)

DataRow 1' ProcessRowCore(DataRow 1, DataRow 1')

WriteDataRow:=WriteDataRow(DataRow 1')

Close

Close()
Fase 4
Close()

Reset()

Reset()
Fase 5
Reset()

Figura 5: Sequenza delle chiamate in un trasferimento sincrono

6
• Un’interfaccia standard per ognuno dei precendenti nodi, con delle
classi base che implementino le funzioni più comuni di ogni nodo.

• Un tool che permetta in maniera rapida di configurare i nodi, e cioè


che permetta di scegliere una sorgente di dati, uno o più manipolatori
di dati ed infine una destinazione.

2.4 Struttura della libreria


2.4.1 Core
La parte centrale della libreria conterrà le classi base necessarie all’im-
plementazione dei processori (InputProcessorBase ,OutputProcessorBase e
RowProcessorBase), alla loro configurazione (InputProcessorCfg, Output-
ProcessorCfg e RowProcessorCfg), alla manipolazione e gestione dei dati
(classi DataRow, DataSchema e Column). InputProcessorBase, Output-
ProcessorBase e RowProcessorBase sono le classi astratte base che rapp-
resentano i processori e dichiarano tutti i metodi che verranno utilizzati
dalla libreria. In particolare i metodi Init, PrepareProcessor, GetDataRow,
Close, Reset e le loro versioni asincrone (definite seguendo l’event based
asyncronous pattern[14]). Definiscono inoltre i metodi LoadConfiguration
e ValidateConfiguration per il caricamento e la validazione della configu-
razione.
La classe DataRow consiste in un array unidimensionale che contiene i
valori di un singolo record. Ad ogni istanza della classe DataRow è associato
un DataSchema che contiene lo schema dei dati contenuti nel vettore. Ogni
processore deve infatti indicare lo schema dei dati che esso manda in output
(InputProcessor) o riceve in input (OutputProcessor); i RowProcessor hanno
invece due DataSchema, uno associato ai dati in entrata, l’altro associato ai
dati in uscita. Questo permette di verificare la compatibilità dei dati inviati
ad un determinato processore già in fase di configurazione, permettendo, a
runtime 1 , di operare direttamente sui vettori di dati (DataRow). L’oggetto
DataSchema è una collezione di oggetti Column. L’oggetto Column rapp-
resenta un campo, ed è costituito da un array associativo di coppie chiave
valore che identificano una proprietà del dato campo. L’oggetto Column
contiene tre proprietà fondamentali:

• Nome del campo

• Tipo di dati nel campo

• Indice (zero-based) della posizione del valore associato a quel campo


all’interno del vettore di valori contenuto negli oggetti DataRow
1
Si intende per runtime il periodo di esecuzione del processo di trasformazione dei dati,
mentre per design-time il periodo in cui viene eseguita la configurazione dei processori.

7
A queste proprietà fondamentali per la gestione del dato, si possono ag-
giungere altre proprietà. Questo per permettere di associare informazioni
al campo utili al processore cui appartiene il DataSchema e quindi l’oggetto
Column.
Ad esempio, il processore dei dati XmlInputProcessor, descritto in se-
guito, avrà associato un DataSchema le cui colonne, oltre alle proprietà
standard, avranno una proprietà xpath che identificherà la query xpath per
estrapolare dall’xml quel particolare campo e una proprietà ColumnFormat
che identificherà il formato dei dati per il campo2 .
La configurazione di ogni processore sarà gestita dalle classi InputPro-
cessorCfg, OutputProcessorCfg e RowProcessorCfg; queste classi dovran-
no essere serializzabili tramite l’oggetto XmlSerializer per poter salvare o
caricare la configurazione da uno stream di dati.

Figura 6: Diagramma UML parziale del progetto core

2
Per formato si intende la stringa rappresentante il formato che viene utilizzata ad
esempio nei metodi ToString(..) o Parse(..) degli oggetti che implementano IFormattable,
ad esempio DateTime o Int32

8
2.4.2 Configurazione
L’interfaccia utente dell’utility di configurazione sarà del tipo SDI3 con una
finestra flottante che conterrà la toolbar, da cui sarà possibile scegliere i
processori da inserire nella pipeline. Per gli inputProcessor e gli output-
Processor sarà necessario poter visualizzare lo schema dei dati, funzione che
sarà assolta da ViewSchema, una finestra modale. Per la configurazione dei
singoli processori, vi sarà un’apposita finestra modale (InputCfg, RowProc-
Cfg, OutputCfg). Queste finestre contengono, oltre alla logica per salvare o
caricare la configurazione da un file, la logica per caricare il giusto designer
per la configurazione. Il designer è uno UserControl[8] predisposto alla con-
figurazione del processore. Ricordando che ad ogni processore è associata
una classe che ne rappresenta la configurazione, a quest’ultima viene associ-
ato un designer applicando l’attributo ConfigurationDesignerAttribute. Per
gestire i vari processori disponibili sono stati previsti tre manager Input-
ProcessorManager, OutputProcessorManager, RowProcessorManager. La
lista dei processori disponibili verrà creata cercando all’interno degli assem-
bly caricati dei tipi di classe cui sia stato applicato uno degli attributi In-
putProcessorAttribute, OutputProcessorAttribute, RowProcessorAttribute.
Inoltre, verrà effettuata la scansione di una cartella plugins. Per aggiungere
un processore all’applicazione sarà quindi sufficiente copiare l’assembly che
lo contiene in questa directory. Della scansione della cartella e più in gen-
erale della gestione degli assembly caricati dinamicamente si occuperà la
classe statica AssemblyHelper.
Per facilitare ulteriormente la configurazione, è necessaria una struttura
per creare dei wizard o procedure guidate. Possiamo semplificare i requisiti
per creare una procedura guidata ai seguenti:

• Una serie consecutiva di passi, all’interno dei quali vengono fornite


all’utente una serie di scelte generalmente organizzate in una o due al
più per passo.

• La possibilità di navigare all’interno dei passi più o meno liberamente,


ad esempio tornare indietro, tornare all’inizio della procedura, an-
nullare la procedura.

• L’avanzamento nei passi può essere controllato dall’applicazione (ad


esempio non è possibile passare al passo successivo se l’utente non ha
effettuato almeno una scelta)

La classe WizardBase rappresenta la classe base per l’implementazione


di un passo della procedura guidata. La navigazione tra i passi avverrà
attraverso dei pulsanti preposti, e i passi successivo o precedente, saranno
indicati nelle proprietà WizardNextType e WizardPrevType. La possibilità
3
Single Document Interfacec[15]

9
Figura 7: Diagramma UML parziale del progetto di configurazione

di controllare il valore di queste proprietà anche a runtime e non solo in fase


di progettazione, garantirà massima libertà nello sviluppo della procedura
guidata. Infine, tutti i passi della procedura condivideranno un array asso-
ciativo per poter raccogliere le scelte effettuate dall’utente passo per passo
accessibile tramite la proprietà WizardItems. La classe WizardController
si occuperà di gestire la procedura guidata, mentre la classe WizardResult
conterrà tutte le informazione ad essa associate.

Figura 8: Diagramma UML parziale delle classi base per realizzare una
procedura guidata

10
2.4.3 Processore di input per file xml
Per il caricamento di dati a partire da file xml, verrà implementato un
processore di input nella classe XmlInputProcessor. La selezione dei dati
avverrà attraverso query xpath [19].
Nell’interfaccia di configurazione dovrà essere possibile la selezione e vi-
sualizzazione del file xml, dovrà avvenire il riconoscimento dei record e dei
campi contenuti all’interno del xml e quindi la deduzione dello schema. La
deduzione dello schema potrà avvenire a partire dal primo record riconosci-
uto all’interno del file xml oppure tramite una query xpath apposita. Lo
schema risultante dovrà comunque essere modificabile manualmente. La
classe XmlInputProcessor Deriverà quindi dalla classe InputProcessor<>.
La classe XmlInputProcessorCfg rappresenterà la configurazione del proces-
sore. InputDesigner sarà il designer ad essa associato, mentre SelectFile e Se-
lectSchema saranno i due passi della procedura guidata per la configurazione
del processore.

Figura 9: Diagramma UML partiale delle classi associate al processore di


input xml.

2.4.4 Processore per la modifica dello schema dei dati


Questo processore, implementato nella classe ColumnMappingProcessor, avrà
il compito di trasformare un DataRow con schema S1 (ricevuto in input) in
un nuovo oggetto DataRow con schema S2 (inviato in output). Definiremo
S1 come lo schema di input del processore e S2 come lo schema di output. In
fase di configurazione permetterà di mappare i campi dello schema in input

11
con quello in output, fissando lo schema S2. Lo schema in output è infatti
quello che il processore successivo nella pipeline si attende, e non può quindi
essere alterato. Ai valori in output sarà possibile associare un campo in in-
put o un valore costante (se il tipo di valore in output è rappresentabile con
una stringa inserita dall’utente). Inoltre una procedura automatica dedurrà
un mapping automatico qual’ora i campi avessero nomi simili.

Figura 10: Diagramma UML parziale delle classi associate a ColumnMap-


pingProcessor

2.4.5 Processori basati su Ado.Net


Poiché Ado.Net[2] rappresenta un’astrazione su differenti dbms4 , sono state
previste due classi il cui compito è quello di fungere da punto di partenza per
l’implementazione di processori basati appunto sul framework di microsoft.
4
DataBase Management Systems

12
L’approccio astratto da Ado.Net si compone di alcuni oggetti fondamen-
tali:

• DbConnection

• DbCommand

• DbDataReader

Ogni specifico provider per Ado.Net implementa queste classi (ad esem-
pio per Ms Sql Server abbiamo SqlConnection, SqlCommand e SqlDataRead-
er), ma, salvo casi particolari, possono essere utilizzati attraverso l’interfac-
cia standard messa a disposizione dalle classi base elencate precedentemente.
Le classi DbInputProcessor e DbOutputProcessor sono classi generiche,
che ereditano da InputProcessor<> e OutputProcessor<> e non specificano
il tipo di configurazione, che sarà lasciato quindi a chi implementerà le classi
per lo specifico provider.
Nelle specifiche implementazioni si dovrà solamente restituire una nuova
istanza di una classe DbConnection e DbCommand e mappare il tipo di dati
dello specifico provider con il tipo di dati del framework.net(tramite l’over-
ride dei metodi CreateCommand, CreateConnection e GetFrameworkType).
MsSqlServerInputProcessor e MsSqlServerOutputProcessor saranno l’im-
plementazione delle classi DbInputProcessor e DbOutputProcessor per il
provider Ado.Net per Microsoft Sql Server. MsSqlServerInputProcessorCfg
e MsSqlServerOutputProcessorCfg saranno le rispettive classi di configu-
razione, e conterranno entrambe le stesse informazioni:

• ConnectionString - la stringa di connessione al database.

• SqlQuery - la stringa contentente il comando sql (o la stored procedure)


da eseguire

• Il tipo di comando 5

Saranno inoltre necessarie due procedure guidate per la configurazione dei


due processori.

2.4.6 Processori basati su http


Il framework .Net mette a disposizione diverse api per l’interazione con il
web. Le principali sono WebClient, che permette un approccio più astratto,
e HttpWebRequest, che permette di controllare in ogni aspetto le richieste
e le risposte http. I processori di cui si parlerà in seguito interagiranno con
risorse sul web attraverso il download di dati con richieste get, o l’invio di
dati attraverso il post o get di form html [20].
5
Si veda l’enumerazione System.Data.CommandType[9]

13
Figura 11: Diagramma UML parziale dei processori basati su Ado.Net

Nell’interazione con sorgenti di dati web protette gioca un ruolo impor-


tante il tipo di autenticazione. Infatti la maggioranza dei siti web utiliz-
za un meccanismo di autenticazione basato su forms (non-standardizzato),
che consiste principalmente nell’inviare le credenziali dell’utente attraver-
so una form html ed autenticare quindi la sessione, che viene mantenuta
tramite un cookie o altro meccanismo. A differenza delle autenticazioni
http, dove il processo di autenticazione si basa su headers e response code
http appositi, la forms authentication non si adatta facilmente all’utilizzo
machine-to-machine. Uno dei principali ostacoli è l’assenza in questo tipo di
autenticazione di un metodo di feedback preciso: quando l’utente inserisce
un url all’interno di un browser, se non è autorizzato viene rediretto ad una
pagina di login dove è presente la form per l’inserimento delle credenziali.
Uno user-agent, potrebbe dedurre empiricamente che la richiesta non è au-
torizzata dalla risposta del server, che avrà un codice di stato 3026 e dall’url
6
l’rfc 2616[23] specifica che tutti i codici di stato 3xx richiedono un redirect. Seppur
il corretto redirect in questo caso sia il codice 302, questa non è condizione necessaria,

14
specificato nell’header http “Location” che conterrà l’indirizzo della pagina
di login con la form html. Tuttavia anche l’url potrebbe variare da richiesta
a richiesta (molti siti appendono infatti all’indirizzo della pagina di login una
query string con l’url o il path della risorsa a cui l’utente voleva originari-
amente accedere, per effettuare poi un altro redirect ad essa). Un’ulteriore
complicazione nasce dal fatto che alcune architetture utilizzano più di un
cookie: Asp.Net per esempio può essere configurato per utilizzare un cookie
per il mantenimento della sessione ed un cookie per l’autenticazione. Infine,
un’applicazione web potrebbe utilizzare tecniche diverse dall’uso dei cookie
per mantenere la sessione.
La casistica fin qui descritta seppur delineando lo standard de-facto,
non si può considerare per nulla esaustiva. Per interagire quindi con risorse
protette da autenticazione form l’approccio dovrà essere diverso caso per
caso. Nel caso in cui si richieda la massima automazione, la sessione sia
mantenuta con uno o più cookie, e il form html contenga solo i campi per
le credenziali di accesso7 , una possibile soluzione potrebbe essere, una volta
che si conoscano:

1. Url assoluto della pagina con la form di login.


2. Url assoluto dell’action della form di login.
3. Url della risorsa richiesta.
4. Nome del(dei) cookie(s) per identificare la sessione.
5. Credenziali di accesso

quella di inviare tramite un post http all’url (2) della pagina di login le
credenziali (5) dell’utente, constatare l’avvenuto login dall’impostazione del
cookie di sessione e/o autenticazione e dal redirect ad una pagina diversa
da quella di login 8 . Quindi includere nelle richieste successive alla risorsa
protetta (3) i cookie ottenuti (attraverso l’header http Set-Cookie) in fase
di autenticazione. Un ulteriore controllo dovrà essere fatto poiché dopo
un certo intervallo di tempo il cookie o la sessione potrebbero scadere e si
dovrebbe quindi procedere nuovamente all’autenticazione. Questa situazione
potrebbe essere identificata qualora la risposta alla richiesta della risorsa
protetta fosse un redirect alla pagina di login.
Nel caso invece in cui si abbia la possibilità(o la necessità) di interagire
con l’utente, si può ipotizzare un approccio più flessibile, una volta che si
conoscano:
introducendo quindi un fattore di incertezza
7
É infatti possibile, proprio allo scopo di impedire l’invio di dati da parte di applicazioni
che nel form venga aggiunto un cosiddetto “captcha o completely automated public Turing
test to tell computers and humans apart”[24]
8
per diverso si intende che abbia scheme, authority e path diverso. Non viene
considerato invece diverso se differisce solo per la query string

15
1. Url assoluto della pagina di login

2. Url della risorsa richiesta


che consiste nel visualizzare la pagina (1) di login integrando nell’appli-
cazione un browser [10]; a questo punto l’utente potrebbe effettuare il login
all’interno del browser. Una volta autenticato, l’utente conferma l’avvenu-
ta autenticazione attraverso un pulsante sull’interfaccia grafica a fianco del
browser. L’applicazione quindi può accedere ai cookie [11] ed inserirli nella
richiesta, questa volta eseguita in automatico, alla risorsa protetta (2).
Queste considerazioni valgono in maniera del tutto analoga sia qual’ora
si voglia ottenere una risorsa via get http(per esempio un file xml tramite
un XmlInputProcessor) sia qualora si vogliano inviare informazioni via post
o get http.
Nel caso di invio di dati, una volta avvenuta l’autenticazione, si procede
come per la richiesta get, all’inserimento dei cookies in ogni richiesta (get o
post).
Nel caso di invio dei dati tuttavia, qualora fosse richiesta un’interazione
dell’utente sui dati si potrebbe considerare un altro approccio. Invece di
effettuare automaticamente il post dei dati all’url specificato, è possibile vi-
sualizzare nel browser la pagina contente la form html e “compilarla” utiliz-
zando le proprietà della classe HtmlDocument[16]. In questo modo l’utente
potrebbe visualizzare e/o modificare i dati prima di inviarli utilizzando il
pulsante di submit specificato nella pagina.
Volendo ora integrare questi approcci con l’architettura della libreria,
possiamo cosı̀ delineare due processori, HttpXmlInputProcessor per la let-
tura di dati xml dal web e HttpPostOutputProcessor per l’invio di dati
tramite il post o get di una form html.
HttpXmlInputProcessor dovrà quindi, come detto, in fase di prepare ac-
cedere alla risorsa effettuando l’autenticazione; qualora si tratti di forms au-
thentication, sarà necessario specificare un controllo WebBrowser tramite cui
possa effettuare la procedura sopra descritta; nel caso di autenticazione http,
basic o digest, sarà necessario semplicemente richiedere le credenziali all’u-
tente e inserirle nella richiesta (come vedremo in fase di implementazione, il
framework .net supporta questo tipo di autenticazioni attraverso l’uso della
classe NetworkCredential sia nelle richieste effettuate tramite WebClient sia
tramite HttpWebRequest). Le opzioni di configurazione necessarie saranno
quindi le stesse per l’XmlInputProcessor, con l’aggiunta di:
• metodo di autenticazione

• credenziali di accesso (per i metodi di autenticazione automatizzati,


con opzione per “chiedi sempre”, per evitare di salvare le credenziali
nel file di configurazione)

• url assoluto della pagina di login

16
• La proprietà Filename specificata per l’XmlInputProcessor dovrà es-
sere un url assoluto alla risorsa richiesta.

Il wizard di configurazione dovrà comprendere un ulteriore passo dedicato


alla raccolta appunto dei dati sull’autenticazione. Una volta eseguito il
download dei dati xml sarà possibile iniziare la lettura e il processo dei
dati come visto nel caso del processore XmlInputProcessor.
HttpPostOutputProcessor è il processore preposto all’invio di dati tramite
post o get http, utilizzerà lo stesso meccanismo di HttpXmlInputProcessor
per il login, aprendo cioè un browser in fase di prepare. Un ulteriore proble-
ma da affrontare durante l’invio di dati è quello del feedback per l’avvenuto
invio con successo. Infatti, generalmente, anche se l’invio non è avvenuto
con successo (ad esempio dati incompleti o non compatibili con i vincoli
imposti dal server) non avremo un feedback a livello http, per cui l’invio dei
dati è perfettamente trasparente, ma si dovrà capire dal corpo della rispos-
ta il risultato dell’operazione. A questo scopo è stata prevista un’opzione
“modalità di feedback” che identifica in che modo si valuterà la risposta; le
alternative possibili sono tre:

• None, indica nessun feedback, e l’invio viene assunto come sempre


riuscito

• HttpCode, dove si verifica che il codice di stato http sia uguale al


valore atteso (non è infatti garantito che il codice di stato 200[21]
indichi l’avvenuto inserimento, un’applicazione web potrebbe infatti
effettuare un redirect alla pagina di dettaglio delle informazioni appena
inserite)

• RegularExpression, dove il contenuto della risposta viene verificato


attraverso la valutazione di un’espressione regolare[17] specificata nella
configurazione.

Un ulteriore opzione per verificare lo stato dell’operazione di inserimento è


quello di visualizzare, all’interno di un browser, il contenuto della risposta.
Le informazioni necessarie per effettuare l’invio dei dati tramite get o
post sono quindi:

• Url a cui effettuare la richiesta

• Metodo (get o post)

• Modalità di autenticazione

• Credenziali per l’autenticazione http

• Pagina di login per l’autenticazione forms

• Encoding della richiesta (default UTF8)

17
• Visualizza la risposta per ogni riga

• Modalità di feedback

• Espressione regolare per controllo del feedback.

• Codice di stato per controllo del feedback

• Lo shema dei dati da inviare costruito in base ai campi del modulo


html, ossia gli elementi input e textarea.

Figura 12: Diagramma UML parziale dei processori basati su http

Nel designer della configurazione quindi sarà necessario un wizard costi-


tuito da tre passi, il primo per raccogliere le informazioni sull’autenticazione,
il secondo per la scelta della form html da utilizzare per l’invio dei dati ed
infine il terzo passo per la deduzione e modifica dello schema dei dati. In

18
particolare, nel secondo passo, la scelta della form avverrà visualizzando
in un browser la pagina contente la form html, quindi attraverso l’ogget-
to HTMLDocument messo a disposizione dal Framework.Net si creerà una
lista di tutte le form html presenti nella pagina e si lascerà effettuare la
scelta all’utente. Una volta selezionata la form, nel passo successivo verrà
automaticamente identificato l’url (1) a cui effettuare le richieste per l’in-
vio dei dati ed il metodo attraverso gli attributi “action” e “method” della
form. Lo schema dei dati invece verrà creato automaticamente a partire
appunto dalla ricerca di tag “input” o “textarea” all’interno della form. Da
notare che lo schema generato include i campi rappresentati dal tag input
con type=“submit”; nel caso di più pulsanti di submit[22], nel post o get
generato dal submit della form che li contiene, apparirà solo il campo del
pulsante che genera l’invio dei dati9 . Poiché questa informazione non può
essere dedotta in modo automatico, sarà compito dell’utente eventualmente
rimuovere i campi associati a tag input con type=submit non voluti. Per
quanto riguarda gli elementi di tipo “radio” o “checkbox” verrà inserito au-
tomaticamente un unico campo per ogni gruppo di elementi con lo stesso
“name”.

2.5 Struttura delle soluzioni per il caso specifico


Nel caso specifico dell’esportazione dei dati tra units.it e uni2b.com, sono
state realizzate due interfacce utente.

2.5.1 La soluzione desktop application


L’applicazione desktop si basa sull’utilizzo del processore di input HttpXm-
lInputProcessor e dell’approccio “Browser filler” per quanto riguarda l’out-
put. Per ogni entità da esportare (progetti e strumenti) sarannò disponibili
le configurazioni dei processori di input, columnmapping e output. Queste
configurazioni saranno incluse come risorse all’interno dell’applicazione.
L’interfaccia grafica dovrà essere divisa in due parti, una in cui l’utente
avrà la possibilità di selezionare l’entità su cui intende lavorare, l’altra dove
sarà visualizzato il browser associato al processore di output. Il browser
dovrà visualizzare la pagina contenente la lista di dati giàinseriti all’interno
di uni2b.com qual’ora non sia in corso un invio di dati. Una volta selezionata
l’entità, verrà caricata la corretta configurazione dei processori, e, dopo la
fase di init e prepare (in cui verrà effettuata anche l’autenticazione ai siti
web), verranno caricati e visualizzati tutti i dati disponibili dal processore
di input. Secondo il pattern master-details qual’ora l’utente selezioni un
record dalla lista, verrà aperta una finestra di dettaglio da cui sarà possibile
9
per un esemptio di questo comportamento si veda ad esempio l’home page di google.it,
dove sono presenti due pulsanti di submit

19
effettuare l’invio, passando quindi l’oggetto DataRow associato a quel record
al ColumnMappingProcessor e quindi al processore di output.

2.5.2 La soluzione web-based e Silverlight


La soluzione web based è stata realizzata utilizzando la tecnologia Silverlight.
Ciò ha permesso di integrare la pipeline dei processori all’interno di una pag-
ina web (sync.htm), da inserire all’interno del sito units.it. Questo approccio
ha due principali benefici. Innanzitutto permette una più facile distribuzione
dell’applicazione; in secondo luogo permette di superare quasi tutti i prob-
lemi legati all’autenticazione nei processori basati su http. Silverlight mette
a disposizione un subset delle funzionalità del framework, tra cui le classi
WebClient e HttpWebRequest. Tuttavia, per una serie di motivi legati a
compatibilità, performance e sicurezza[27], non permette la gestione diretta
di tutti gli header http, proibendo in particolare quelli legati all’autenti-
cazione (header http “cookie”, “www-authenticate” etc.). Tutta la logica di
autenticazione viene quindi gestita dal browser, e l’applicazione silverlight
( e tutte le richieste http da essa generate) condividono la sessione con la
pagina web su cui risiede l’applicazione stessa. Nel caso specifico, poiché
la pagina con l’applicazione silverlight e la risorsa da cui viene effettuato il
download dei dati xml risiedono nello stesso dominio e nella stessa appli-
cazione web, condividono lo stesso cookie di sessione / autenticazione e non
sarà quindi necessario che il processore di input effettui l’autenticazione.
Più complessa è invece la gestione dell’autenticazione per il processore
di output, che dovrà effetturare delle richieste cross-site al sito uni2b.com.
Per motivi di sicurezza infatti questo non è sempre possibile. Per permet-
tere l’invio di dati da un altro dominio, il sito uni2b.com dovrà autorizzare
silverlight ad eseguire l’operazione[13]. Una volta abilitato l’invio dei dati
cross-site, rimane in teoria comunque il problema di effettuare queste richi-
este in maniera autenticata. Tuttavia, se l’autenticazione o la gestione della
sessione è basata sui cookies, si è notato che la maggioranza dei browser
inviano i cookies in ogni richiesta fatta a quel dominio[26], secondo ovvi-
amente le opportune regole. Nella pratica questo si traduce nel seguente:
se apriamo in un browser un sito S1 che utilizza l’autenticazione form, ci
autentichiamo e riceviamo il cookie C(S1), poi apriamo in un’altra finestra
(o tab) dello stesso browser una pagina da un’altro dominio S2. Se questa
pagina S2 esegue una richiesta get o post cross-site al dominio del sito S1
in cui ci siamo autenticati, il browser inserirà sempre in questa richiesta il
cookie di sessione/autenticazione C(S1)10 .
Nel caso in esame quindi sarà sufficiente presentare in un iframe all’inter-
no della pagina con l’applicazione silverlight dove venga aperta la pagina di
login su uni2b.com ed invitare l’utente ad eseguire l’autenticazione prima di
10
permette quindi alla pagina S2 di inviare dati come se fosse autenticata su S1. Si veda
Cross Site Request Forgery[25]

20
effettuare l’invio dei dati. In tutte le seguenti chiamate ad uni2b.com verrà
inviato lo stesso cookie (ricevuto da uni2b.com), quindi tutte le chiamate
risulteranno appartenenti alla stessa sessione e quindi autenticate.
Questo approccio viola però il vincolo progettuale di non alterare il sito
uni2b.com (sebbene l’aggiunta di un file di autorizzazione all’invio dei dati
cross-domain sia facilmente implementabile e più che legittimo). Si è quin-
di scelto di raffinare ulteriormente la soluzione evitando di inviare i dati
tramite WebClient o HttpWebRequest, ma, sfruttando l’interoperabilità tra
silverlight e javascript, eseguire il post dei dati tramite un iframe inserito
nella pagina. Il processore di output che implementa questo approccio, Http-
SubmitterOutputProcessor, accedendo tramite l’HTML Bridge[12] inietterà
all’interno dell’iframe del codice html che rappresenta la form da inviare(Se
venisse impostato l’attributo src dell’iframe all’indirizzo contente la form
html per l’invio dei dati, silverlight non potrebbe avere accesso al DOM
dell’iframe stesso, in quanto risiederebbe su un dominio differente). Quin-
di, accedendo sempre tramite l’HTML Bridge, si ricercheranno all’interno
del markup iniettato gli elementi input della form html, e si imposterà il
loro valore in accordo con i dati in ingresso al processore. Il submit della
form può quindi essere effettuato da silverlight richiamando una funzione
javascript sulla pagina che contiene l’applicazione che richiami a sua volta
il metodo submit sulla form html. Tuttavia, per lasciare la possibilità di
revisione e aggiunta dei dati, si lascerà all’utente il compito di effettuare
l’invio della form tramite un apposito pulsante di submit inserito nell’iframe
insieme al markup della form stessa.

21
Figura 13: Diagramma UML parziale per la soluzione web-based

22
3 Implementazione
L’intero progetto è stato realizzato all’interno di una solution di Visual
Studio e suddiviso in diversi progetti (nome e tipo):

• Core - Class library

• ConfigUI - Windows Forms application

• Xml - Class library

• Http - Class library

• MsSqlServer - Class library

• RowProcessors - Class library

• SilverlightCore - Silverlight class library

• SilverlightUI - Silverlight application

• SilverlightUI.Web - Web application utilizzata per il debug dell’appli-


cazione silverlight

• WpfUI - Windows Presentation Foundation application

3.1 Progetto Core


Nella classe InputProcessorBase sono dichiarati i principali metodi utiliz-
zati all’interno della libreria. In particolare nel listato 1 si può notare il
pattern più volte utilizzato per l’implementazione dei metodi asincroni. Il
metodo che effettivamente esegue le funzionalità di preparazione del proces-
sore è il metodo astratto Prepare che dovrà essere implementato negli eredi.
Tuttavia, Prepare è un membro protected, cosı̀ che non possa essere richiam-
ato direttamente ma solo tramite PrepareProcessor, che esegue prima un
controllo per validare la configurazione. Per quanto riguarda la versione
asincrona, si è implementato l’Event-Based Asyncronous Pattern come già
accennato. In particolare, PrepareProcessorAsync (pubblico) effettuerà il
controllo sulla configurazione e quindi chiamerà il metodo protetto virtuale
CorePrepProcAsync. Il metodo è stato dichiarato come virtuale per far si
che nell’implementazione di base (righe 18-22 listato 1) venga invocato su un
worker-thread il metodo Prepare, implementato per la versione sincrona. In
questo modo, salvo casi particolari, l’implementazione sincrona può essere
utilizzata in maniera asincrona senza dover implementare nessun altro meto-
do. Per i casi particolari si potrà sempre effettuare l’override del metodo
CorePrepProcAsync.

23
1 protected abstract void Prepare () ;
2
3 public void PrepareProcessor ()
4 {
5 if ( V a l i d a t e C o n f i g u r a t i o n () ) Prepare () ;
6 else OnProcessorError () ;
7 }
8
9 public void P r e p a r e P r o c e s s o r A s y n c ()
10 {
11 if ( V a l i d a t e C o n f i g u r a t i o n () )
12 Core PrepPr ocAsyn c () ;
13 else
14 OnProcessorError () ;
15 }
16 protected virtual void CoreP repPro cAsync ()
17 {
18 ThreadPool . Queue UserWo rkItem ( new WaitCallback (( o )
=>
19 {
20 Prepare () ;
21 O n P r e p a r e P r o c e s s o r A s y n c C o m p l e t e d () ;
22 }) ) ;
23 }
24 protected void O n P r e p a r e P r o c e s s o r A s y n c C o m p l e t e d ()
25 {
26 if ( P r e p a r e P r o c e s s o r A s y n c C o m p l e t e d != null )
27 P r e p a r e P r o c e s s o r A s y n c C o m p l e t e d ( this ,
EventArgs . Empty ) ;
28 }
29 public event EventHandler
PrepareProcessorAsyncCompleted ;

Listato 1: InputProcessorBase

Il pattern fin qui descritto verrà applicato anche al metodo GetDataRow.


Per i metodi Init, Close e Reset infine, non è stata prevista una versione as-
incrona e nessuna implementazione virtuale, sono infatti dichiarati come
abstract. Le classi OutputProcessorBase e RowProcessorBase sono imple-
mentate in maniera analoga.
InputProcessor<CfgType> funge da classe base per l’implementazione
dei processori di input. Il parametro generico CfgType indica il tipo della
classe che definisce la configurazione per l’input processor. Come si può
notare a riga 8 del listato 2 all’istanziazione del processore viene automati-
camente creata un’istanza della classe di configurazione. La proprietà Con-

24
figuration infine altro non è che un wrapper sulla proprietà InternalCon-
figuration.ConfigurationInstance (di cui effettua il cast a CfgType, righe
14-15).

1 public abstract class InputProcessor < CfgType >


2 : In pu tP ro ce ss or Ba se where CfgType : class
3 {
4 public InputProcessor ()
5 {
6 Inp utProc essorC fg cfg = new Inp utProc essorC fg () ;
7 cfg . TypeName =
this . GetType () . A s s e m b l y Q u a l i f i e d N a m e ;
8 cfg . C o n f i g u r a t i o n I n s t a n c e =
Activator . CreateInstance < CfgType >() ;
9 Loa dConfi gurati on ( cfg ) ;
10 }
11
12 public CfgType Configuration
13 {
14 get { return
InternalConfiguration . ConfigurationInstance
as CfgType ; }
15 set { I n t e r n a l C o n f i g u r a t i o n . C o n f i g u r a t i o n I n s t a n c e
= value ; }
16 }
17 }

Listato 2: InputProcessor<CfgType>

La classe InputProcessorCfg viene utilizzata come helper per la serializ-


zazione della configurazione del processore. Contiene tutte le informazioni
necessarie a istanziare e configurare il processore

1 private string _type_name = null ;


2
3 [ XmlAttribute ]
4 public string TypeName
5 {
6 get { return _type_name ; }
7 set { _type_name = value ; }
8 }
9 private Type _ipt_type = null ;
10
11 [ XmlIgnore ]

25
12 public Type Inp ut Pr oc es so rT yp e
13 {
14 get
15 {
16 if ( _ipt_type == null &&
! string . IsNullOrEmpty ( _type_name ) )
17 { _ipt_type = Type . GetType ( _type_name ) ; }
18 return _ipt_type ;
19 }
20 }
21 public I np ut Pr oc es so rB as e CreateProcessor ()
22 {
23 I npu tP ro ce ss or Ba se ipb =
Activator . CreateInstance ( this . In pu tP ro ce ss or Ty pe )
as I np ut Pr oc es so rB as e ;
24
25 if ( ipb == null ) return null ;
26 ipb . L oadCon figura tion ( this ) ;
27 return ipb ;
28 }

Listato 3: InputProcessorCfg

Il metodo CreateProcessor sfrutta la proprietà InputProcessorType per


conoscere il tipo del processore da istanziare (Listato 3). Poiché la classe
Type non è serializzabile utilizzando XmlSerializer la proprietà InputPro-
cessorType cercherà di ottenere il giusto tipo sfruttando la proprietà Type-
Name, che conterrà una stringa con l’assembly qualified name del tipo.
Per quanto riguarda la serializzazione, la class InputProcessorCfg imple-
menta l’interfaccia IXmlSerializable che consente di controllare pienamente
come questo oggetto sarà serializzato. Questo è necessario al fine di se-
rializzare la proprietà ConfigurationInstance, che è definita come object,
ma il cui tipo è definito come il parametro generico della classe base del
tipo dell’input processor. Questo tipo ci viene restituito dalla proprietà
InputProcessorConfigurationType.

1 [ XmlIgnore ]
2 internal Type I n p u t P r o c e s s o r C o n f i g u r a t i o n T y p e
3 { get {
4 return I np ut Pr oc es so rT yp e . BaseType .
5 G e tG e n er i c Ar g u me n t s () [0];
6 }}

Listato 4: InputProcessorCfg

26
In fase di serializzazione quindi, questa informazione viene salvata in un
attributo del tag di root del’xml (riga 26 del listato 5) generato, per poi es-
sere utilizzato in fase di deserializzazione (righe 3, 6, 14 del listato 5). Infine,
la serializzazione/deserializzazione dell’istanza della classe di configurazione
avviene utilizzando un XmlSerializer, e passando l’xml writer / reader cor-
rente e il tipo restituito da InputProcessorConfigurationType (righe 14, 30,
del listato 5).

1 public void ReadXml ( XmlReader reader )


2 {
3 _type_name = reader . GetAttribute ( " TypeName " ) ;
4 _name = reader . GetAttribute ( " Name " ) ;
5
6 _ipt_type = Type . GetType ( _type_name ) ;
7
8 XmlNodeType xnt = reader . MoveToContent () ;
9 if ( xnt == XmlNodeType . Element )
10 {
11 reader . ReadStartElement () ;
12 reader . ReadStartElement () ;
13
14 XmlSerializer ser = new XmlSerializer (
this . I n p u t P r o c e s s o r C o n f i g u r a t i o n T y p e ) ;
15 _configuiration_class =
ser . Deserialize ( reader . ReadSubtree () ) ;
16 if ( reader . IsEmptyElement )
17 reader . Read () ;
18 else reader . ReadEndElement () ;
19 reader . ReadEndElement () ;
20 }
21 reader . ReadEndElement () ;
22 }
23
24 public void WriteXml ( XmlWriter writer )
25 {
26 writer . W r i t e A t t r i b u t e S t r i n g ( " TypeName " ,
_type_name ) ;
27 writer . W r i t e A t t r i b u t e S t r i n g ( " Name " , _name ) ;
28 writer . Wr iteSta rtElem ent ( " InputProcessor " ) ;
29
30 XmlSerializer ser = new XmlSerializer (
this . I n p u t P r o c e s s o r C o n f i g u r a t i o n T y p e ) ;
31
32 ser . Serialize ( writer , _ c o n f i g u i r a t i o n _ c l a s s ) ;
33
34 writer . WriteEndElement () ;
35 }

27

Listato 5: InputProcessorCfg

L’implementazione delle classi OutputProcessorCfg e RowProcessorCfg


è del tutto analoga.
La classe DataSchema è stata implementata derivando una lista osserv-
abile per poter generare un evento qualora lo schema fosse modificato. Tut-
tavia, poiché non esiste una classe che implementi una lista osservabile co-
mune a Silverlight e al Framework 3.5, si è dovuto ricorrere alla compilazione
condizionale.

1 # if SILVERLIGHT
2 public class DataSchema :
ObservableCollection < Column >
3 {
4 public DataSchema () {
5 this . Col lecti onChan ged += new
System . Collections . Specialized .
6 N o t i f y C o l l e c t i o n C h a n g e d E v e n t H a n d l e r ( ... ) ;
7 }
8
9 void D a t a S c h e m a _ C o l l e c t i o n C h a n g e d ( object
sender , System . Collections . Specialized .
10 NotifyCollectionChangedEventArgs e)
11 {
12 if ( this . SchemaChanged != null )
13 SchemaChanged ( this , EventArgs . Empty ) ;
14 }
15 # else
16 public class DataSchema : BindingList < Column >
17 {
18
19 public DataSchema () {
20 this . ListChanged += new
L i s t C h a n g e d E v e n t H a n d l e r ( DataSchema ...
21 }
22
23 void D a t a S c h e m a _ L i s t C h a n g e d ( object sender ,
ListChangedEventArgs e)
24 {
25 if ( this . SchemaChanged != null )
26 SchemaChanged ( this , EventArgs . Empty ) ;
27 }
28 # endif

28

Listato 6: DataSchema

Le motivazioni e la descrizione di questo approccio verranno presentate


più avanti nella sezione dedicata al progetto SilverlightCore. In maniera
analoga, per la classe column:

1 # if SILVERLIGHT
2 public class Column : IXmlSerializable {
3 # else
4 public class Column :
IXmlSerializable , I C u s t o m T y p e D e s c r i p t o r {
5 # endif

Listato 7: Column

Come si può notare nell’implementazione della classe column per il frame-


work 3.5 è stata aggiunta un’interfaccia, ICustomTypeDescriptor[5]. Questa
interfaccia è utilizzata da alcune classi del framework, come PropertyGrid[1]
o DataGrid[3] per ottenere una descrizione del tipo, allo scopo di visualiz-
zare le proprietà dell’oggetto o gestire il data binding. Richiamando quanto
detto in fase di analisi, Column è costituito da un array associativo (riga
1, listato 8) che contiene tutte le informazioni associate alla colonna dello
schema. Sebbene siano state definite delle proprietà wrapper sull’array asso-
ciativo (righe 4-12, listato 8) a cui si può effettuare il binding normalmente,
agli occhi della classe GridView rimarrebbero invisibili tutte le informazioni
non mappate da proprietà, ma visibili attraverso l’indicizzatore.

1 Dictionary < string , object > _column_info = new


Dictionary < string , object >() ;
2
3 [ XmlIgnore ]
4 public string Name
5 {
6 get
7 {
8 if (! _column_info . ContainsKey ( " ColumnName " ) )
return null ;
9 return _column_info [ " ColumnName " ]. ToString () ;
10 }
11 set { _column_info [ " ColumnName " ] = value ; }

29
12 }
13 public object this [ string pColumnName ]
14 {
15 get { return _column_info [ pColumnName ]; }
16 set { _column_info [ pColumnName ] = value ; }
17 }

Listato 8: Column

Per rendere possibile il binding all’indicizzatore è stata implementata


appunto l’interfaccia ICustomTypeDescriptor, in particolare il metodo Get-
Properties, che restituisce una lista di PropertyDescriptor[4]; dopo aver ag-
giunto alla lista le proprietà standard contenute dall’oggetto column (righe
4-7, listato 9), viene aggiunta un’istanza della classe IteratorPropertyDe-
scriptor per ogni proprietà non standard (righe 9-13, listato 9).

1 public P r o p e r t y D e s c r i p t o r C o l l e c t i o n
GetProperties ( Attribute [] attributes )
2 {
3 ...
4 foreach ( Pr op ert yD es cr ip to r prop in
TypeDescriptor . GetProperties ( this , attributes ,
true ) )
5 {
6 props . Add ( prop ) ;
7 }
8 PropertyInfo iterator =
typeof ( Column ) . GetProperty ( " Item " ) ;
9 foreach ( string s in
this . _schema . G e t S c h e m a E x t r a F i e l d s () )
10 {
11 I t e r a t o r P r o p e r t y D e s c r i p t o r pd = new
I t e r a t o r P r o p e r t y D e s c r i p t o r ( iterator , s ) ;
12 props . Add ( pd ) ;
13 }
14 ...
15 return props ;
16 }

Listato 9: Column

La classe IteratorPropertyDescriptor erediterà dalla classe PropertyDe-


scriptor ed in particolare effettuerà l’override dei metodi GetValue e SetVal-
ue, andando a ottenere o impostare il valore specificando l’indice all’indiciz-
zatore.

30
1 public class I t e r a t o r P r o p e r t y D e s c r i p t o r :
P rop er ty De sc ri pt or
2 {
3 public override object GetValue ( object component )
4 {
5 return _indexer . GetValue ( component , new object []
{ _index }) ;
6 }
7 public override void SetValue ( object component ,
object value )
8 {
9 _indexer . SetValue ( component , value , new object []
{ _index }) ;
10 ...
11 }

Listato 10: IteratorPropertyDescriptor

Ritornando all’implementazione della classe Column, va messo in eviden-


za come la serializzazione tramite XmlSerializer di un oggetto che implemen-
ta l’interfaccia IDictionary<, > non sia possibile. E’ stato quindi necessario
implementare l’interfaccia IXmlSerializable e gestire la serializzazione delle
informazioni contenute nell’array associativo come una lista di coppie chiave
valore (listato 11), utilizzando allo scopo la classe helper Pair.

1 ...
2 public void ReadXml ( System . Xml . XmlReader reader )
3 {
4 reader . ReadStartElement () ;
5 XmlSerializer xs = new
XmlSerializer ( typeof ( List < Pair >) ) ;
6
7 List < Pair > rows = xs . Deserialize ( reader ) as
List < Pair >;
8
9 foreach ( Pair kvp in rows )
10 _column_info . Add ( kvp . Key , kvp . Value ) ;
11
12 reader . ReadEndElement () ;
13 }
14
15 public void WriteXml ( System . Xml . XmlWriter writer )
16 {

31
17 XmlSerializer xs = new
XmlSerializer ( typeof ( List < Pair >) ) ;
18
19 List < Pair > pairs = new List < Pair >() ;
20 foreach ( string k in _column_info . Keys )
21 pairs . Add ( new Pair () { Key = k , Value =
_column_info [ k ] }) ;
22
23 xs . Serialize ( writer , pairs ) ;
24 }
25 public class Pair
26 {
27 public string Key { get ; set ; }
28 public object Value { get {...} set {...} }
29 }

Listato 11: Column

L’implementazine della classe DataRow infine contiene un array di object


che rappresentano i valori del record, accessibili attraverso un indicizzatore.
E’ stato inoltre implementato il metodo SetValue che ha la funzione di im-
postare il valore in una determinata posizione dell’array effettuando però
una conversione di tipo (listato 12).

1 public void SetValue ( object pValue , Column pColumn )


2 {
3 object c_type = null ;
4 try
5 {
6 c_type = Convert . ChangeType ( pValue ,
pColumn . TypedType , pColumn . FormatProvider ) ;
7 }
8 catch
9 {
10 c_type = null ;
11 }
12 _row_values [ pColumn . Ordinal ] = c_type ;
13 }

Listato 12: DataRow

Questo fornisce una funzionalità standard per i processori per impostare


il valore di un campo della DataRow al corretto tipo specificato all’interno
dello schema dei dati. Inoltre, l’uso di informazioni contenute nello schema

32
come ColumnFormat o ColumnFormatCulture permette di gestire la con-
versione di tipi di dati che dipendono dalla cultura o il cui formato non
è standard (ad esempio numeri con un separatore dei decimali diverso da
quello previsto dalla cultura impostata nell’applicazione .net)

3.2 Progetto ConfigUI


In questo progetto sono state implementate tutte le funzionalità atte a con-
figurare la libreria. Le classi InputProcessorManager, OutputProcessorMan-
ager e RowProcessorManager gestiscono la lista di processori che l’utente
ha a disposizione. La loro implementazione è molto simile e viene quindi
riportato di seguito solo un estratto dell’implementazione dell’InputProces-
sorManager. Come si può vedere viene creata un’unica istanza del manager
accessibile tramite la proprietà statica Instance; il manager mantiene inter-
namente un array associativo contenente il nome del processore e un oggetto
InputProcessorDescriptor, che contiene le informazioni ottenute durante la
ricerca di processori negli assembly.

1 public class I n p u t P r o c e s s o r M a n a g e r
2 {
3 private static I n p u t P r o c e s s o r M a n a g e r _instance =
new I n p u t P r o c e s s o r M a n a g e r () ;
4
5 public static I n p u t P r o c e s s o r M a n a g e r Instance { get
{ return _instance ; } }
6
7
8 Dictionary < string , InputProcessorDescriptor >
_processors = new Dictionary < string ,
InputProcessorDescriptor >() ;
9 }

Listato 13: InputProcessorManager

La ricerca dei processori utilizzabili avviene ricercando all’interno di un


assembly tutti i tipi per cui sia definito uno degli attributi InputProcesso-
rAttribute, OutputProcessorAttribute o RowProcessorAttribute. Assembly-
Helper è una classe statica preposta alla gestione degli assembly. Contiene
il metodo ScanAssembly, che come in precedenza descritto cerca processori
all’interno di un assembly (listato 14).

1 public static bool ScanAssembly ( Assembly pAssembly )

33
2 {
3 bool res = false ;
4 foreach ( Type type in pAssembly . GetTypes () )
5 {
6 I n p u t P r o c e s s o r A t t r i b u t e ipa =
Attribute . Ge tC us to mA tt ri bu te ( type ,
typeof ( I n p u t P r o c e s s o r A t t r i b u t e ) ) as
InputProcessorAttribute ;
7 if ( ipa != null )
8 {
9 res = true ;
10 I n p u t P r o c e s s o r D e s c r i p t o r ipd = new
I n p u t P r o c e s s o r D e s c r i p t o r ( type , ipa ) ;
11
12 I n p u t P r o c e s s o r M a n a g e r . AddI nputPr ocess or ( ipd ) ;
13 }
14 ...

Listato 14: AssemblyHelper

Il metodo ScanLoadedAssemblies richiama il metodo ScanAssembly per


ogni assembly attualmente caricato e il metodo LoadNewAssembly perme-
tte di caricare un assembly specificato dal nome del file ed effettuarne la
scansione.
La classe MultiThreadContext contiene una serie di metodi per facilitare
la gestione del multi-threading . Il problema principale nasce nel momento
in cui processori che operano in modalità asincrona debbano interagire con
elementi di interfaccia grafica, come form o controlli. Per la tipologia di
gestione dei thread e degli oggetti COM di .net infatti, le chiamate a elementi
di interfaccia possono essere effettuate solo dal thread da cui sono stati
creati 11 . La classe tiene quindi traccia del thread principale dell’apartment
corrente e permette di invocare dei delegati su questo thread tramite il
metodo Invoke.

3.3 Progetto Xml


Nel progetto Xml è implementato il processore di input per file xml, il design-
er e la procedura guidata per la sua configurazione. Il progetto dipenderà
dal progetto Core e dal progetto ConfigUI. Nell’implementazione della classe
XmlInputProcessor è stato effettuato l’override del metodo Prepare dove
come si può notare alle righe 5-6 del listato 15 viene, se richiesto dalla config-
urazione, visualizzata la finestra per la selezione del file xml (righe 5-6, listato
11
Questo qual’ora il processo sia in modalità single thread apartment, per maggiori
informazioni si veda [6]; inoltre, per poter essere compatibile con il controllo WebBrowser,
l’applicazione dovrà essere in modalità single thread apartment.

34
15, si noti l’uso della classe MultiThreadContext precedentemente descrit-
ta). Viene quindi creata un’istanza di un documento XPathDocument[18],
su cui viene effettuata la query xpath (riga 17, listato 15).

1 protected override void Prepare ()


2 {
3 if ( Configuration . AskForFile )
4 {
5 ThreadStart ts = delegate {
Configuration . Filename =
SelectFile . Promp tForFi lename (
Configuration . Filename ) ; };
6 M ult iT hr ea dC on te xt . Current . Invoke ( ts ) ;
7
8 if ( string . IsNullOrEmpty ( Configuration . Filename )
|| ! File . Exists ( Configuration . Filename ) )
9 {
10 throw new
C o n f i g u r a t i o n E x c e p t i o n ( string . Format ( " Input
xml file not specified or not found
( ’{0} ’) " , Configuration . Filename ) ,
" Filename " ) ;
11 }
12 }
13
14 _document = new
XPathDocument ( Configuration . Filename ) ;
15 XPathNavigator navigator =
_document . CreateNavigator () ;
16
17 _iterator =
navigator . Select ( Configuration . XPathExpression ) ;
18 }

Listato 15: XmlInputProcessor

Nell’implementazione del metodo ReadDataRow sarà quindi utilizzato


l’iteratore ottenuto dalla query per navigare tra i nodi dell’xml che rappre-
sentano i record; viene quindi istanziato un oggetto di tipo DataRow e sul
nodo corrente vengono effettuate le query xpath (informazione salvata nello
schema dei dati sotto la chiave ColumnXPath) per ogni oggetto Column
nello schema dei dati (righe 3-6, 9, listato 16). Se la query ha successo il
valore per il campo viene caricato nell’istanza dell’oggetto DataRow tramite
il metodo SetValue (righe 10-14, listato 16).

35
1 protected override DataRow ReadDataRow ()
2 {
3 if ( _iterator . MoveNext () )
4 {
5 DataRow dr = CreateRow () ;
6 XPathNavigator current = _iterator . Current ;
7 foreach ( Column cn in DataSchema )
8 {
9 XPa thNode Iterat or node =
current . Select ( cn [ " ColumnXPath " ]. ToString () ) ;
10 if ( node . MoveNext () )
11 {
12 object value = node . Current . TypedValue ;
13 dr . SetValue ( value , cn ) ;
14 }
15 }
16 return dr ;
17 }
18 return null ;
19 }

Listato 16: XmlInputProcessor(continua)

Da notare che infine non è necessario specificare un comportamento par-


ticolare per il funzionamento asincrono, che viene quindi lasciato all’imple-
mentazione di base di InputProcessorBase (descritta nella sez 3.1). La classe
XmlInputProcessorCfg implementa banalmente tutte le proprieta per gestire
la configurazione; da notare l’uso dell’attributo ConfigurationDesigner per
associare alla classe un designer, e l’implementazione di un UITypeEditor
per la scelta del file xml dalla property grid.

1 [ C o n f i g u r a t i o n D e s i g n e r ( typeof ( InputDesigner ) ) ]
2 public class X m l I n p u t P r o c e s s o r C f g
3 {
4 [ Editor ( typeof ( XmlFil eName Editor ) ,
5 typeof ( System . Drawing . Design . UITypeEditor ) ) ]
6 public string Filename { get ; set ; }

Listato 17: XmlInputProcessorCfg

La classe InputDesigner è implementata derivando da UserControl, si


compone di tre parti, un pulsante per l’avvio della procedura guidata, una
PropertyGrid per la modifica della configurazione e una gridview, cui verrà
associata mediante data binding lo schema dei dati.

36
3.3.1 RowProcessors
Questo progetto contiene il processore utilizzato per modificare lo schema
dei dati, ColumnMappingProcessor. In fase di prepare il processore carica
dalla configurazione la lista di mapping (che rappresentano la mappatura
appunto tra i campi in ingresso e i campi in uscita) e li carica all’interno
di una lista di RuntimeMapping, per velocizzare l’esecuzione nella fase di
processo dei dati.

1 [ RowProcessor ( " Column Mapping Processor " ," " ) ]


2 public class C o l u m n M a p p i n g P r o c e s s o r :
RowProcessor < ColumnMappingCfg >
3 {
4 protected override void ProcessRowCore ( DataRow
pInputRow , DataRow pOutputRow )
5 {
6 for ( int i = 0; i < this . _mappings . Length ; i ++)
7 {
8 if ( _mappings [ i ]. IsConstant )
9 {
10 pOutputRow . SetValue ( _mappings [ i ]. ConstantValue ,
pOutputRow . Schema . G etC ol um nB yO rd in al (
_mappings [ i ]. OutputIndex ) ) ;
11 }
12 else
13 {
14 pOutputRow [ _mappings [ i ]. OutputIndex ] =
pInputRow [ _mappings [ i ]. InputIndex ];
15 }
16 }
17 }
18 }

Listato 18: ColumnMappingProcessor

Il metodo ProcessRowCore quindi assegna al record in uscita i valori del


record in entrata modificandone la posizione all’interno del vettore di valori
(riga 15, listato 18). Se tutta via il campo è mappato su di una costante
definita in fase di configurazione, questa viene inserita effettuandone la con-
versione di tipo come definito nello schema di output per quel campo (riga
10, listato 18).

37
3.4 Processori basati su Ado.Net
3.4.1 DbInputProcessor e DbOutputProcessor
La classe DbInputProcessor implementa le funzionalità per integrare l’ar-
chitettura di Ado.Net con un processore. In particolare, fornisce un’imple-
mentazione dei metodi Prepare, ReadDataRow, Close e Reset. Nel metodo
prepare, si richiamano due metodi (righe 6-7, listato 19) astratti che dovran-
no restituire un oggetto DbCommand (il comando da eseguire) e un oggetto
DbConnection (la connessione con la base di dati), quindi aprirà la connes-
sione ed effettuerà la query ottenendo un oggetto DbDataReader. Da notare
che in questo modo l’inizializzazione del comando viene lasciata agli eredi,
permettendo quindi di configurarne le implementazioni specifiche.

1 protected abstract DbConnection CreateConnection () ;


2 protected abstract DbCommand CreateCommand () ;
3
4 protected override void Prepare ()
5 {
6 _connection = CreateConnection () ;
7 DbCommand cmd = CreateCommand () ;
8 cmd . Connection = _connection ;
9 _connection . Open () ;
10 _reader =
cmd . ExecuteReader ( CommandBehavior . KeyInfo ) ;
11 }
12
13 protected override DataRow ReadDataRow ()
14 {
15 DataRow row = null ;
16 if ( _reader . Read () )
17 {
18 row = new
DataRow ( _reader . FieldCount , this . DataSchema ) ;
19
20 for ( int i = 0; i < _reader . FieldCount ; i ++)
21 row [ i ] = _reader . GetValue ( i ) ;
22 }
23 return row ;
24 }

Listato 19: DbInputProcessor

Nel metodo ReadDataRow quindi si itererà sull’oggetto DbDataReader e


si inseriranno i valori letti all’interno di un oggetto DataRow. Da notare che
l’accesso ai valori all’interno del reader avviene in maniera posizionale (righe

38
20-21, listato 19); questo implica che in fase di configurazione del processore
lo schema associato al processore sia identico a quello che la query produce.
Per la classe DbOutputProcessor il metodo Prepare è del tutto identi-
co. Il metodo WriteData invece sfrutterà lo schema dei dati per costruire
la lista di parametri da passare all’oggetto DbCommand che effettuerà l’in-
serimento con il metodo ExecuteScalar. Il parametro viene istanziato, come
per la connessione e il comando, da un metodo astratto, per poter essere
inizializzato dall’erede.

1 public override void WriteData ( DataRow row )


2 {
3 _cmd . Parameters . Clear () ;
4 foreach ( Column dr in DataSchema )
5 {
6 string cname = dr . Name ;
7 int ordinal = ( int ) dr . Ordinal ;
8 object value = row [ ordinal ];
9 if ( value == null ) value = DBNull . Value ;
10 DbParameter prm = CreateParameter ( cname ,
value ) ;
11 _cmd . Parameters . Add ( prm ) ;
12 }
13 object result = _cmd . ExecuteScalar () ;
14 }

Listato 20: DbOutputProcessor

3.4.2 MsSqlServerInputProcessor e MsSqlServerOutputProces-


sor
L’implementazione specifica per Microsoft Sql Server, è stata realizzata in
maniera estremamente semplice:

1 [ InputProcessor ( " Ms Sql Server Input Processor " ," " ) ]


2 public class M s S q l S e r v e r I n p u t P r o c e s s o r :
DbInputProcessor < MsSqlServerInputProcessorCfg >
3 {
4 protected override DbCommand CreateCommand ()
5 {
6 SqlCommand cmd = new
SqlCommand ( Configuration . SqlQuery ) ;
7 cmd . CommandType = Configuration . CommandType ;
8 return cmd ;

39
9 }
10 protected override DbConnection CreateConnection ()
11 {
12 return new
SqlConnection ( Configuration . ConnectionString ) ;
13 }
14 }

Listato 21: MsSqlServerInputProcessor

1 [ OutputProcessor ( " Ms Sql Server Output Processor " ,


"")]
2 public class M s S q l S e r v e r O u t p u t P r o c e s s o r :
DbOutputProcessor < MsSqlServerOutputProcessorCfg >
3 {
4 protected override DbCommand CreateCommand ()
5 {
6 SqlCommand cmd = new
SqlCommand ( Configuration . SqlQuery ) ;
7 cmd . CommandType = Configuration . CommandType ;
8 return cmd ;
9 }
10 protected override DbConnection CreateConnection ()
11 {
12 return new
SqlConnection ( Configuration . ConnectionString ) ;
13 }
14 protected override DbParameter
CreateParameter ( string pName , object pValue )
15 {
16 string name = SqlHelper . GetParameterName ( pName ) ;
17 SqlParameter prm = new SqlParameter ( name , pValue ) ;
18 return prm ;
19 }
20 }

Listato 22: MsSqlServerOutputProcessor

Per questi processori, è stata inoltre implementata un’interfaccia di con-


figurazione basata su delle procedure guidate che utilizzano finestre di dialog
standard (come ad esempio la finestra per la selezione della stringa di con-
nessione alla base di dati). In particolare, lo schema dei dati associato ad
una query, sia tramite sql sia tramite l’esecuzione di una stored procedure,
viene dedotto automaticamente.

40
3.5 Processori basati su http
3.5.1 HttpXmlInputProcessor
Questo processore implementa le stesse funzionalità del processore XmlIn-
putProcessor, con la differeneza che il file xml viene scaricato dal web tramite
una richiesta http, e supporta l’autenticazione Basic, Digest e basata su
forms html. Questa differenza risiede interamente nel metodo Prepare, che
include la procedura di autenticazione (righe 5-20, listato 23), l’invio della
richiesta http e l’apertura dello stream con la risposta.

1 protected override void Prepare ()


2 {
3 _cookies = new CookieContainer () ;
4
5 switch ( Configuration . Authentication . Mode )
6 {
7 case A ut h e nt i c at i o nM o d es . Basic :
8 _ h t t p _ a u t h _ c r e d e n t i a l s = new
Netw orkCre denti al (
Configuration . Authentication . Username ,
Configuration . Authentication . Password ) ;
9 break ;
10
11 case A ut h e nt i c at i o nM o d es . Digest :
12 _ h t t p _ a u t h _ c r e d e n t i a l s = new
Netw orkCre denti al (
Configuration . Authentication . Username ,
Configuration . Authentication . Password ) ;
13 break ;
14
15 case A ut h e nt i c at i o nM o d es . Forms :
16 Browser . DoAuthentication (
Configuration . Authentication
. FormsAuthLoginUrl , _cookies ) ;
17 break ;
18 case A ut h e nt i c at i o nM o d es . None : ...
19 default : ...
20 }
21 ...
22 HttpWebRequest req =
( HttpWebRequest ) HttpWebRequest .
Create ( Configuration . Filename ) ;
23 req . Credentials = _ h t t p _ a u t h _ c r e d e n t i a l s ;
24 req . CookieContainer = _cookies ;
25 HttpWebResponse resp =
( HttpWebResponse ) req . GetResponse () ;

41
26
27 _document = new
XPathDocument ( resp . GetRes ponse Stream () ) ;
28 XPathNavigator navigator =
_document . CreateNavigator () ;
29 _iterator = navigator .
Select ( Configuration . XPathExpression ) ;
30 }

Listato 23: HttpXmlInputProcessor, metodo Prepare

Come si può notare alla riga 16 del listato 23, l’autenticazione forms
viene eseguita richiamando un metodo statico che visualizzerà una finestra
contenente un browser in cui verrà effettuata l’autenticazione. Inoltre i
cookies ricevuti verranno inseriti all’interno di un oggetto CookieContainer
che permetterà poi di inserirli facilmente in tutte le chiamate successive.
Infine alle righe 22-25 del listato 23 viene inviata la richiesta get per il
download dei dati xml.

3.5.2 HttpPostOutputProcessor
In questo processore il metodo Prepare ha l’unica funzione di eseguire l’au-
tenticazione effettuata esattamente come per il processore HttpXmlInput-
Processor.
Nell’override del metodo WriteDataRow invece viene costruita la richies-
ta http che effettuerà l’invio dei dati. Nel caso di richiesta get verrà costru-
ita una query string concatenando le coppie chiave valore rappresentate da
nome del campo (preso dallo schema dei dati) e il valore del campo. Dopo
aver eseguito la richiesta si valuta la risposta secondo la modalità specificata
(righe 39-51 del listato 24)

1 public override void WriteData ( DataRow row )


2 {
3 string url = Configuration . TargetUrl ;
4
5 if ( Configuration . Method . ToUpperInvariant () ==
" GET " )
6 {
7 foreach ( Column dr in DataSchema )
8 {
9 ....
10 }
11 }

42
12
13 HttpWebRequest req =
( HttpWebRequest ) HttpWebRequest . Create ( url ) ;
14 req . Method = Configuration . Method ;
15
16 if ( Configuration . Authentication . Mode ==
A u th e n ti c a ti o n Mo d e s . Basic ||
Configuration . Authentication . Mode ==
A u th e n ti c a ti o n Mo d e s . Digest )
17 req . Credentials = _ h t t p _ a u t h _ c r e d e n t i a l s ;
18 else if ( Configuration . Authentication . Mode ==
A u th e n ti c a ti o n Mo d e s . Forms )
19 req . CookieContainer = _cookies ;
20
21
22 if ( Configuration . Method . ToUpperInvariant () ==
" POST " )
23 {
24 using ( Stream strm = req . GetRequestStream () )
25 {
26 TextWriter tw = new StreamWriter ( strm ,
Configuration . RequestEncoding ) ;
27 bool isfirst = true ;
28 foreach ( Column dr in DataSchema )
29 {
30 ...
31 }
32 tw . Flush () ;
33 }
34 }
35 try
36 {
37 using ( HttpWebResponse response =
( HttpWebResponse ) req . GetResponse () )
38 {
39 switch ( Configuration . R ow P o st F e ed b a ck M o de )
40 {
41 case R o w P o s t F e e d b a c k M o d e s . HttpCode :
42 FeedbackHttpCode ( response ) ;
43 break ;
44
45 case R o w P o s t F e e d b a c k M o d e s . None :
46 // i n t e n t i o n a l l y do n o t h i n g .
47 break ;
48
49 case R o w P o s t F e e d b a c k M o d e s
. Regula rExpre ssion :
50 F e e d b a c k R e g u l a r E x p r e s s i o n ( response ) ;

43
51 break ;
52 }
53
54 if ( Configuration . D i s p l a y P e r R o w R e s p o n s e )
55 {
56 string body = GetResponseText ( response ) ;
57 bool res =
Browser . ShowString ( body , true ) ;
58 this . Configuration . D i s p l a y P e r R o w R e s p o n s e
= res ;
59 }
60 response . Close () ;
61 }
62 }
63 catch ( WebException ex )
64 { throw new P r o c e s s E r r o r E x c e p t i o n ( ex ) ; }
65 }

Listato 24: HttpPostOutputProcessor, WriteData

Infine, righe 56-58 del listato 24, viene eventualmente visualizzata la


risposta attraverso una finestra contente un browser.

3.5.3 BrowserFillerOutputProcessor
Il processore BrowserFillerOutputProcessor implementa l’approccio di invio
dei dati tramite la compilazione di una form html visualizzata all’interno
di un controllo WebBrowser. Al processore viene assegnato un controllo
WebBrowser per effettuare le operazioni tramite la proprietà WebBrowser-
Control. Questo processore non supporta la modalità sincrona. Nel’over-
ride del metodo CorePrepProcAsync si visualizzerà nel browser la pagina di
autenticazione (righe 16-17, listato 25).

1 [ OutputProcessor ( " Browser Filler Output


Processor " ," " ) ]
2 public class B r o w s e r F i l l e r O u t p u t P r o c e s s o r :
OutputProcessor < BrowserFillerOutputProcessorCfg >
3 {
4 public override void WriteData ( DataRow row )
5 { throw new N o t S u p p o r t e d E x c e p t i o n ( " Syncronous mode
not supported . " ) ; }
6 public override void WriteDataAsync ( DataRow row )
7 {
8 FillDataState state = new FillDataState () ;

44
9 state . RowToFill = row ;
10 state . Processor = this ;
11 _ w e b _ b r o w s e r _ c o n t r o l . Navigate (
Configuration . FillPageUri ) ;
12 _ w e b _ b r o w s e r _ c o n t r o l . Do cument Comple ted += new
WebBrowserDocumentCompletedEventHandler (
state . _ w e b _ b r o w s e r _ c o n t r o l _ D o c u m e n t C o m p l e t e d ) ;
13 }
14 protected override void CoreP repPro cAsync ()
15 {
16 Web Browse rContr ol . Doc umentC omplet ed += new
WebBrowserDocumentCompletedEventHandler (
WebBrowserControl_DocumentCompleted );
17 Web Browse rContr ol . Navigate ( Configuration
. Authentication . Forms AuthLo ginUrl ) ;
18 }
19
20 void W e b B r o w s e r C o n t r o l _ D o c u m e n t C o m p l e t e d ( object
sender , W e b B r o w s e r D o c u m e n t C o m p l e t e d E v e n t A r g s e )
21 {
22 Web Browse rContr ol . Doc umentC omplet ed -= new
WebBrowserDocumentCompletedEventHandler (
WebBrowserControl_DocumentCompleted );
23 this . O n P r e p a r e P r o c e s s o r A s y n c C o m p l e t e d () ;
24 }
25 public WebBrowser We bBrows erCont rol
26 { get { ... } set { ... } }
27 }

Listato 25: BrowserFillerOutputProcessor

Nel’override del metodo WriteDataAsync viene istanziato un oggetto


di tipo FillDataState utilizzato allo scopo di mantenere lo stato associato
all’operazione di invio durante la procedura asincrona (righe 8-11 del listato
25). Quindi viene visualizzata la pagina contenente la form html all’interno
del browser (riga 12 del listato 25).

1 private class FillDataState


2 {
3 public DataRow RowToFill { get ; set ; }
4 public B r o w s e r F i l l e r O u t p u t P r o c e s s o r Processor {
get ; set ; }
5 internal void _ w e b b r o w s e r _ _ D o c u m e n t C o m p l e t e d ( object
sender , W e b B r o w s e r D o c u m e n t C o m p l e t e d E v e n t A r g s e )
6 {

45
7 WebBrowser browser = sender as WebBrowser ;
8 try
9 {
10 HtmlDocument doc = browser . Document as
HtmlDocument ;
11 foreach ( Column col in RowToFill . Schema )
12 {
13 HtmlElement element =
doc . GetElementById ( col . Name ) ;
14 if ( element . TagName . ToLowerInvariant () ==
" input " && element . GetAttribute ( " type " )
. ToLowerInvariant () == " text " )
15 element . InnerText =
RowToFill . _row_values [ col . Ordinal ]
. ToString () ;
16 else if ( element . TagName . ToLowerInvariant ()
== " textarea " )
17 element . InnerText =
RowToFill . _row_values [ col . Ordinal ]
== null ? " " :
RowToFill . _row_values [ col . Ordinal ]
. ToString () ;
18
19 }
20 Processor . O n W r i t e D a t a A s y n c C o m p l e t e t d ( RowToFill ) ;
21 }
22 finally { ... }
23 }
24 }

Listato 26: La classe FillDataState

Nell’handler dell’evento DocumentCompleted, definito all’interno della


classe FillDataState, una volta che il browser ha terminato di aprire la pagina
per l’invio dei dati, si acccede al documento nel browser e vengono assegnati
i valori ai campi presenti nello schema (righe 13-17 del listato 26).

3.5.4 HttpSubmitterOutputProcessor
Questo processore viene utilizzato all’interno del progetto SilverlightUI. L’ap-
proccio implementato è quello di iniettare del markup html (contenente una
form) all’interno di un’iframe (righe 4-5, listato 27), compilarne i campi e
quindi visualizzare l’iframe o effettuare il submit della form html. Questo
viene realizzato nel metodo WriteDataAsync (questo processore non sup-
porta la modalità sincrona).

46
1 public override void WriteDataAsync ( DataRow row )
2 {
3 HtmlDocument doc = HtmlPage . Document ;
4 HtmlElement submitter = doc . GetElementById (
Configuration . S u bm i t te r I F ra m e Na m e ) ;
5 submitter . SetProperty ( " innerHTML " ,
Configuration . SubmitterHtml ) ;
6
7 foreach ( Column col in row . Schema )
8 {
9 HtmlElement element =
doc . GetElementById ( col . Name ) ;
10 if ( element != null )
11 {
12 if ( element . TagName . ToLower () == " input " &&
( element . GetAttribute ( " type " ) == null ||
element . GetAttribute ( " type " ) . ToLower () ==
" text " ) )
13 element . SetProperty ( " value " ,
row . _row_values [ col . Ordinal ] == null ? " "
:
row . _row_values [ col . Ordinal ]. ToString () ) ;
14 else if ( element . TagName . ToLower () ==
" textarea " )
15 element . SetProperty ( " value " ,
row . _row_values [ col . Ordinal ] == null ? " "
:
row . _row_values [ col . Ordinal ]. ToString () ) ;
16 }
17
18 }
19 Application . Current . RootVisual
. Dispatcher . BeginInvoke ( new System . Action (() = >
20 {
21 HtmlPage . Window . Invoke (
Configuration . J a v a s r i p t P r o m p t D a t a S u b m i s s i o n ) ;
22 }) ) ;
23
24 O n W r i t e D a t a A s y n c C o m p l e t e t d ( row ) ;
25 }

Listato 27: HttpSubmitterOutputProcessor

Alle righe 9-15 del listato 27 viene compilata la form html, quindi tramite
HTML Bridge di silverlight viene invocata una funzione javascript che definirà
se semplicemente visualizzare l’iframe o effettuarne il submit in maniera

47
automatica (righe 19-22 del listato 27).

3.6 Interfaccia utente Windows Presentation Foundation


Nel progetto WpfUI è stata realizzata l’interfaccia per la soluzione “ap-
plicazione desktop” al problema del trasferimento dati tra i siti units.it e
uni2b.com. Ciò è stato realizzato in un’unica finestra contenente sul lato de-
stro un controllo WebBrowser12 e sulla sinistra l’interfaccia per la selezione
delle entità su cui operare e per la selezione dei record. In particolare, per
la visualizzazione dei record che verranno caricati dall’HttpXmlInputPro-
cessor, è stato utilizzato un controllo ListBox ed è stato implementato il
databinding bi-direzionale all’indicizzatore della classe DataRow. Il proces-
sore di input infatti ci restituirà una lista di oggetti DataRow, in cui i valori
associati ai vari campi sono accessibili solo tramite l’indicizzatore. Alla se-
lezione di un record viene visualizzato un controllo che rappresenta una vista
di dettaglio sui dati selezionati; su questo controllo è presente un pulsante
per passare il dato al processore di trasformazione dello schema e quindi al
processore di output per l’invio.
Nel listato 28 è riportato un esempio di inizializzazione del processore di
input.

1 Inpu tProce ssorCf g cfg = null ;


2 using ( System . IO . Stream strm =
Application . GetR esourc eStrea m ( new
Uri ( " WpfUI ; component / " + item + " Input . cfg " ,
UriKind . Relative ) ) . Stream )
3 {
4 XmlSerializer xs = new
XmlSerializer ( typeof ( Input Proces sorCfg ) ) ;
5 cfg = xs . Deserialize ( strm ) ;
6 }
7 _current_input = cfg . CreateProcessor () ;
8 _current_input . ProcessorError += new EventHandler (
... ) ;
9 _current_input . R e a d D a t a R o w A s y n c C o m p l e t e t d += new
EventHandler < DataRowEventArgs >( ... ) ;
10 _current_input . P r e p a r e P r o c e s s o r A s y n c C o m p l e t e d += new
EventHandler ( ... ) ;
11 _current_input . P r e p a r e P r o c e s s o r A s y n c () ;

12
La versione 3.5 del framework .net mette a disposizione due versioni del controllo
WebBrowser, una per Windows Forms e l’altra per WPF. Poichè tuttavia la versione
per Windows Forms contiene già una serie di wrapper agli oggetti COM che rappre-
sentano l’html dom nel browser, si è scelto di utilizzare questa nei processori sviluppati
per la libreria; quindi anche qui è stata inserita questa versione utilizzando il controllo
WindowsFormsHost.

48

Listato 28: Inizializzazione di un processore

1 List < BindableDataRow > rows = new


List < BindableDataRow >() ;
2 while ( _current_input . CanRead )
3 {
4 DataRow row = _current_input . GetDataRow () ;
5 if ( row == null ) break ;
6 rows . Add ( new BindableDataRow () { Row = row }) ;
7 }

Listato 29: Lettura dei dati in input

3.7 Implementazione della soluzione web-based


3.7.1 Progetto SilverlightCore
Poiché gli assembly creati per il framework.net e per silverlight non sono di-
rettamente compatibili è stato necessario creare il progetto SilverlightCore,
di tipo silverlight class library. Ad esso, sono stati aggiunti come link i file
sorgenti del progetto Core; si è cosi ottenuta una versione della libreria com-
patibile con silverlight. In qesta versione, si sono dovute colmare alcune la-
cune con il framework 3.5; sono state aggiunte le classi SerializableAttribute
e BrowsableAttribute13 e degli extension method[7] per la classe string.

1 namespace System
2 {
3 public class S e r i a l i z a b l e A t t r i b u t e : Attribute { }
4
5 public class Bro ws ab le At tr ib ut e : Attribute
6 {
7 public B ro ws ab le At tr ib ut e ( bool browsable ) { }
8 }
9
10 public static class Extensions
11 {

13
Sebbene le due classi non abbiano alcuna utilità all’interno dell’assembly silverlight,
in quanto non utilizzati dalla libreria ma al più dall’applicazione di configurazione, si è
preferito aggiungerle per il fatto che il loro ampio utilizzo avrebbe richiesto la modifica,
per l’aggiunta di compilazione condizionale, di svariati files.

49
12 public static string ToLowerInvariant ( this string
s)
13 {
14 return s . ToLower ( CultureInfo . InvariantCulture ) ;
15 }
16 public static string ToUpperInvariant ( this string
s)
17 {
18 return s . ToUpper ( CultureInfo . InvariantCulture ) ;
19 }
20 }
21 }

Listato 30: Silverlight fixes

3.7.2 Interfaccia utente web


L’interfaccia web è stata realizzata in una pagina html costituita da due parti
distinte (in analogia con la soluzione wpf), a destra un iframe in cui verrà
visualizzato il sito web uni2b.com e a sinistra l’applicazione silverlight (in
modalità windowless, per permettere ad altri controlli html, come l’iframe
per l’invio dei dati, di sovraporsi ad essa). L’applicazione contiene sempre
i pulsanti per la selezione dell’entità e un ListBox per la visualizzazione
dei record. All’interno della stessa pagina è infine definita una funzione
javascript che visualizza l’iframe per l’invio dei dati.

50
4 Conclusioni
Per quanto riguarda l’obiettivo primo del progetto, l’esportazione di dati
tra due siti web, può dirsi raggiunto grazie alle due soluzioni proposte, l’ap-
plicazione desktop e la soluzione web-based. Ciò che forse non si è riuscito
a rendere chiaro all’interno di questo documento, è come le due soluzioni
siano frutto di un percorso non lineare alla ricerca della miglior soluzione
in termini di usabilità, portabilità e standardizzazione. Sebbene entrambe
soddisfino i requisiti, è, a mio avviso, la soluzione basata su Silverlight il
punto di arrivo, in quanto più fruibile e semplice. Tuttavia, la natura delle
due soluzioni le rende complementari e non necessariamente alternative.
Anche la libreria è stata frutto di un continuo percorso di raffinamento,
cercando di generalizzare sempre l’approccio ai problemi, e sebbene si siano
cercati di affrontare gli scenari più ampi, dai processori basati su Ado.Net
a quelli basati su http, a quelli per file xml, non è scontato che la soluzione
proposta possa essere sempre accettabile.
Nei limiti degli ambiti considerati, tuttavia, la libreria ha raggiunto pien-
amente gli obiettivi prefissati. Ne è prova il risultato ottenuto nel caso speci-
fico, dove la libreria si adatta ad un’interfaccia web e desktop, mescola WPF
e Windows Forms, mette in contatto tecnologie diverse, dai dbms a siti web
in php o java, fino ad interagire con javascript.
La molteplicità degli aspetti coinvolti rende difficile quantificare il lavoro
svolto, se vogliamo si può certo quantificare il lavoro prodotto, 180 classi
per circa 25000 righe totali in una libreria e tre applicazioni, ma preferisco
quantificare la quantità di argomenti che questo progetto mi ha permesso
di approfondire, http, html, i cookies e come i browser li supportano, Wpf,
Silverlight, Windows Forms, javascript, Ado.Net, xml, xpath.

51
5 Riferimenti bibliografici

[1] msdn - PropertyGrid Class (System.Windows.Forms)


<http://msdn.microsoft.com/en-us/library/system.windows.
forms.propertygrid.aspx>

[2] msdn - ADO.NET


<http://msdn.microsoft.com/en-us/library/e80y5yhx(VS.80)
.aspx>

[3] msdn - DataGrid Class (System.Windows.Forms)


<http://msdn.microsoft.com/it-it/library/system.windows.
forms.datagrid.aspx>

[4] msdn - PropertyDescriptor Class (System.ComponentModel)


<http://msdn.microsoft.com/en-us/library/system.
componentmodel.propertydescriptor(VS.80).aspx>

[5] msdn - ICustomTypeDescriptor Interface (System.ComponentModel)


<http://msdn.microsoft.com/en-us/library/system.
componentmodel.icustomtypedescriptor.aspx>

[6] msdn - Processes, Threads, and Apartments


<http://msdn.microsoft.com/en-us/library/ms693344(VS.85)
.aspx>

[7] msdn - Extension Methods (C# Programming Guide)


<http://msdn.microsoft.com/en-us/library/bb383977.aspx>

[8] msdn - UserControl Class (System.Windows.Controls)


<http://msdn.microsoft.com/en-us/library/system.windows.
controls.usercontrol.aspx>

[9] msdn - CommandType Enumeration (System.Data)


<http://msdn.microsoft.com/en-us/library/system.data.
commandtype.aspx>

[10] msdn - Classe WebBrowser (System.Windows.Forms)


<http://msdn.microsoft.com/it-it/library/system.windows.
forms.webbrowser(VS.80).aspx>

[11] msdn - Proprietà HtmlDocument.Cookie (System.Windows.Forms)


<http://msdn.microsoft.com/it-it/library/system.windows.
forms.htmldocument.cookie(VS.80).aspx>

52
[12] msdn - HTML Bridge: Interaction Between HTML and Managed
Code
<http://msdn.microsoft.com/en-us/library/cc645076(VS.95)
.aspx>

[13] msdn - Network Security Access Restrictions in Silverlight 2


<http://msdn.microsoft.com/en-us/library/cc645032(VS.95)
.aspx>

[14] msdn - Event-based Asynchronous Pattern Overview


<http://msdn.microsoft.com/en-us/library/wewwczdw(VS.80)
.aspx>

[15] msdn - Opzioni di layout dei Windows Form


<http://msdn.microsoft.com/it-it/library/aa983768(VS.71)
.aspx>

[16] msdn - WebBrowser.Document Property


<http://msdn.microsoft.com/en-us/library/system.windows.
forms.webbrowser.document.aspx>

[17] msdn - .NET Framework Regular Expressions


<http://msdn.microsoft.com/en-us/library/hs600312(VS.71)
.aspx>

[18] msdn - XPathDocument Class


<http://msdn.microsoft.com/en-us/library/system.xml.
xpath.xpathdocument(VS.71).aspx>

[19] W3C - XML Path Language (XPath)


<http://www.w3.org/TR/xpath>

[20] W3C - Forms in HTML documents


<http://www.w3.org/TR/html401/interact/forms.html>

[21] W3C - HTTP/1.1: Status Code Definitions


<http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>

[22] W3C - Forms in HTML documents - 17.13.2 Successful controls


<http://www.w3.org/TR/html401/interact/forms.html#
successful-controls>

[23] W3C - HTTP/1.1: Status Code Definitions - 10.3 Redirection 3xx


<http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#
sec10.3>

[24] Wikipedia - CAPTCHA


<http://it.wikipedia.org/wiki/Captcha>

53
[25] Wikipedia - Cross-site request forgery
<http://en.wikipedia.org/wiki/Cross_site_request_
forgery>

[26] IETF - HTTP State Management Mechanism


<http://www.ietf.org/rfc/rfc2965.txt>

[27] Silverlight Guide - Blog Archive - Nice overview of Silverlight HTTP


stack
<http://www.silverlightguide.net/
Nice-overview-of-Silverlight-HTTP-stack/>

54