Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
FACOLTÀ DI INGEGNERIA
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.
3
Figura 2: Entità esportate da uni2b.com
4
Figura 4: Diagramma di flusso del trasferimento di dati
• Close - speculare del prepare, ad esempio per la chiusura del file o della
connessione al database.
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)
WriteDataRow:=WriteDataRow(DataRow 1')
Close
Close()
Fase 4
Close()
Reset()
Reset()
Fase 5
Reset()
6
• Un’interfaccia standard per ognuno dei precendenti nodi, con delle
classi base che implementino le funzioni più comuni di ogni nodo.
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.
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:
9
Figura 7: Diagramma UML parziale del progetto di configurazione
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.
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.
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:
• Il tipo di comando 5
13
Figura 11: Diagramma UML parziale dei processori basati su Ado.Net
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:
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
16
• La proprietà Filename specificata per l’XmlInputProcessor dovrà es-
sere un url assoluto alla risorsa richiesta.
• Modalità di autenticazione
17
• Visualizza la risposta per ogni riga
• Modalità di feedback
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”.
19
effettuare l’invio, passando quindi l’oggetto DataRow associato a quel record
al ColumnMappingProcessor e quindi al processore di output.
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):
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
24
figuration infine altro non è che un wrapper sulla proprietà InternalCon-
figuration.ConfigurationInstance (di cui effettua il cast a CfgType, righe
14-15).
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
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).
27
Listato 5: InputProcessorCfg
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
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
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
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
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
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
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)
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
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
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).
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)
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
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.
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.
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.
39
9 }
10 protected override DbConnection CreateConnection ()
11 {
12 return new
SqlConnection ( Configuration . ConnectionString ) ;
13 }
14 }
Listato 21: MsSqlServerInputProcessor
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.
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)
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
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).
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
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
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).
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 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
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
52
[12] msdn - HTML Bridge: Interaction Between HTML and Managed
Code
<http://msdn.microsoft.com/en-us/library/cc645076(VS.95)
.aspx>
53
[25] Wikipedia - Cross-site request forgery
<http://en.wikipedia.org/wiki/Cross_site_request_
forgery>
54