di Francesco BalenaOggi gironzolavo per la rete e ho trovato un forum dove qualcuno poneva la seguente domanda: "data la stringa ABCD è possibile scrivere una regular expression che fa il match con un qualsiasi anagramma di tale stringa - ad es. DBAC o CDBA ma non ABDE oppure AABC?". Ovviamente la domanda era intrigante per uno che come me smanetta molto con le regex, e stranamente non l'avevo mai vista in giro. Qualcuno obiettava che le regex non erano abbastanza potenti, e che serviva scrivere un piccolo programmino per testare le varie combinazioni, quindi mi sono sentito in obbligo di pensarci un po' su e postare la mia soluzione.
Se state semplicemente controllando che una stringa fornita dall'utente sia una permutazione di ABCD la soluzione è in effetti molto semplice:
^(?=.*a)(?=.*b)(?=.*c)(?=.*d)[abcd]{4}$
dove il costrutto (?=.*a) si chiama positive lookahead ed esegue il match con l'espressione nella parentesi ma senza consumare caratteri, quindi in altre parole significa che la stringa deve contenere il carattere A da qualche parte, ovvero preceduto da zero o più caratteri. Con le successive espressioni positive lookahead si controlla che nella stringa ci siano anche i caratteri B,C e D. Quando questa fase è completata, la regex [abcd]{4} assicura che la stringa contenga solo questi quattro caratteri e non altri.
Se invece stiamo cercando tutti i possibili anagrammi di ABCD all'interno di un testo di lunghezza qualsiasi, allora la regex è solo leggermente più complicata:
\b(?=.{0,3}a)(?=.{0,3}b)(?=.{0,3}c)(?=.{0,3}d)[abcd]{4}\b
In questo caso oltre a imporre che i caratteri A,B,C,D siano tutti presenti ci accertiamo anche che non distino più di 3 caratteri dall'inizio della parola, e usiamo i delimitatori \b per imporre che la parola stessa contenga esattamente quattro caratteri. Ovviamente dovete specificare se il case dei caratteri è significativo o meno, perchè il match avvenga indipendentemente dal fatto che le parole siano in maiuscolo o minuscolo.
di Francesco BalenaPremessa. Non so se questa piccola scoperta che ho fatto di recente sia già documentata da qualche parte su Internet. Non ne ho mai sentito parlare, ma mi sembra strano che nessun’altro ci abbia pensato prima. Comunque, ci sono arrivato da solo e questo è sufficiente per scriverne sul blog.
Mi sembra inutile rimarcare il fatto che uno dei problemi che assilla chi scrive software per professione è la protezione dalle copie. In più, se programmate con .NET avete anche il problema di proteggere le applicazioni dalla decompilazione. Ovviamente i due problemi sono collegati, perchè se volete proteggere una applicazione con qualche meccanismo software (ad es. la lettura delle caratteristiche della macchina su cui il cliente installa la sua copia) occorre evitare che una semplice decompilazione + ricompilazione possa permettere ad un hacker neanche troppo esperto di bypassare la vostra protezione.
Non si tratta certo di problemi nati con .NET. Una quindicina di anni fa campavo sviluppando tool per programmatori con la mia azienda personale – la SoftWhale, dove “Whale” = “Balena” per chi non lo sapesse – e uno dei miei prodotti più gettonati era NOWAY, un programma che crittografava gli eseguibili MS-DOS proteggendoli quindi dalla decompilazione (un problema molto sentito da chi lavorava in Clipper) e permetteva di lanciarli solo sui computer per i quali la software house forniva al cliente dei codici di sblocco. NOWAY era scritto in puro Assembly 8086 e non è sopravvissuto al passaggio da MS-DOS a Windows.
In commercio esistono molti tool per .NET che servono a risolvere questi problemi, incluso obfuscator, compilatori “nativi” e chiavi hardware. Se volete proteggere la vostra IP (Intellectual Property) dovreste darci una occhiata approfondita.Per nostra fortuna, in Code Architects scriviamo soprattutto applicazioni Enterprise che non richiedono questo tipo di protezione, quindi non ho mai approfondito la questione. Immagino che questi tool facciano bene il loro lavoro, e li testerò se e quando avremo la necessità di farlo.
Se però (a) scrivete perlopiù applicazioni Windows Forms, (b) non vendete software pacchettizzato ed eseguite direttamente voi le installazioni presso il cliente, e (c) fate anche assistenza post-vendita, allora è possibile ottenere la protezione dalla decompilazione e dalle copie illegali con un meccanismo davvero molto semplice.
Cominciamo scrivendo una semplice applicazione Windows Forms. In questo esempio uso VB2005 ma ovviamente la tecnica si applica a qualsiasi linguaggio .NET.
Imports System.Reflection
Friend Module Module1
<STAThread()> _
Public Sub Main()
' in this demo we call the secret routine both directly and via reflection
' just to prove that either method works
MySecretCode("Direct call")
GetType(Module1).InvokeMember("MySecretCode", BindingFlags.InvokeMethod, _
Nothing, Nothing, New Object() {"Reflection call"})
End Sub
Public Sub MySecretCode(ByVal title As String)
#If Not COPYPROTECT Then
' here goes all the code that you want to protect from decompilation
MessageBox.Show("Running secret code!", title)
' ...
#End If
End Sub
End Module
Poichè la variabile di compilazione COPYPROTECT non è definita, il codice nel metodo MySecretCode è incluso nell’eseguibile come se il blocco #IF non esistesse. In questo esempio proteggo un solo metodo, ma in una applicazione reale dovreste ripetere il procedimento con tutti i metodi che contengono del codice critico, magari dove implementate gli algoritmi più interessanti oppure dove controllate che l’utente abbia la licenza di eseguire il programma sul computer in questione. È importante che tutti i blocchi #IF... #ENDIF siano completamente contenuti in singoli metodi, ovvero che non capiti mai che uno di questi blocchi contenga le keyword Sub o Function (o le corrispettive End Sub o End Function). Per lo stesso motivo, potete utilizzare il meccanismo anche con le Property ma solo se utilizzate i blocchi #IF all’interno del blocco Get o Set. I blocchi #IF non devono assolutamente includere field, definizioni di eventi, o altro.
Per completare la preparazione è opportuno definire una nuova configurazione per la soluzione. Dal menù Build selezionate il comando Configuration Manager, dalla combobox in alto a sinistra selezionate <New> e create una configurazione chiamata CopyProtected, che copia le impostazioni iniziali dalla configurazione Release.

Dopo esservi accertati che la nuova configurazione è quella attiva, create una costante condizionale di compilazione chiamata COPYPROTECT e impostata al valore True. In C# questa costante si crea dalla pagina Build delle proprietà di progetto, mentre in VB2005 dovete cliccare sul pulsante Advanced Compile Options che trovate nella pagina Compile:

Grazie a questa nuova configurazione, potete passare facilmente dalla versione normale (ossia Release) a quella copy-protected. Compilando l’applicazione in entrambe le configurazioni, avrete due eseguibili con lo stesso nome (ad es. Project1.exe) e contenenti gli stessi metadati (perchè le #IF sono sempre all’interno dei metodi e non includono campi, proprietà, o altro). L’esempio che segue presume che abbiate compilato una applicazione che si chiama Project1, che NON è firmata con uno strong name.
Preparate il setup, se necessario, utilizzando la versione Release, ma ricordatevi anche di memorizzare separatamente (ad es. su una chiavetta USB) l’eseguibile ottenuto in configurazione copy-protected e l’utility NGEN fornita con .NET Framework. Infine, recatevi presso il vostro cliente e seguite accuratamente la semplice procedura:
1) Lanciate il setup della applicazione, o più banalmente utilizzate XCOPY per copiare tutti i file sul disco rigido del cliente. In questo esempio supporremo che l’installazione sia avvenuta nella directory c:\myapp e che quindi il file eseguibile sia c:\myapp\Project1.exe.
2) Aprite una finestra di comandi, navigate nella directory che contiene l’esegubile e lanciate NGEN per creare una immagine in codice nativo della applicazione. Il comportamento di default di NGEN è OK, quindi non preoccupatevi troppo delle varie opzioni. (Lasciate aperta questa finestra, perchè vi servirà presto.)
3) Aprite una seconda finestra di comandi, navigate nella directory c:\windows\assembly\NativeImages_v2.0.50727_32\nomeassembly, dove nomeassembly è il nome dell’EXE ma senza l’estensione (Project1 in questo esempio). Noterete che in questa directory contiene una sottodirectory il cui nome è una stringa di caratteri esadecimali (nel mio esempio ho ottenuto d37032afe4f6f44588d52ca99d7bb1e5). Questa directory contiene un eseguibile dal nome nomeassembly.ni.exe (Project1.ni.exe nel nostro esempio), che contiene la versione compilata (senza IL e quindi non decompilabile) dell’eseguibile completo.
4) Dal prompt della seconda finestra dei comandi, con un comando COPY o MOVE spostate il file <nomeassembly>.ni.exe su qualche altra directory del disco rigido, ad esempio: MOVE d37032afe4f6f44588d52ca99d7bb1e5\Project1.ni.exe c:\ Questa operazione serve ad evitare che questo eseguibile sia cancellato dalla successiva invocazione di NGEN. Lasciate aperta anche questa seconda finestra dei comandi.
5) Sovrascrivete il file c:\myapp\Project1.exe con la versione copy-protected che avete sulla chiavetta USB.
6) Dalla prima finestra di comandi lanciate nuovamente NGEN, questa volta sulla versione copy-protected. Se questo programma venisse eseguito ora probabilmente andrebbe in errore o comunque non funzionerebbe bene, perchè alcune parti del codice non sono presenti, quindi NON lo mandate in esecuzione.
7) Dalla seconda finestra dei comandi, eseguite un comando DIR e noterete che la directory creata precedentemente (d37032afe4f6f44588d52ca99d7bb1e5, in questo esempio) è stata sostituita oppure è stata affiancata da un’altra directory dal nome random. (Nell’esempio che sto eseguendo mentre scrivo queste note è stata creata una directory di nome 9990426218ec334e9d3d62f41cb9a255.) Questa nuova directory contiene una nuova versione di Project1.ni.exe, che corrisponde alla versione in codice nativo dell'’eseguibile incompleto.
8) Sempre dalla seconda finestra dei comandi, copiate la copia precedente di Project1.ni.exe (salvata al punto #4) sulla nuova copia, ad esempio con questo comando: MOVE c:\project1.ni.exe 9990426218ec334e9d3d62f41cb9a255 rispondendo Yes alla domanda se intendiamo sovrascrivere il file Project1.ni.exe.
Fatto! Ora potete staccare la chiavetta USB e lasciare sul disco rigido del vostro cliente solo l'assembly "copy-protected" (in versione IL) e l'assembly completo (solo in versione compilata in modo nativo e quindi non decompilabile). Per quanto può sembrare incredibile, l'applicazione funziona correttamente e mostra le due message box Ma se provate a usare ILDASM o Reflector per sbirciare dentro il metodo MySecretCode (e qualsiasi altro metodo il cui interno è racchiuso con un #IF Not COPYPROTECT) troverete che il metodo è vuoto!

Ovviamente neanche Reflector riesce a decompilare alcunchè, visto che proprio manca il codice IL. Nè aiuta usare ILDASM sull’eseguibile Project1.ni.exe, perchè l’unica cosa che si ottiene è visualizzare un manifest senza codice IL:

Come può funzionare questa piccola magia? La spiegazione è semplice: quando lanciate un qualsiasi eseguibile .NET, per prima cosa il CLR va a controllare se vi è una “native image” che corrisponde esattamente a quella applicazione, e la corrispondenza viene cercata confrontando il nome della applicazione e la sua firma (la stringona esadecimale). Se esiste una immagine nativa, quella viene lanciata al posto dell’eseguibile. La condizione però perchè tutto il codice funzioni correttamente è che i metadati coincidano anch’essi, altrimenti troppe funzioni del CLR non funzionerebbero a dovere (il garbage collector, tanto per citarne il più importante). Nel nostro caso noi abbiamo creato due eseguibili con gli stessi metadati (ecco perchè è importante che i blocchi #IF non devono eliminare alcun metodo, campo, o proprietà), e siamo riusciti a mantenere sul disco rigido la versione IL “incompleta” mentre mandiamo in esecuzione la versione nativa “completa”.
Oltre a proteggere dalla decompilazione, molto simpaticamente questo meccanismo protegge anche dalle copie illegali. Infatti, se provate a spostare tutti i file (compreso l’eseguibile Project1.ni.exe) su un’altro computer, è praticamente certo che l'applicazione NON funzionerà correttamente. Da quel che ho potuto capire, questo accade perchè il nome della directory creata da NGEN – ovvero la “firma” di un particolare eseguibile – dipende anche da qualche informazione di sistema e quindi varia da macchina a macchina.
Per quanto interessante, questa tecnica ha alcune limitazioni abbastanza serie:
A) Se si intende proteggere una DLL peer Windows Forms l'assembly non deve essere segnato con uno strong name. Il motivo è che nel manifest degli assembly che referenziano una DLL con strong-name viene memorizzato anche l'hash dell'assembly in questione, e questo è un valore che cambia quando si compila la versione copy-protected. Quindi, visto che la DLL non può avere uno strong-name, neanche gli EXE che usano la DLL possono averlo, il che rappresenta una limitazione notevole. Questo limite non esiste per le applicazioni ASP.NET.
B) Se avviene un errore in una applicazione ASP.NET protetta oppure si modifica il Web config in qualsiasi modo, l'applicazione smette di funzionare. Il problema si risolve con un IISRESET.
C) Se avviene qualche modifica sostanziale all’hardware o al software di sistema – ad es. la sostituzione della CPU, l’aggiunta di memoria, l'upgrade del sistema operativo, ecc. – il .NET Framework non può più usare l’immagine nativa e lancerà l’eseguibile normale, che ovviamente non funzionerà bene perchè alcune porzioni di codice mancano. In quel caso occorre ripetere l’installazione e tutto il procedimento descritto sopra. È importante quindi mettere in guardia il vostro cliente dai rischi che corre facendo questi upgrade durante la notte, il fine settimana, o mentre siete in vacanza alle Bahamas.
Per mitigare il problema potete almeno fare in modo che l’applicazione mostri un chiaro messaggio di errore quando l’immagine nativa non è aggiornata e il CLR torna ad usare l’eseguibile “incompleto”. Per fare questo è sufficiente avere una istruzione MessageBox.Show in un blocco #IF COPYPROTECT (senza il Not), ad es:
Friend Module Module1 Public Sub Main()
#If COPYPROTECT Then
MessageBox.Show("This app requires reinstallation. Please contact tech support.")
Exit Sub
#End If
' Here the real application begins
' ...
End Sub
End Module
Per quanto ne so, Microsoft non ha mai documentato quali modifiche al sistema disabilitano l’immagine nativa creata con NGEN, quindi non posso essere più preciso su questo punto. Se qualcuno conosce qualche articolo che spiega questi casi, lo menzioni nei commenti.
Per il resto, ho fatto un po’ di prove, sembra che tutto quanto fili liscio e che la tecnica non abbia altre controindicazioni. Se vi imbattete in qualche problema, fatemelo sapere.
di Enrico SabbadinFate un Web Service che riceve una stringa e restiuisce una stringa. Fate si che l'implementazione del web service restituisca System.Environment.NewLine (la sequenza /r/n). Fate quindi un chamante del web service e fate si che il chiamante passi anch'esso System.Environment.NewLine. Lanciate in debug e vedete cosa succede : da entrambi i lati riceventi la sequenza /r/n viene trasformata in /n .. come mai ?
Il fatto è che c'è una specifica dell'XML che dice che una tale sequenza deve essere trasformata (normalizzata) in /n (specificatamente , la sequenza di caratteri asci 13 , 10 deve essere trasformata in 10. Il responsabile della sparizione del /r è il lato ricevente: di default i web services usano un xmltextreader con la proprietà Normalization impostata a true (provate a serializzare e quindi deserializzare in maniera esplcita un oggetto avente una proprietà stringa impostata con un /r/n usando l'XMLSerializer a cui passate un xmltextreader con la proprietà Normalization impostato prima a true e poi false). La sparizione della sequenza /r/n puo' essere un bel problema per molte applicazioni.
Per fortuna esiste una soluzione : Per evitare che venga persa la sequenza nella risposta del Web Service occorre intervenire sul proxy lato client andando in override del metodo GetReaderForMessage nel modo seguente:
protected override System.Xml.XmlReader GetReaderForMessage( System.Web.Services.Protocols.SoapClientMessage message, int bufferSize) { System.Xml.XmlReader l_tmp = base.GetReaderForMessage(message, bufferSize); ((System.Xml.XmlTextReader)l_tmp).Normalization = false; return l_tmp; }
(ovviamente non fatelo sul proxy generato dinamicamente , derivate da quello) Lato server la cosa è piu' articolata : Create una classe custom che deriva da SoapServerProtocolFactory. Tale classe deve restituire un'altra vostra classe custom che deriva da SoapServerProtocol public class MyWSFactory :
System.Web.Services.Protocols.SoapServerProtocolFactory { protected override System.Web.Services.Protocols.ServerProtocol CreateIfRequestCompatible( System.Web.HttpRequest request) { if (request.PathInfo.Length > 0) { return null; } if (request.HttpMethod != "POST") { // need reflection to create the internal UnsupportedRequestProtocol class Type l_t = Type.GetType ("System.Web.Services.Protocols.UnsupportedRequestProtocol, System.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true); Object l_tmp = l_t.InvokeMember(null, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, new object[] { 0x195 }); return (System.Web.Services.Protocols.ServerProtocol)l_tmp ; // return new UnsupportedRequestProtocol(0x195); } return new MyWS(); }
Ed ecco l'implementazione della SoapServerProtocol custom in cui si puo' ottenere una referenza all'XMLTextReader utilizzato :
public class MyWS : System.Web.Services.Protocols.SoapServerProtocol { protected override System.Xml.XmlReader GetReaderForMessage( System.Web.Services.Protocols.SoapServerMessage message, int bufferSize) { System.Xml.XmlReader l_tmp = base.GetReaderForMessage(message, bufferSize); ((System.Xml.XmlTextReader)l_tmp).Normalization = false; return l_tmp; } }
Come ultimo passo dovete aggiungere nel web .config l'entrata seguente per dire ad ASP.NET di agganciare il vostro soapServerProtocolFactory ...
<configuration> <system.web> <webServices> <soapServerProtocolFactory type ="WSFactory.MyWSFactory, WSFactory" />
Poteva essere + facile .. ma per adesso le cose stanno cosi' :)
di Francesco BalenaIn genere mi piace molto la possibilità di estendere all’infinito la potenza delle mie applicazioni semplicemente aggiungendo un reference a qualche assembly che contiene le funzioni o i controlli che mi servono. Mi piace molto meno il fatto di dover distribuire numerose DLL con i miei eseguibili. In questo articolo illustrerò una tecnica per comprimere tutte (o quasi) le DLL satelliti di una applicazione Windows Forms e “fonderle” con l’eseguibile principale.
Tutti i file che vi servono sono contenuti in questo ZIP, che contiene l’utility AsmZip.exe (che si lancia dalla riga di comando) e due file sorgenti, Unzipper.cs e Unzipper.vb. Copiate l’utility AsmZip in una directory presente nel path di sistema, per poterla richiamare facilmente.
I passi da seguire Ecco i passi necessari per implementare questa tecnica.
1) Aggiungere il file Unzipper.vb o Unzipper.cs al progetto principale della vostra applicazione, rispettivamente se lavorate in Visual Basic o Visual C#.
2) Nella procedura Main, aggiungete una istruzione che inizializza la classe AssemblyUnzipper (contenuta nel file sorgente aggiunto al punto precedente): ' (Visual Basic 2005) CodeArchitects.AssemblyUnzipper.Initialize() // Visual C# 2005 CodeArchitects.AssemblyUnzipper.Initialize(); È di fondamentale importanza che questo codice sia eseguito prima di ogni altra istruzione del programma, in particolare prima di mostrare un form che contiene al suo interno dei controlli di cui non volete distribuire le DLL. Se lavorate in VB e l’applicazione ha un form di partenza (e quindi non avete a disposizione la procedura Main), dovete inserire queste istruzioni nel costruttore statico del form di partenza: Shared Sub New() CodeArchitects.AssemblyUnzipper.Initialize() End Sub
3) compilate la soluzione, ovviamente in Release mode visto che utilizzerete questa tecnica poco prima di consegnare l’eseguibile al vostro cliente.
4) aprite una finestra con il prompt dei comandi nella directory di output del programma, e lanciate l’utility AsmZip in questo modo: AsmZip main.exe *.dll dove main.exe è il nome dell’eseguibile principale. Il comando precedente comprime *tutte* le DLL nella directory e mette i dati compressi in coda al file main.exe. Per comprimere solo alcune DLL tra quelle presenti nella directory dovete specificarne i nomi, come in questo esempio: AsmZip main.exe CodeArchitects*.dll Microsoft*.dll (Ci possono essere dei buoni motivi per non comprimere alcune DLL usate dalla applicazione, come spiego più avanti.)
5) a questo punto potete cancellare tutte le DLL che avete compresso, perchè l’applicazione – grazie alla classe AssemblyUnzipper - è in grado di trovarle da sola in coda al proprio eseguibile, di decomprimerle, e di caricarle in memoria.
I vantaggi Prima di procedere ad una spiegazione più dettagliata di come funziona questa tecnica, proverò a riassumere i suoi vantaggi:
a) deployment semplificato: dovete distribuite un minor numero di file (spesso il solo EXE principale) b) applicazioni più robuste: non vi è il rischio che il cliente renda il programma inutilizzabile cancellando inavvertitamente una DLL c) minore occupazione su disco (tutte le DLL sono compresse e accodate all'EXE principale) d) la possibilità di “nascondere” i vostri piccoli segretucci, ad esempio quali controlli di terze parti avete utilizzato nella applicazione. e) un maggior grado di protezione: il codice nelle DLL compresse non può essere decompilato, perlomeno non senza avere estratto e decompresso i singoli eseguibili.
È evidente che gli ultimi due punti non costituiscono una vera barriera per un programmatore anche solo un po’ esperto, se è davvero intenzionato a sapere quali DLL avete usato e a decompilarle. Per ottenere queste informazioni un hacker dovrà soltanto decompilare l’EXE principale, capire come funziona la classe AssemblyUnzipper, e scrivere un programmino che ne ripete il funzionamento ma salva su disco gli assembly compressi. In definitiva, questa tecnica permette di nascondere il vostro sorgente e le vostre DLL alla prima occhiata, ma non potete certo considerarla una tecnica di protezione dal reverse engineering.
L’utility AsmZip usa la classe GZipStream per comprimere le DLL originarie, quindi il grado di compressione che si può raggiungere con questa tecnica non è paragonabile a quello ottenibile con WinZip o WinRar, ma è comunque più che sufficiente, come mostra la figura.

Come funziona Questa tecnica si basa sull’evento AssemblyResolve dell’oggetto AppDomain, che scatenato quando il .NET Framework deve caricare un assembly richiesto dalla applicazione. Gestendo opportunamente questo evento potete mettere in pratica tante piccole “magie” che non sarebbero possibili in alcun altro modo. Ad esempio, potreste caricare gli assembly satelliti di una applicazione da uno share di rete oppure da un campo blob di un database.
La classe AssemblyUnzipper, invece, usa questo evento per cercare l’assembly richiesto da uno stream compresso che è stato accodato all’eseguibile del programma principale. // the handler for AssemblyResolve event static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) { // find the assembly with given name, cause error if not found AssemblyInfo info = null; if ( AsmInfos.TryGetValue(e.Name, out info) ) return ExtractAssembly(info); // signal error Debug.WriteLine("Failed to uncompress assembly " + info.Name); return null; } Ciascun oggetto AssemblyInfo tiene traccia di dove, nel file EXE, è memorizzato ciascuna DLL compressa. Il dictionary AsmInfos permette di trovare subito le informazioni associate ad una DLL con un certo nome; questo dictionary è creato nel metodo Initialize, alla partenza del programma, e viene poi usato ogni volta che l’applicazione richiede una DLL.
Non spiegherò nel dettaglio come funziona il codice, perchè il sorgente è commentato in modo adeguato, sia nella versione VB che C#.
I limiti Ho provato questa tecnica con numerose applicazioni Windows Form, senza alcun problema. Il limite principale deriva dal fatto che gli assembly caricati in questo modo hanno la proprietà Location nulla, ma se non utilizzate reflection per esplorare le proprietà degli assembly non ve ne accorgerete neanche. Ad esempio, se la vostra applicazione carica gli assembly dinamicamente da una certa directory, magari per esplorarne gli attributi, è evidente che quel codice non può funzionare se le DLL sono state compresse e cancellate. In tal caso dovrete evitare di processare queste DLL con AsmZip.
La classe AssemblyUnzipper funziona solo con applicazioni Windows Forms. Da quanto ne so, è possibile utilizzare l’evento AssemblyResolve anche con applicazioni ASP.NET, ma non è possibile usare la mia classe in quel contesto. In realtà, i problemi che questa tecnica risolve non sono molto sentiti in ambito ASP.NET, quindi non credo che abbia senso preparare una versione per applicazioni di quel tipo.
L'unico altro limite è che questa tecnica funziona con le DLL, ma non con l'EXE principale. Se avete un EXE di grosse dimensioni che richiamo poche piccole DLL, il vantaggio che ne potete ricavare è molto limitato. In tal caso, potete comunque raggiungere il massimo grado di compressione "complessiva" spostando i form dall’eseguibile principale in una DLL, e comprimendo poi tale DLL con AsmZip. In teoria, l’EXE di partenza dovrebbe contenere solo lo splash screen (se ne avete uno), e poi dovrebbe caricare il form principale dalla DLL che contiene l’applicazione vera e propria. In questo modo è spesso possibile ottenere un fattore di compressione complessivo superiore al 60 per cento.
Nota: nella prima implementazione di questa tecnica ero riuscito a comprimere anche l'EXE. In tal caso accodavo poi tutti i dati compressi a un piccolo eseguibile "stub" che aveva il solo compito di fare partire l'eseguibile vero e proprio. Dopo alcune prove, però, mi sono reso conto che i problemi che si venivano a creare erano superiori ai vantaggi, per cui ho deciso di utilizzare il metodo descritto in questo articolo.
di Francesco BalenaQualche tempo fa ho registrato per Microsoft Italia alcuni brevi webcast (20 minuti ciascuno) su .NET 2.0 e Visual Studio 2005. Si tratta di sessioni introduttive, ma "succose", su feature come generics, reflection, multitasking, e regular expression. C'è anche un mini-webcast su come creare applicazioni Windows Forms estendibili mediante plug ins. Finalmente tutti i webcast sono online, a questo URL: http://www.microsoft.com/italy/msdn/risorsemsdn/visualbasic/vs2005_pillole.mspx
di Marco BellinasoIn questo periodo sono occupato quasi settimanalmente con lo Starting Innovation Tour, un ciclo di conferenze gratuite organizzate da Microsoft nelle principali città italiane per mostrare a sviluppatori e partner le principali novità di Vista e Office 2007. Dopo Catania, Napoli, Bologna, Bari e Roma, questa settimana tocca a Padova, poi Milano, Firenze e Torino. A mio parere la conferenza è davvero un'ottima occasione per chi non ha ancora visto con i propri occhi (o toccato con mano, con preferite) le nuove major release dei due prodotti principali di MS. Io mi occuperò delle sessioni su Office, ovvero:
- Sviluppo con Office 2007 Client Side: come sfruttare il nuovo formato XML dei documenti per modificare o creare nuovi documenti senza avere Office installato - da applicazioni desktop o web - nonchè una panoramica sulla creazione di add-in, Action Pane e ribbon custom.
- Windows SharePoint Services 3.0: nuove funzionalità per le liste e document library (cestino, supporto per versioning esteso, supporto RSS,...), nuovi template (blog e wiki tanto per essere moderni, ma altro ancora), utilizzo masterpage a-là ASP.NET 2.0, webpart, utilizzo workflow built-in o utilizzo del nuovo SharePoint Designer per la definizione dei propri flussi.
- Office SharePoint Portal 2007: Excel Services (il motore di calcolo di Excel direttamente sul server + rendering via browser + esposizione del motore tramite web service!), Forms Services (praticamente le form InfoPath via browser!) e Business Data Catalog (indicizzazione e visualizzzione di record presi da un qualunque database o fonte di accesso ai dati esterna)
Insomma, mi sembra ci sia un bel po' di roba da vedere Per non parlare delle bellissime e impressionanti demo grafiche fatte con WPF per Vista! Dai, ci vediamo ad una delle prossime tappe allora...
di Enrico Sabbadin A qualcuno è sfuggito questo ?
ADO.NEt VNEXT:
http://msdn.microsoft.com/data/ref/adonetnext/default.aspx
Forse finalmente un tool di O/R (object Relational mapping) fornito da MS.. dopo tante false partenze (ricordate Object Spaces) ?
Non è ben chiaro quando sara rilasciato, direi non con .NET 3.0, per quanto ho potuto capire.
Vi segnalo questi due screencast per farvi venire l'acquolina in bocca .. buona visione.
http://datajunkies.net/screencasts/adonet_vnext_part1/adonet_vnext_part1.html
di Francesco BalenaPurtroppo non riesco a stare dietro ai vari commenti a questo blog, e ogni tanto me ne perdo qualcuno davvero interessante. In particolare, non avevo letto subito quello di Pasquale Esposito a un mio post di fine Aprile. In quel post parlavo di una mia DLL per VB6 e chiudevo chiedendo ai lettori VB6 quando si sarebbero decisi a passare a .NET. Pasquale ha risposto con un commento lungo e esauriente, di cui riporto qui i pezzi salienti:
Ci chiedi quand'è che ci decidiamo a migrare a VB.NET. Vedi, io sono rimasto profondamente deluso dal passaggio al byte code operato dalla Microsoft. Da circa dieci anni produco software shareware che distribuisco su Internet o nei CD allegati a riviste di informatica (ultima pubblicazione: Soluzione Bilancio 1.0 recensito a pag. 97 di PC Magazine di maggio 2006). Ho provato a tastare il terreno distribuendo qualche piccolo applicativo in .NET e mi sono subito reso conto che i tempi non sono affatto maturi per passare a questa tecnologia, almeno per chi utilizza la metodologia shareware come canale di distribuzione. Lo affermo per almeno due motivi:
1) Non è per niente facile chiedere ad un utente che non ha il Framework installato sulla propria macchina di scaricarlo da Internet solo per far funzionare la propria applicazione: per chi non ha una connessione ADSL sognificherebbe effettuare un download di qualche ora. Inoltre, anche chi ha installato il SP2 di WinXP, non è detto che abbia scelto di includere il Framework. Conclusione: molto probabilmente, l'utente in questione sceglierà di rinunciare all'applicativo in .NET e cercherà qualcos'altro di meno problematico. In poche parole, il "DLL Hell" di VB6 ha lasciato il posto al ".NET Framework Hell" di VB.NET.
2) Programmare in byte code significa produrre software estremamente vulnerabile, non solo dal punto di vista del cracking ma perfino da quello del reverse-engineering. Basta pochissima esperienza per entrare in possesso del codice sorgente altrui ed è necessario ricorrere a buoni obfuscator di terze parti per proteggere le proprie applicazioni. Questi strumenti non soltanto sono alquanto costosi ma molto spesso fanno uso di codice nativo (anziché di byte code), rendendo la piattaforma .NET non più autosufficiente. Conclusione: non ritengo che programmare in byte code sia conveniente per chi produce shareware. Al contrario, un applicativo in VB6 può essere crackato soltanto da esperti e in nessun caso è possibile ottenere i sorgenti. Il byte code, quindi, non è adatto a fini commerciali. Molti programmatori VB6 non lo hanno mai preso in considerazione, altrimenti sarebbero passati a Java già molto tempo fa.
.....
L'unico motivo che mi potrebbe far pensare alla migrazione a VB.NET è il timore che, dopo Windows Vista, Microsoft possa abbandonare strategicamente la tecnologia COM a 32 bit. Spero davvero che ciò non accada perché ciò significherebbe costringere la gente a buttare via tutto il software attualmente in commercio. Insomma, VB6 è senz'altro lo strumento che utilizzerò nei prossimi 4-5 anni dato che i runtime di base saranno ancora presenti in Windows Vista (lo stesso Microsoft Anti-Spyware è stato progettato in VB6), dopodiché potrò considerare l'ipotesi di effettuare la migrazione.
.....
La richiesta che vorrei rivolgerti è questa: VB6 non è morto. E non è neanche obsoleto. Molti programmatori si g uadagnano da vivere con VB6 e vorrebbero che tu ti occupassi ancora di questo strumento. Come hai reso il tuo MsgHookX nuovamente disponibile online, così dovresti fare con tutta la tua produzione che riguarda VB6. Anzi, dovresti creare ancora per l'ambiente VB6. Infine, se puoi, riferisci alla Microsoft che esiste ancora un esercito di programmatori VB6 che sarebbe pronto ad acquistare una nuova versione unmanaged del suo strumento di sviluppo. Dal punto di vista commerciale, sarebbe senz'altro una mossa vincente!
Io trovo che l’opinione di Pasquale sia fondata, nel senso che se io facessi il suo lavoro (vendere shareware) potrei avere dei problemi a passare armi e bagagli a .NET. Pero’ ci sono alcune considerazioni da fare.
*) Le dimensioni del runtime: La stesso problema se lo ponevano i programmatori shareware 10 anni fa, quando confrontavano il runtime di VB6 con i piccoli eseguibili scritti in C. Ma allora c'è da chiedersi: perchè gli shareware-isti che lavorano in VB hanno avuto spesso più successo di quelli che lavorano in altri linguaggi come C ? La risposta, a mio avviso, è che scrivere un programma in VB richiedeva una frazione del tempo necessario a scriverlo in C quindi a parità di impegno è possibile creare programmi più potenti e ricchi di funzioni. (solo il Delphi può competere con VB quanto a produttività ) Forse non altrettanto veloci di quelli scritti in C, ma sufficientemente veloci per la maggior parte dei compiti.
Rapportiamo questa esperienza ad oggi: i 20M circa del framework sono circa 15 volte più grandi del runtime di VB6, pero' è anche vero che ADSL è almeno 15 volte più veloce del dialup, quindi la proporzione regge. Anzi, se i vostri clienti hanno la fibra ottica, neanche se ne accorgono. Insomma, chi tanti anni fa ha fatto la scelta di passare a VB infischiandosene delle dimensioni del runtime ha avuto ragione. Secondo me, lo stesso accade a chi oggi decide di passare a VB.NET
Certo, non tutti gli utenti hanno la ADSL, ma il ragionamento che farei io se fossi un autore shareware è: quanto mi interessano davvero questi utenti? i clienti migliori per il software e per i servizi sono le aziende e il power-user, e quelli l'ADSL ce l'hanno sempre. Se qualcuno non ha i 20€ al mese per pagare la connettività, difficilmente pagherebbe per i miei programmi. Gli unici che non rientrano in questo mio ragionamento sono le aziende e gli utenti che spenderebbero volentieri questi soldi, ma purtroppo non sono serviti da ADSL. Pero' oggettivamente si tratta di casi che diventeranno sempre più rari, e penso che in 2-3 anni saranno impossibili da trovare. (A parte il fatto che uno si puo' collegare a internet anche con una scheda UMTS...)
Allora, la domanda da porsi è: se sono un autore di software (shareware o non), vale la pena davvero continuare ad usare uno strumento che era eccezionale 10 anni fa ma adesso è decisamente superato? In questi anni io credo di avere dimostrato di saper fare davvero di tutto con il "vecchio Visual Basic", eppure oggi quando devo tornare a scrivere codice con VB6 mi sento un impedito. Non si tratta solo del linguaggio, ma anche dell'IDE e degli strumenti a corredo. Dopo pochi mesi con il VB.NET ero già molto più produttivo che in VB6 (che avevo usato per 10 anni). Oggi che conosco bene il .NET Framework credo di essere, mediamente, almeno tre volte più veloce. Ovvero scrivo un programma in un terzo del tempo che ci mettevo prima. Io vendo programmi principalmente ad aziende, ma anche se facessi shareware mi porre la stessa domanda: vale la pena rinunciare a questa enorme produttività per raggiungere qualche utente in più, che probabilmente non comprerebbe comunque il mio software?
*) Anche se il .NET Framework è ben pesante, non è strettamente necessario distribuirlo tutto con le proprie applicazioni. In teoria una applicazione WinForm ha bisogno di circa un terzo dell'intero framework. Anche se non mi sono mai interessato più di tanto alla questione, ho letto che ci sono dei programmi che sono in grado di comprire un eseguibile .NET e tutte e sole le librerie che utilizza , senza cioè richiedere una installazione completa del framework. Se le dimensioni del runtime fossero davvero un problema, proverei a fare qualche ricerca più approfondita su questi prodotti.
*) come lo stesso Pasquale fa notare, lo shareware si puo' diffondere anche con mezzi che non siano Internet, ad es. sui CD allegati alle riviste. Pero' è un dato di fatto che ci sono sempre meno riviste che allegano i CD, proprio perchè la maggior parte degli utenti ha una linea veloce e preferisce scaricare dalla Rete per essere sicuri di avere la versione più recente. Ad esempio, Microsoft Press, Mondadori e altri publisher importanti non allegano più i CD ai propri libri (a meno che il contenuto non superi i 30-40M) e loro prendono queste decisioni solo dopo analisi di mercato fatte per bene.
*) se fossi un programmatore VB6, a rendere ancora più semplice e più netta la mia decisione di passare a .NET c'è la considerazione che tutto quello che scrivo oggi con VB6 dovrà forzatamente essere buttato via tra qualche anno, vuoi perchè Microsoft non supporterà più VB, vuoi perchè i programmi e i controlli ActiveX potrebbero non funzionare bene con le prossime versioni di Windows. E' giusto fare pressione su Microsoft per evitare che cio' avvenga, ma le probabilità di fare rimangiare le loro decisioni sono prossime allo zero. Quindi è un piccolo "suicidio professionale" puntare tutto su questa speranza e nel frattempo fare finta che il mondo sia quello di dieci anni fa. Se consigliassi a qualcuno di continuare a scrivere codice VB6 sarei un vero incosciente.
*) Il discorso sul byte code: E' innegabile che un programma che possa essere decompilato facilmente pone dei seri problemi. Purtroppo .NET non offre una netta risposta a questo problema, ma solo mezze soluzioni (tipo installare sul server come servizio o sito asp.net). Quando Microsoft lanciò le prime beta di .NET cinque anni fa, questo aspetto mi sembrava davvero fondamentale, in grado addirittura di rallentare la diffusione del nuovo linguaggio.
Le cose sono andate diversamente, per fortuna. C'è da tenere presente che il problema del reverse-engineering è molto sentito da tutti i programmatori, non solo quelli che vendono shareware. Se uno mette sul mercato uno shareware e qualcuno ne fa il reverse engineering, è molto facile dimostrare che il nuovo programma è un clone. Basta decompilarli entrambi e mostrare a tutto il mondo il risultato, sottolineando che il proprio programma è stato messo sul mercato prima del clone. Certo non è efficace quanto una azione legale (che pure è possibile, ma costosa) ma nel mondo di Internet è sufficiente per perdere la reputazione. Certo, uno puo' fare il reverse engineering e poi modificare il codice, ma è facile mettere delle "trappole" in giro per il sorgente, ossia delle istruzioni che non fanno nulla e che chi ha clonato il programma non saprebbe giustificare. Per togliere queste trappole uno si deve studiare meglio tutto il sorgente, ma allora il lavoro del copiatore si complica.
A parte queste considerazioni, c'è da sottolineare il fatto che questo problema è sempre esistito nel mondo dello sviluppo software. Molti linguaggi che in passato hanno avuto successo erano dei byte-code decompilabili: ad esempio dBASE, il Clipper (spacciato come compilatore), Java, e lo stesso Visual Basic fino alla versione 4.0. E anche se il linguaggio era compilato, resta il fatto che i tracciati dei record e le tabelle dei database - ovvero un aspetto fondamentale delle applicazioni gestionali - sono sempre stati visibili e interpretabili. Se vuoi capire come funziona un gestionale, la prima cosa da fare è vedere come sono strutturate le sue tabelle del DB.
Insomma, non sto dicendo che il problema non esiste: sto dicendo che è meno serio di quello che molti programmatori credono.
Termino il post rispondendo direttamente a Pasquale: hai ragione a dire che VB6 non è morto. Ci sono moltissimi programmatori che ancora lo usano e il mio libro su VB6 continua a vendere abbastanza bene, addirittura meglio di alcuni libri su VB.NET (non dei miei libri su VB.NET, per fortuna! )
Pero' hai (molto) torto a dire che NON è obsoleto. Non si tratta di "rinnegare" il passato e soprattutto non dobbiamo parlare di VB come di un cagnolino a cui ci siamo affezionati, che non si vuole rottamare per ragioni sentimentali (ho letto anche interventi di questo tenore sui vari forum....). Quando è nato era eccezionale e per anni io ho campato (e bene!) con questo prodotto, ma se lo confronti con VB.NET (e con l'IDE di Visual Studio 2005) ti accorgi che il Visual Basic 6 è davvero morto e sepolto.
Ecco perchè non ho firmato alcuna petizione pro-VB6 e perchè sto facendo di tutto per convincere gli sviluppatori VB6 a passare il prima possibile a .NET: quando cominci a lavorare con la programmazione ad oggetti "vera", con gli attributi e reflection, i controlli di Windows Forms, le applicazioni ASP.NET e tutto il resto, il Visual Basic 6 ti sembra tanto ma tanto lontano.
NOTA: anche se non sviluppo più sotto VB6, forse cercando sul mio disco rigido troverei qualche cosa interessante che non ho ancora pubblicato. In tal caso sarà un piacere metterla a disposizione di tutti su questo sito. L'ho fatto per anni e posso continuare a farlo anche ora...
di Francesco Balena
|
Senza che me ne rendessi conto, da una decina di giorni è disponibile su Amazon, e quindi in tutte le librerie americane, il mio nuovo libro Programming Microsoft Visual C# 2005: The Base Class Library, l'ennesimo lunghissimo titolo per un libro di "sole" 600 pagine, di fatto il libro più piccolo dei sette che ho scritto per Microsoft Press.
Oltre ad essere il libro più corto è stato anche quello che ha richiesto meno tempo, in quanto i suoi 13 capitoli non sono che una "traduzione" in C# di altrettanti capitoli presi dal mio Programming Microsoft Visual Basic 2005: The Language, ovvero tutti i capitoli che hanno a che fare più con le classi del Framework che con il linguaggio C#. Ma non si tratta di una traduzione letterale, poichè sia il codice che il testo è stato rivisto a fondo per sfruttare al meglio le nuove feature di C# 2005. Ecco il sommario:
1. .NET Framework Basic Types 2. Object Lifetime 3. Interfaces 4. Generics 5. Arrays and Collections 6. Regular Expressions 7. Files, Directories, and Streams 8. Assemblies and Resources 9. Reflection 10. Custom Attributes 11. Threads 12. Object Serialization 13. PInvoke and COM Interop | Anche se il linguaggio C# non è il focus principale del libro, il capitolo 4 (oltre 40 pagine) contiene davvero tutto quello che vi serve sapere su questa nuova feature, mentre il capitolo 3 mostra come utilizzare gli iterators, e il capitolo 5 contiene numerosi esempi di anonymous methods, quindi il libro copre le tre principali novità della versione 2005 del linguaggio. Ma ovviamente la parte interessante è quella che riguarda la BCL, con numerosi esempi pratici su come utilizzare in pratica feature come reflection, regular expression, e custom attributes.
di Francesco BalenaUPDATE: dopo aver messo online questo articolo ho modificato leggermente la libreria CAPermutations, e il link ora punta alla versione aggiornata. Tutto quanto scritto nel post originario resta valido, eccetto che il terzo argomento da passare al costruttore è ora un enumerativo anzichè un booleano.
Da quando ho potuto mettere le mani su un computer sono sempre stato affascinato dai problemi combinatoriali. In prima approssimazione, un problema combinatoriale prevede la scelta di una (o più) permutazioni o combinazioni di N elementi in modo da soddisfare alcuni vincoli. E' facile intuire che molti problemi si risolvono solo grazie a tecniche combinatoriali:
- La generazione di tutti gli anagrammi di una parola richiede la generazione di tutte le permutazioni possibili dei suoi caratteri, per poi scegliere tra le parole ottenute quelle di senso compiuto
- Un programma per generare sistemi di Totocalcio, Lotto, Totip e simili richiedono la generazione di tutte le colonne possibili, da cui vengono poi scelte quelle che soddisfano alcuni requisiti (tipo numero minimo e massimo di segni, ecc.)
- Un programma per determinare l'orario scolastico, oppure più in generale, per stabilire turni di lavoro richiede di generare tutte le possibili assegnazioni (persona,turno) per poi scegliere quelle che soddisfano i vincoli impostati dall'utente
- Molti problemi matematici si basano sulla generazione di tutte le possibili permutazioni o combinazioni di N elementi, ad esempio la generazione degli quadrati magici di ordine N oppure la risoluzione del famoso problema delle regine (ovvero: come sistemare N regine su una scacchiera N*N in modo che nessuna dia scacco ad un'altra)
- Lo stesso vale per molti giochi di tipo enigmistico, come ad esempio il Sodoku oppure le parole crociate.
- Il problema dello zaino: abbiamo N oggetti di peso o ingombro differenti da trasportare in uno zaino che ha una capacità (come peso o volume) massima pari a un certo valore: quali oggetti scegliere in modo che la capacità dello zaino sia sfruttata al massimo, in modo cioè che la somma dei volumi o dei pesi degli oggetti scelti sia la più vicina possibile (ma non superiore) alla capacità dello zaino? Se pensate che invece di uno zaino avete a che fare con un container, vedete che il problema ha delle implicazioni pratiche non indifferenti
- Il modo migliore per tagliare sagome bidimensionali da una superficie piana si determina provando i diversi modi di combinare le varie sagome in modo da minimizzare il materiale non utilizzato (si pensi ad esempio al problema di tagliare in modo ottimale le pelli di una sedia o un divano a partire dal pellame a disposizione ed evitando i punti in cui il pellame non è utilizzabile o presenta imperfezioni)
- Il percorso ottimale per andare dalla località A alla località B in trenoo aereo può essere risolto combinando i vari percorsi intermedi, in modo da ridurre la distanza complessiva, il costo complessivo dei biglietti, il numero delle coincidenze aeree o ferroviarie, oppure ancora il tempo speso aspettando tali coincidenze. (Pensate ad esempio a come prenotate un viaggio con Expedia.)
- La generazione di permutazioni e combinazioni è anche alla base di numerose tecniche di "attacco" per scardinare alcuni sistemi di sicurezza. Ad esempio, i programmi che tentano di trovare le password per un sito funzionano combinando le parole in un dizionario che contiene le parole e nomi propri più comuni.
Potrei continuare con altri esempi, ma credo che sia chiaro il fatto che le tecniche combinatorie possono essere utili in moltissimi casi. Per saperne di più, non dovete fare altro che gogglare un po' su termini come "permutations" o "combinations" e scoprirete un sacco di cose interessanti. (La ricerca per "permutations" restituisce circa 10 milioni di hit, quella per "combinations" oltre 137 milioni!).
In questi anni ho scritto numerosi programmi che richiedevano la generazione di permutazioni o combinazioni di elementi simili. Forse il primo programma di questo tipo è stato un generatore di sistemi per il totocalcio che girava su Sinclair Spectrum. Uno dei più recenti è stato il Sodoku Solver che risolve automaticamente gli schemi di Sodoku più complessi in qualche frazione di secondo, direttamente dal vostro broswer. Nei vent'anni intercorsi tra questi due programmi, ho usato tecniche combinatoriali per ottimizzare la soluzione di alcuni problemi come quelli citati sopra.
Una delle cose più intriganti di un problema combinatoriale è il fatto che anche problemi abbastanza semplici richiedono spesso soluzioni complesse dal punto di vista computazionale, e soprattutto richiedono tanto tempo di CPU a meno di non ottimizzare al meglio l'algoritmo. Le possibili permutazioni di 10 oggetti distinti è pari al fattoriale di 10, ovvero 1*2*3*4*5*6*7*8*9*10 = 3.628.800. Disporre i numeri 1-16 su una griglia 4x4 per vedere quali combinazioni generano un quadrato magico (ossia un quadrato in cui la somma lungo le righe, colonne, e diagonali è costante) richiede l'analisi di 20mila miliardi di posizioni, e cosi' via. Per riuscire a risolvere questi problemi in tempi accettabili si richiede spesso di ottimizzare il codice al massimo e di trovare delle tecniche di "potatura" in modo da poter scartare interi gruppi di soluzioni prima di doverle analizzare una a una. Anche in questo modo, la risoluzione di un quadrato magico di ordine 5 può richiedere alcuni giorni anche sulle CPU più performanti. Per avere una idea di quante varianti vi siano per questi problemi combinatoriali, date una occhiata al sito The Combinatorial Object Server.
Ogni problema combinatoriale è una storia a sè stante, nel senso che il codice per trovare le soluzioni di uno schema di Sodoku è molto diverso dal codice per generare l'orario scolastico ottimale o per trovare il tragitto aereo migliore. D'altra parte, queste applicazioni hanno anche molti tratti in comune. In particolare, la parte che genera le varie permutazioni o combinazioni di elementi è spesso simile. Non uguale, ma simile. Quello che cambia in ciascun caso sono le routine che controllano se una combinazione è valida o meno, oppure le funzioni che procedono alla "potatura" degli alberi delle soluzioni. Alla fine ho pensato: perchè non estrapolare l'algoritmo di generazione delle combinazioni in modo da poterlo riutilizzare in occasioni differenti? Il risultato è la libreria CAPermutations, ovvero l'oggetto di questo articolo e di alcuni altri post che seguiranno.
La libreria in questione contiene solo una classe, Permutations, che però già in questa prima versione è in grado di risolvere la maggior parte dei problemi citati in precedenza. Per i problemi non particolarmente complessi, utilizzare questa libreria è davvero semplice: si crea una istanza della classe e si passa al costruttore un vettore contenente tutti gli elementi da permutare, poi si entra in un ciclo For Each che permette di enumerare tutte le possibili permutazioni. La classe Permutations usa i generics, in modo da accettare e restituire un array tipizzato contenente gli elementi che devono essere (o che sono stati) combinati:
' generate all the permutation of the characters A,B,C,D Dim elements() As Char = {"A"c, "B"c, "C"c, "D"c} Dim perms As New Permutations(Of Char)(elements) For Each chars() As Char In perms ' create and display the string obtained by concatenating the characters in the result Console.Write(New String(chars) & ", ") Next
Il codice C# è altrettanto semplice:
// generate all the permutation of the characters A,B,C,D char elements[] = {"A"c, "B"c, "C"c, "D"c}; Permutations<Char> permutations = New Permutations<Char>(elements); foreach ( char[] chars in perms ) { // create and display the string obtained by concatenating the characters in the result Console.Write(new string(chars) + ", "); }
Ecco il risultato che appare nella finestra di console, ovvero tutte le possibili permutazioni degli N elementi forniti in input:
ABCD, ABDC, ACBD, ACDB, ADBC, ADCB, BACD, BADC, BCAD, BCDA, BDAC, BDCA, CABD, CADB, CBAD, CBDA, CDAB, CDBA, DABC, DACB, DBAC, DBCA, DCAB, DCBA,
Invece di un loop For Each potete anche usare il metodo GetAllPermutations, che restituisce in un colpo solo tutte le pemutazioni. Poichè ogni permutazione è un vettore di tipo T (dove T è definito al momento di istanziare la classe generica Permutations), allora il risultato di questo metodo è un jagged array di tipo T, ovvero un vettore dove ciascun elemento è a sua volta un vettore di tipo T.
' VB Dim results()() As Char = perms.GetAllPermutations()
// C# char[][] results = perms.GetAllPermutations();
La classe Permutations è anche in grado di determinare le permutazioni di un gruppo di K elementi presi dall'insieme di N elementi forniti in input, con K <= N . Per ottenere tali permutazioni basta passare al costruttore un secondo argomento, pari al numero K di elementi che devono apparire nel risultato:
' generate all the permutation of two characters chosen from the charaters A,B,C,D Dim elements() As Char = {"A"c, "B"c, "C"c, "D"c} Dim perms As New Permutations(Of Char)(elements, 2) ' .... for each loop as before
Ecco il risultato generato dal ciclo:
AB, AC, AD, BA, BC, BD, CA, CB, CD, DA, DB, DC,
La classe Permutations permette di risolvere una numerosa classe di problemi combinatori e statistici. Ad esempio, se A,B,C,D rappresentano 4 città, l'insieme delle permutazioni rappresentano tutti i possibili percorsi che uniscono le città e che non ripassano mai da una città: un programma può facilmente analizzare questi percorsi per trovare quello che richiede meno tempo o ha un costo minore. Se invece gli elementi rappresentano possibili azioni - ad esempio, l'azione di sistemare una pedina su una determinata casella delle scacchiera - allora l'insieme delle permutazioni permette di stabilire quale sequenza di azioni tra quelle possibili permettono di ottenere il risultato migliore (ad esempio, vincere una partita di tris). E' importante notare che il risultato non conterrà due elementi uguali, il che è corretto perchè non vogliamo visitare due volte la stessa città e non possiamo sistemare due pedine nella stessa casella.
La classe Permutations permette anche di generare le combinazioni di K oggetti presi da un universo di N oggetti, come al solito con K <= N. L'unica differenza tra permutazioni e combinazioni è che con queste ultime l'ordine è ininfluente, quindi ad esempio la soluzione ABC è considerata equivalente a ACB, BAC, BCA, CAB, e CBA perchè quello che conta sono gli elementi che compaiono nel risultato e non il loro ordine. Le combinazioni permettono di risolvere tipi di problemi differenti da quelli visti finora. Per esempio, le combinazioni degli elementi {Giuseppe,Francesco,Marco,Piero,Gianni,MariaTeresa} con K=2 potrebbero servire per generare il calendario della prima serie di partite di un torneo di tennis a cui partecipano sei membri del team di Code Architects, in modo che ciascuna persona si batta una volta contro tutte le altre. In questo caso occorre usare le combinazioni perchè non occorre disputare la partita Francesco-Giuseppe se si è già giocata la partita Giuseppe-Francesco. (In altre parole, l'ordine degli elementi è ininfluente.)
Per generare combinazioni anzichè permutazioni, è sufficiente passare un valore enumerativo PermutationKind come terzo argomento al costruttore della classe Permutations. Ecco ad esempio come generare le partite del torneo:
Dim elements() As String = {"Giuseppe", "Francesco", "Marco", "Piero", "Gianni", "MariaTeresa"} Dim perms As New Permutations(Of String)(elements, 2, PermutationKind.Combinatio ) ' anzichè PermutationKind.Permutations For Each elem() As String In perms ' each element in the result is an array with two elements Console.WriteLine("{0} - {1}", elem(0), elem(1)) Next
Ed ecco il risultato che appare nella console window.
Giuseppe - Francesco Giuseppe - Marco Giuseppe - Piero Giuseppe - Gianni Giuseppe - MariaTeresa Francesco - Marco Francesco - Piero Francesco - Gianni Francesco - MariaTeresa Marco - Piero Marco - Gianni Marco - MariaTeresa Piero - Gianni Piero - MariaTeresa Gianni - MariaTeresa
Fin quì niente di particolarmente eccitante, visto che è facile ottenere lo stesso risultato con qualche ciclo innestato di C# o VB, soprattutto se i valori di K e N sono costanti. Con K o N variabili occorre prevedere degli array che gestiscono gli indici dei vari loop, ma è un codice alla portata di tutti.
Le cose cominciano a diventare interessanti quando l'insieme degli elementi contiene elementi uguali, che non possono quindi essere considerati distinti quando si generano le permutazioni o le combinazioni. L'esempio classico è la generazione di anagrammi: il numero totale di anagrammi della parola "case" è 23, dato dal numero di pemutazioni possibili di 4 lettere (=1*2*3*4) meno uno per evitare di conteggiare la parola originale. D'altra parte, il numero di anagrammi della parola "casa" è soltanto 11, perchè la parola originale contiene due lettere uguali e quindi il numero di permutazioni distinte che è possibile creare è pari a 12, non 24. Questo particolare inizia a complicare non poco la struttura di un programma C# o VB che risolve il problema specifico, ma la classe Permutations rimuove automaticamente le ripetizioni dal risultato:

Ecco il codice nell'evento Click nel pulsante "Show anagrams" del programma demo:
' prepare to generate all permutations of all the characters in the word Dim chars() As Char = txtWords.Text.ToCharArray() Dim permutations As New Permutations(Of Char)(chars) lstAnagrams.Items.Clear() For Each chrs() As Char In permutations Dim anagram As String = New String(chrs) ' don't include the original word in the result If anagram <> txtWords.Text Then lstAnagrams.Items.Add(anagram) Next lblMessage.Text = String.Format("Found {0} anagrams", lstAnagrams.Items.Count)
Come ho fatto notare prima, nella maggior parte dei casi quando chiediamo le permutazioni o combinazioni di un gruppo di elementi non vogliamo che lo stesso elemento compaia più volte. Ad esempio, se un elemento rappresenta la posizione di una regina posta sulla scacchiera, l'elemento non puo' comparire più di una volta perchè una casella non puo' contenere più pezzi. Altri problemi combinatori però non hanno questa limitazione. Se ad esempio stiamo calcolando tutte le possibili permutazioni di due dadi, dovremo includere anche i risultati in cui vi sia un doppio uno, un doppio due, ecc. Per ottenere questo risultato occorre specificare un valore maggiore di 1 come quarto argomento del costruttore della classe Permutations:
' get all the possible permutations of two dice Dim elements() As Integer = {1, 2, 3, 4, 5, 6} ' we pass 2 as fourth argument because we accept that the same value can appear twice Dim perms As New Permutations(Of Integer)(elements, 2, PermutationKind.Permutations , 2) For Each dice() As Integer In perms Console.WriteLine("{0} {1}", dice(0), dice(1)) Next
In una prima versione della libreria avevo usato un booleano per indicare se le ripetizioni erano ammesse o meno, ma usare un intero ha il vantaggio di permettere una maggiore flessibilità. Ad esempio, è possibile generare tutte le permutazioni di tre dadi in cui lo stesso valore compare al massimo due volte:
Dim perms As New Permutations(Of Integer)(elements, 3, PermutationKind.Permutations, 2)
Queste prime note dpvrebbero essere sufficienti per cominciare a utilizzare la libreria CAPermutations, ma le sue possibilità sono di gran lunga superiori e fornirò qualche altro esempio nei prossimi giorni, man mano che ne creo. Nel frattempo, potete scaricare la libreria e il piccolo programma demo visibile nella figura da questo link: PermutationsDemo1.zip (23.15 KB)
Una ultima, importante nota: questa versione della libreria CAPermutations è disponibile come DLL compilata e può essere usata esclusivamente nei programmi freeware o no-profit. Se avete dubbi su questa forma di licenza o se avete la necessità di utilizzarla in applicazioni commerciali, contattatemi via email.
di Marco BellinasoPer il progetto sul quale stò lavorando c'era la necessità di produrre un PDF sulla base di un report HTML abbastanza complesso, con griglie, immagini, CSS interni all'HTML stesso o in file esterni linkati. Abbiamo provato per giorni ad usare un certo prodotto commerciale piuttosto famoso che fa tutto ciò, ma richiedendo di creare account Windows particolari, configurare in modo appropriato addirittura i browser che stà sul server, creare varie cartelle, configurare l'apertura di porte particolari ecc. ecc. Insomma, bisogna dedicare un server a questo tool, e anche così bisogna perdere del tempo per sistemare il tutto. Stanchi di queste difficoltà per il test e il deployment, ieri mi sono messo a cercare un'alternativa. Risultato :ABCpdf! Questo componente fa davvero tutto quello che ci serve, in modo ottimale a quanto sembra per ora, e la sua installazione e utilizzo sono di una semplicità disarmante: basti dire che in poco più di un'ora ho scaricato e installato il prodotto, mi sono visto un paio di loro esempi, ho riscritto il servizio di generazione di PDF e ho testato che funzionava bene... Insomma, forse l'altro componente sarà anche in grado di svolgere compiti più complessi (fa da vero e proprio server PDF, mettendo in coda le richieste e smaltendole in modo asincrono), ma se non avete necessità di gestire carichi davvero enormi, e non avete un server da dedicarci, ABCpdf è molto veloce e può fare al caso vostro. Tra l'altro, il componente è gratuito se mettete sul sito un link al produttore (altrimenti c'è una licenza di prova di 30 giorni oltre i quali si dovrà comprare la licenza commerciale)!
Colgo l'occasione di questo post per segnalare anche che nel blog in inglese ho appena pubblicato un nuovo estratto dal mio libro, che mette a confronto le varie tecnologie MS per gestire le transazioni distribuite e non all'interno di applicazioni .NET: transazioni ADO.NET, COM+/SWC e System.Transactions. Il tutto in modo piuttosto conciso (del resto è solo una delle tante cose che introduco nel capitolo 3, riguardanti i fondamenti della progettazione del sito), non è certamente intesa come una trattazione esausitva ma credo possa dare velocemente un'idea delle varie possibilità a chi ancora non ci ha avuto a che fare.
di Enrico SabbadinHo ripreso una macro che avevo scritto qualche anno fa: Essa impostava l'ordine di build dei progetti nella solution in base alle referenze tra i vari progetti.
VS.NET fa in automatico una cosa del genere quanod le referenze sono di tipo progetto, ma questo è un problema quando si devono gestire delle soluzioni che hanno un grado variabilità notevole in termini di progetti che la compongono.
Questo addin fa lo stesso mestiere per le referenze di tipo assembly... La routine è stata aggiornata alla versione 2005 per gestire i progetti di tipo ASP.NET / WEB Service che rappresntano un tipo specifico di progetto.
Option Strict On Imports System Imports System.Windows.Forms Imports EnvDTE Imports EnvDTE80 Imports System.Diagnostics
Public Module CalcDepends
Public Function GetBuildOutPutWindow() As OutputWindowPane Dim win As Window = DTE.Windows.Item( _ EnvDTE.Constants.vsWindowKindOutput) Return CType(win.Object, OutputWindow).OutputWindowPanes.Item("Build") End Function
Public Const c_prjtypeVBNET As String = "VBPROJ" Public Const c_prjtypeCS As String = "CSPROJ"
Public Enum projectType VBNET CSHARP UNKWOWN End Enum Public Function getProjectType(ByVal p_prj As Project) As projectType Dim l_prjtype As String = p_prj.FullName.Substring(p_prj.FullName.Length - 6).ToUpper If l_prjtype = c_prjtypeCS Then Return projectType.CSHARP ElseIf l_prjtype = c_prjtypeVBNET Then Return projectType.VBNET Else Return projectType.UNKWOWN End If
End Function
Public Sub CalcBuildDepends() Try GetBuildOutPutWindow.OutputString("++++++++++++++ ENTERING CalcBuildDepends ++++++++++++++" & vbCrLf) Dim l_bd As BuildDependency For Each l_bd In _ DTE.Solution.SolutionBuild.BuildDependencies l_bd.RemoveAllProjects() Next
For Each l_prac As EnvDTE.Project In _ DTE.Solution.Projects If TypeOf l_prac.Object Is VSLangProj.VSProject Then If getProjectType(l_prac) = projectType.CSHARP Or getProjectType(l_prac) = projectType.VBNET Then Dim l_ActVSProject As VSLangProj.VSProject = CType(l_prac.Object, VSLangProj.VSProject) ' I get the Assembly Name of the Current Project For Each ref As VSLangProj.Reference In l_ActVSProject.References For Each l_pr As EnvDTE.Project In DTE.Solution.Projects ' looking for projects in the solution that are in the current project references list If TypeOf l_pr.Object Is VSLangProj.VSProject Then If getProjectType(l_pr) = projectType.CSHARP Or getProjectType(l_pr) = projectType.VBNET Then Dim l_VSProject As VSLangProj.VSProject = CType(l_pr.Object, VSLangProj.VSProject) 'GetBuildOutPutWindow.OutputString("Checking if " _ ' & l_ActVSProject.Project.Name _ ' & " references " _ ' & l_pr.Properties.Item("AssemblyName").Value.ToString & vbCrLf)
If (l_pr.Properties.Item("AssemblyName").Value.ToString = ref.Name) Then Try DTE.Solution.SolutionBuild.BuildDependencies.Item( _ l_ActVSProject.Project).AddProject(l_pr.UniqueName) GetBuildOutPutWindow.OutputString("**** Adding Reference " & l_pr.Name & " to " & l_prac.Name + " *****" & vbCrLf) System.Windows.Forms.Application.DoEvents() Catch ex As System.Exception MessageBox.Show("Error adding dependency " + l_pr.UniqueName + _ " to " + l_ActVSProject.Project.Name + " Error is:" + _ ex.Message) End Try End If End If End If Next Next End If ElseIf TypeOf l_prac.Object Is VsWebSite.VSWebSite Then Dim l_ActVSProjectWS As VsWebSite.VSWebSite = CType(l_prac.Object, VsWebSite.VSWebSite) For Each ref As VsWebSite.AssemblyReference In l_ActVSProjectWS.References For Each l_pr As EnvDTE.Project In DTE.Solution.Projects If TypeOf l_pr.Object Is VSLangProj.VSProject Then If getProjectType(l_pr) = projectType.CSHARP Or getProjectType(l_pr) = projectType.VBNET Then Dim l_VSProject As VSLangProj.VSProject = CType(l_pr.Object, VSLangProj.VSProject) 'GetBuildOutPutWindow.OutputString("Checking if " _ ' & l_ActVSProject.Project.Name _ ' & " references " _ ' & l_pr.Properties.Item("AssemblyName").Value.ToString & vbCrLf)
If (l_pr.Properties.Item("AssemblyName").Value.ToString = ref.Name) Then Try DTE.Solution.SolutionBuild.BuildDependencies.Item( _ l_ActVSProjectWS.Project).AddProject(l_pr.UniqueName) GetBuildOutPutWindow.OutputString("**** Adding Reference " & l_pr.Name & " to " & l_prac.Name + " *****" & vbCrLf) System.Windows.Forms.Application.DoEvents() Catch ex As System.Exception MessageBox.Show("Error adding dependency " + l_pr.UniqueName + _ " to " + l_ActVSProjectWS.Project.Name + " Error is:" + _ ex.Message) End Try End If End If End If Next Next End If Next GetBuildOutPutWindow.OutputString("++++++++++++++ EXITING CalcBuildDepends ++++++++++++++" & vbCrLf) 'MessageBox.Show("Recalculation Done") Catch ex As System.Exception MessageBox.Show("Error in seek:" + ex.Message) End Try End Sub
End Module
di Enrico SabbadinMentre cominiciavo a prendere confidenza con (W)WF, cioè Windows WorkFlow Foundation, mi sono scontrato con un baco della versine corrente (in beta), per cui non sono sollevati gli eventi di passaggio di stato di un'Activity.
Per girare attorno al problema avevo la necessità di far si che la mia classe che deriva da SequenceActivity andasse in override del metodo OnEvent.
Qui sorge però il problema: tale metodo non è overridable: esso è definito in un'interfaccia implementata dalla classe base in maniera esplicita.
Se vengo a reimplementare l'interfaccia esplicitamente l compilatore C# non da errore ed il runtime del WF chiama la mia implementazione .. ma come faccio a chiamare l'implementazione esplicita della classe base, cioè come faccio a fare logicamente base.OnEvent ?
Dopo un breve consultazione con Francesco, siamo giunti alla conclusione che pare non esista nessuna keyowrd o sintassi del linguaggio che possa aiutarci. Occorre lavorare di reflection come mostrato nel codice sottostante.
Si noti che si chiama GetMethod su BaseType this.GetType().BaseType
// My Interface explicit re-implementation void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent( object sender, ActivityExecutionStatusChangedEventArgs e) {
// I do my work here ...
// the following code is logically like calling base.OnEvent string methodName = "System.Workflow.ComponentModel.IActivityEventListener<System.Workflow.ComponentModel.ActivityExecutionStatusChangedEventArgs>.OnEvent"; MethodInfo myMethod = this.GetType().BaseType.GetMethod(methodName,BindingFlags.Instance | BindingFlags.NonPublic); myMethod.Invoke(this, new object[] { sender, e }); } Notate che per ragioni di performance, se occorre chiamare il metodo più di una volta, è opportuno acquisire una referenza a myMethod una sola volta e poi tenere tale referenza in cache per i successivi riutilizzi.
di Giuseppe Dimauro

Ciao a tutti, vi riporto di seguito il calendario ufficiale dei prossimi webcast architetturali tenuti da Maurizio, Pierre, me ed altri. Il primo appuntamento e' domani. Illustrero' le novita' della rinnovata versione dell'Enterprise Library di Patterns & Practices per poi proseguire fino a giugno con una nutrita serie di argomenti e approfondimenti su tecnologie reali.
Calendario dei prossimi Architect Webcast
§Febbraio
•7/2: Pattern architetturali per la realizzazione di applicazioni e servizi - Parte I
•14/2: Pattern architetturali per la realizzazione di applicazioni e servizi - Parte II
•21/2: Introduzione alla metodologia agile MSF 4.0 con Visual Studio 2005 Team System
•28/2: Progettare il Web Testing nel mondo Enterprise con Visual Studio 2005 Team Test
§Marzo
•07/3: BizTalk Server 2006: uno strumento per tutta l'azienda
•14/3: BizTalk Server 2006: mille e uno usi di uno strumento versatile
•21/3: BizTalk Server 2006 e lo sviluppo di applicazioni orientate ai servizi
•28/3: WinFX: Windows Workflow Foundation - Parte I
§Aprile
•04/4: WinFX: Windows Workflow Foundation - Parte II
•11/4: Realizzare servizi distribuiti con Windows Communication Foundation - Parte I
•19/4: Realizzare servizi distribuiti con Windows Communication Foundation - Parte II
•27/4: Architettura SOA. Perché non se ne può fare a meno?
§Maggio
•09/5: Le applicazioni client negli scenari d'integrazione - Parte I
•16/5: Le applicazioni client negli scenari d'integrazione - Parte II
•23/5: Interoperabilità e migrazione tra .NET e COM
§Giugno
•06/6: Smart Client. Unire il meglio di idee e tecnologie diverse
•13/6: Il dato al centro dell'informazione aziendale. Come gestirlo
•20/6: Snellire i processi aziendali gestendo il flusso di informazioni con Office
•27/6: Smart Document: la nuova faccia del documento

di Francesco BalenaHo già parlato in questo blog di CodeWall.NET, un tool per la protezione degli eseguibili .NET 2.0 dalla decompilazione, a cui io e Vito Plantamura stiamo lavorando da un paio di mesi. Finalmente abbiamo una beta robusta e funzionante, e non vediamo l'ora di metterla online.
Date le finalità del prodotto, non è un mistero il fatto che abbiamo passato la maggior parte del tempo per implementare tutte le tecniche "anti-sprotezione" di nostra conoscenza, incluso alcune davvero esoteriche. E' un vero peccato non poterle descrivere nei dettagli, per motivi che dovrebbero essere evidenti. Comunque, qualche cosa la posso comunque raccontare.
Uno dei punti deboli di praticamente tutti i meccanismi di protezione di questo tipo è che, se un "cracker" trova il modo per sproteggere UN SOLO eseguibile protetto, egli è in grado di scrivere uno script o un tool in grado di sproteggere TUTTI gli eseguibili protetti, la qual cosa sarebbe ovviamente un vero disastro per noi e per i nostri clienti. Per ovviare a questo serissimo problema, ogni copia di CodeWall.NET è unica. Non solo utilizza una chiave differente, ma contiene anche un algoritmo di decifratura leggermente differente. Questo accorginmento rende l'attacco mediante script virtualmente impossibile.
Ecco un'altra tecnica che abbiamo adottato per rendere la protezione più robusta. Le applicazioni protette con CodeWall.NET possono contenere delle stringhe criptate, che saranno riportate in chiaro solo in fase di esecuzione, e solo se l'assembly "gira" sotto CodeWall.NET. Questo significa che se anche qualcuno riuscisse a estrarre un assembly dall'eseguibile protetto, dovrebbe capire come funziona il decrypt delle stringhe e poi decrittare le stringhe una a una. Attenzione, perchè non solo è possibile criptare le stringhe in questo modo: è anche possibile criptare nomi di classi e di metodi, quindi se queste stringhe non sono decrittate correttamente il programma non funzionerebbe affatto.
Ovviamente, il loader di CodeWall.NET è scritto in codice unmanaged, in modo da NON essere decompilabile con Reflector, Anakrino, o ILDASM. Un malintenzionato particolarmente motivato potrebbe usare un disassembler "classico", ma poi gli toccherebbe decifrare migliaia di opcode assembly "nativo", il che non è proprio alla portata di tutti. Purtroppo, pero', l'uso del codice unamanged limita l'utilizzo di CodeWall.NET ai soli eseguibili Win32 (Windows Forms e Console) che girano in modalità full-trust. Non possiamo quindi supportare appliczioni ASP.NET e neanche applicazioni ClickOnce, a meno che questi ultimi non girino appunto in full-trust mode.
Non abbiamo ancora stabilito il prezzo di listino di CodeWall.NET, ma prevediamo che si aggirerà sui 250 dollari (circa 200 euro) e quasi certamente ci sarà uno sconto per il periodo del lancio. A breve speriamo di mettere una versione "trial" disponibile sul sito. Nel frattempo, se avete domande lasciate pure un commento.
di Francesco BalenaOggi mi sono imbattuto in un nuovo attributo del .NET Framework che mi era sfuggito durante le mie precedenti esplorazioni. Si tratta di SuppressIldasmAttribute, che si può applicare a livello di assembly
<Assembly: System.Runtime.CompilerServices.SuppressIldasm()>
Come il suo nome lascia intendere, questo attributo impedisce di usare ILDASM per disassemblare l'eseguibile. Se provate a farlo, vi beccate infatti il seguente messaggio di errore:

Eccezionale! Proprio quello che ci voleva per proteggere i nostri sorgente da occhi indiscreti, giusto? Peccato che questo attributo è riconosciuto solo da ILDASM e non da Reflector e simili decompilatori, che invece riescono a tradurre anche questi eseguibili in sorgente, senza batter ciglio. Insomma un attributo molto promettente ma assolutamente inutile. Morale della storia: per proteggere i frutti del vostro lavoro, usate un obfuscator o - meglio ancora - aspettate anche qualche settimana per il nostro CodeWall.NET.
di Francesco Balena
 |
Nel mio precedente post annunciavo il lancio ufficiale di dotnet2themax.com, la versione in lingua inglese di questo sito e spiegavo che in linea di massima i due siti avrebbero ospitato molti articoli ma non sarebbero stati uno la traduzione dell'altro. In particolare, il sito punto-com ospiterà la home page dei nostri libri e dei nostri prodotti.
Per inaugurare in bellezza, la prima cosa che ho messo online è la lista di 700+ best practice per applicazioni .NET. Si tratta di un documento Word di oltre 30 pagine che riassume di tutte le regole contenuto nel libro cho ho scritto insieme a Giuseppe Dimauro, ovvero Practical Guidelines and Best Practices for Microsoft Visual Basic .NET and Visual C# Developers. Il documento è formattato come una checklist, in modo da poter "spuntare" i vari elementi quando si esegue il code review di un progetto particolarmente complesso. Noi lo usiamo regolarmente per i progetti interni e spero che lo troviate anche voi utile. Se poi lo troverete davvero utile, potete sempre comprare il libro per trovare esempi e tutto il resto. 
Oltre alla lista delle guideline, sul nuovo sito americano è disponibile il codice sorgente degli esempi del libro e ben tre capitoli del libro: il capitolo 6, "Types", il capitolo 23, "Memory Usage", e il capitolo 31, "Serviced Components." I capitoli sono in formato PDF e pronti ad essere stampati. E' la prima volta che questo materiale è disponibile online.
Sul sito sono disponibili altri capitoli mai apparsi prima online, tratti dal mio Programming Microsoft Visual Basic .NET 2003, ovvero il capitolo 18, "Windows Forms Custom Controls.", il capitolo 12, "Regular Expressions", e il capitolo 21, "ADO.NET in Disconnected Mode" che era già disponibile sul sito MSPress. Si tratta di oltre 100 pagine di reference che continua ad essere valido anche per VB2005 e .NET 2.0, in particolare nel caso delle regular expression.
NOTA: Per accedere a questi contenuti occorre registrarsi, con nome e email, e iscriversi alla newsletter del sito. Se poi volete davvero farci un piccolo regalo, dedicato un paio di minuti per rispondere alle domande del nostro questionario, che ci permette di capire davvero qual è il nostro pubblico. Noi in compenso vi promettiamo solennemente (e ovviamente) che non faremo mai spamming e che useremo la newsletter (che ha una cadenza mensile) solo per comunicare nuove iniziative e prodotti che vi possono davvero interessare. |
di Francesco BalenaAi primi anni '90 avevo una piccola software house tutta mia, di nome Softwhale (chi è bravo con l'inglese apprezzerà il gioco di parole, spero ). Già da allora il mio pallino era scrivere tool per sviluppatori, ed avevo realizzato alcuni prodotti niente affatto male, tra cui dSwapper (una utility che abbatteva il limite dei 640K per i programmi Ms-Dos), Batch Wizard (un compilatore per file batch, che molte software house italiane hanno usato per creare procedure di installazione intelligenti), e QBKIT (una libreria di 700+ nuove funzioni per QuickBasic, incluso menu e finestre 3D). Ma uno dei maggiori successi di quegli anni fu NOWAY , un piccolo tool scritto in Assembly - come la gran parte del software che scrivevo allora - in grado di crittografare gli eseguibili Ms-Dos per evitarne il reverse engineering. All'epoca, infatti, la maggior parte dei gestionali erano scritti in Clipper, e vi erano sul mercato un paio di decompilatori in grado di ricostruire il sorgente originale, con tanti saluti alla proprietà intellettuale. L'altra caratteristica notevole di Noway era la possibilità di "legare" un eseguibile ad una macchina, in modo da proteggere dalla diffusione di copie pirata.
Come vedete, dopo una decina di anni il mondo del software ha fatto passi da gigante, ma alcune cose non sono cambiate. Anche oggi i linguaggi .NET permettono una produttività incredibile - come e più del Clipper negli anni '90 - e soffrono del medesimo difetto: chiunque può decompilare gli assembly .NET con strumenti come Reflector, e scoprire in pochi minuti un algoritmo che vi ha impegnato per settimane o mesi. Non è un caso che alcune software house hanno deciso di rinunciare ai benefici del .NET Framework pur di evitare che ciò accada.
Un paio di mesi fa stavo facendo qualche esperimento con il .NET Framework 2.0 e ho avuto una piccola illuminazione, ovvero una tecnica che permette di crittografare un assembly e di riportarlo in chiaro "al volo", al momento di caricarlo in memoria. Non intendo dilungarmi sui dettagli di questa tecnica, ma l'effetto è particolarmente interessante: poichè l'assembly su disco è sempre in forma cifrata, non è possibile decompilarlo in alcun modo, neanche con ILDASM. Insomma, mi sono detto, questo potrebbe diventare il Noway per .NET! Ecco la storia che si ripete 
Ovviamente, tra una idea e un prodotto finito passa del tempo. Prima di tutto occorreva risolvere alcuni punti potenzialmente deboli del meccanismo. Ad esempio, un hacker abbastanza in gamba potrebbe eseguire il trace del programma per capire cosa succede al caricamento, oppure potrebbe aspettare che l'assembly sia caricato "in chiaro" in memoria e salvarlo facendo un dump della memoria stessa. Sono tecniche forse fuori dalla portata di un programmatore .NET "medio" ma non di un hacker che si rispetti.
Io non sono un hacker e soprattutto non ho le conoscenze che servono per "blindare" un programma di questo tipo, quindi la cosa più saggia da fare era rivolgersi a qualcuno che queste cose le sa fare. E non credo di conoscere nessuno più indicato di Vito Plantamura, ovvero quello che io considero il "Mark Russinovich italiano". Vito non è un hacker nel senso deteriore del termine, ma conosce i meandri di Windows meglio di qualunque altro programmatore italiano io abbia conosciuto. Se volete avere una idea di cosa è in grado di fare, date una occhiata alla sua home page. Tra le tante cose che ha fatto, c'è anche BugChecker, un "clone" nientedimeno di SoftICE, un tool che fa sembrare un giocattolo il debugger di Visual Studio. Vito è nel team di Code Architects e tra non molto potrete leggerlo su questo blog o altrove su questo sito.
Comunque, in questi mesi le ricerche e gli esperimenti sono continuate ed ora siamo vicini alla fase beta vera e propria. Qualche ora fa abbiamo "battezzato" il nuovo prodotto come CodeWall.NET, seguendo il suggerimento di un gentilissimo partecipante alla WPC (dove avevamo mostrato la prima versione in anteprima). La versione 1.0 supporta soltanto applicazioni Windows Forms ed è in grado di comprimere tutti gli assembly di una solution in un unico file compresso, che quindi si puo' scaricare più velocemente. L'applicativo deve girare in modalità full-trust e quindi al momento non supportiamo ClickOnce. Se tutto va bene il prodotto finale dovrebbe essere disponibile verso i primi di gennaio.
Devo essere sincero: abbiamo rimuginato per mesi prima di decidere di rilasciare un prodotto per la protezione degli assembly .NET. Chiunque nel settore informatico sa perfettamente che non esiste la protezione inviolabile. Per quanto sia possibile blindare un eseguibile è sempre possibile arrivare a scardinare la protezione. E' una questione di abilità dell'hacker e di tempo necessario. Oppure, se si preferisce, di quanti soldi una azienda è disposta a spendere per appropriarsi dei segreti dei propri concorrenti, assoldando se necessario qualche ragazzino particolarmente versato per queste attività.
Se è vero che qualsiasi eseguibile può essere sprotetto - disponendo di tempo e denaro in abbondanza - ciò non significa che non dobbiamo neanche tentare di rendere la vita più complicata a chi vuole rubare il nostro lavoro. E' un po' lo stesso ragionamento che facciamo quando chiudiamo a chiave casa prima di uscire: sappiamo perfettamente che un malintenzionato davvero motivato - disponendo di tempo e della attrezzatura giusta - è in grado di fare saltare qualsiasi porta blindata, ma sappiamo anche che la nostra serratura potrebbe scoraggiarlo. E magari speriamo inconsciamente che il malintenzionato sposti la sua attenzione su qualche obiettivo più accessibile.
Il compito di strumenti come CodeWall.NET è di alzare la security bar tra il nostro codice e l'hacker di turno, o il cracker come sarebbe più corretto chiamare questi individui. Noi crediamo che con tutte le tecniche che abbiamo implementato - e che non possiamo discutere pubblicamente per ovvii motivi - renderemo la vita molto, ma molto difficile a questi figuri. Abbiamo passato ore e giorni a discutere su come il programma potrebbe essere attaccato e quindi siamo passati a realizzare una contromisura adeguata. In teoria anche gli assembly protetti con CodeWall.NET sono sproteggibili, ma in pratica servirà qualcuno molto bravo e con un bel po' di tempo a disposizione...
A questo punto, però, mi interessa davvero la vostra opinione. Quando realmente sentite l'esigenza di un prodotto come CodeWall.NET? e quanto i suoi limiti potrebbero indurvi a NON utilizzarlo? Lasciate pure i vostri commenti, please.
di Francesco BalenaIn generale, penso che il namespace My di VB 2005 sia una bufala. Nei piani di Microsoft, questa nuova feature del linguaggio dovrebbe servire ad avvicinare i programmatori VB6 alla nuova versione, ma chiunque si è trovato a fronteggiare realmente la migrazione - del codice esistente o anche semplicemente della conoscenza - sa bene che non sono questi i motivi per cui il programmatori VB6 fanno ancora tanta resistenza a .NET. I veri problemi sono altri: il fatto che il wizard di traduzione non risolve tutti i problemi, il modello di ADO.NET così diverso da quello di ADO, e così via.
La mia impressione è che il namespace My non solo non servirà a conquistare nuovi convertiti alla religione .NET ma - cosa molto peggiore - servirà ad allontare qualche VB-ista dal "Verbo" del .NET Framework. Il namespace My non è che l'ennesimo strato che separa il linguaggio dal Framework vero e proprio. Avrebbero potuto farlo con una libreria di classi (e regalarla anche ai programmatori C#, poveretti!), invece hanno creato una infrastruttura che servirà a rendere il VB ancora una volta un linguaggio sui generis.
Quale sarà il risultato? secondo me, nel migliore dei casi saranno in pochi a usare questa feature, quindi alla fine non verrà estesa più di tanto. Nel caso peggiore, invece, molti programmatori VB, invece di studiare il .NET Framework e migliorare le loro conoscenze in materia, finiranno per usare questo strato che aggiunge poco o nulla e in alcuni casi addirittura offre meno flessibilità dell'accesso diretto alle classi del .NET Framework. Con l'aggravante che se vi fate viziare dal namespace My sarà più difficile passare a C# o a qualche altro linguaggio .NET.
In molti casi una chiamata a un metodo nel namespace My produce lo stesso effetto di una singola chiamata a un metodo del .NET Framework, quindi è lecito chiedersi perchè dovrei usare questa sovrastruttura se non porta alcun vantaggio, neanche a livello di maggiore concisione del codice? è una domanda ancora senza risposta. O meglio, il namespace My è una risposta a una domanda che in realtà nessuno ha mai fatto ... D'altra parte, Microsoft non fa queste cose alla leggera e sono certo che sono stati fatti focus group e sondaggi per misurare in anticipo l'indice di gradimento di questa feature. Bah, vedremo....
Ad essere obiettivo, comunque, nel namespace My ci sono delle cosucce davvero interessanti che vorrei tanto che fossero parte del Framework. Non che siano impossibili da ottenere altrimenti (ovviamente, visto che è comunque codice managed) ma la facilità con cui si ottengono con il namespace My è degna di nota. Ecco qualche esempio:
L'evento StartupNextInstance di My.Application: permette di sapere quando un'altra istanza della applicazione è stata lanciata e di leggere l'argomento passato sulla linea di comando. In questo modo si implementano applicazioni single-instance senza giochi strani con Mutex o altre tecniche non proprio semplicissime.
La collection OpenForms dell'oggetto My.Application: è comodo poter ciclare su tutti i form aperti nella applicazione senza dover scrivere il codice che mantiene sempre aggiornato l'elenco.
I metodi CopyDirectory, CopyFile, DeleteDirectory, DeleteFile, MoveDirectory e MoveFile dell'oggetto My.Computer.FileSystem: permettono di copiare, cancellare e spostare file e directory mostrando la dialog animata standard di Windows, senza dover usare PInvoke per chiamare la API ShFileOperation.
Il metodo FindInFile dell'oggetto My.Computer.FileSystem: cerca una stringa (opzionalmente in modo case-insensitive) in tutti i file di testo in un folder o un albero di directory, e restituisce la collection dei file che contengono la stringa. Lavorando con le regex si possono fare ricerche molto più sofisticate, ma questo metodo è molto più semplice da usare.
L'oggetto My.Computer.Keyboard: espone proprietà a sola lettura che testano lo stato dei tasti di shift e di lock, risparmiando una chiamata alle API di Windows.
I metodi DownloadFile e UploadeFile dell'oggetto My.Computer.Network: eseguono il download o l'upload di un file all'URL specificato, mostrando anche una dialog con il progress dell'operazione e segnalando se l'utente ha cancellato il comando.
A parte queste poche eccezioni - e qualcun'altra che potrebbe essermi sfuggita - non mi pare ci sia un reale vantaggio ad usare i metodi e gli oggetti del namespace My rispetto alle chiamate standard al Framework.
P.S. Non includo in questo elenco gli oggetti My.Resources e My.Settings, perchè anche se fanno parte del namespace My sono comunque a disposizione anche degli sviluppatori C#, anche se con una sintassi differente.
di Enrico SabbadinVi sarà capitato di vedere, in del codice di esempio o generato da Wizards, l'utilizzo del motodo GetChanges del Dataset per estrarre un Dataset con solo le righe modificate. Tale Datsaet veniva mandato al Business Layer potenzialment remoto invece che tutto il DataSet per risparmiare banda.
Evidentemente questa cosa non mi ha mai convinto: se il mio Dataset era fatto da una Table Ordine e una Table RigaOrdine, mi sarei ritrovato sul server degli "oggetti" ordine incompleti .. come avrei potuto implementare logiche che richiedevano per esempio un numero minimo di righe nell'ordine ?
Quando uno usa i Dataset per contenere cose che sono logicamente "oggetti" fatti di elementi in relazione gerarchica tra loro, ha bisogno di una GetChanges "Object Oriented" che estrae tutti gli ordini che hanno la testata o una delle righe modificate (che vuol dire aggiunte, editate o cancellate).
Questo è un requisito non solo per implementare correttamente logiche di business ma anche per implementare funzionalità logicamente proprie di un oggetto all'interno del proprio business layer : specificatamente con una GetChanges "object oriented" ho potuto facilmente implementare una politica di update basata su timestamp non della singola riga ma di tutto l'oggetto (se modifico una riga dell'ordine cambio il timestamp della testa ordine). Un altra cosa che ho potuto fare facilmente è salvare una copia di "backup/revisione/storia" (un po' alla source safe) di ogni oggetto che gestisco ogni volta che un elemento a qualunque livello della gerarcia dell'oggetto è modificato.
Qui di seguito trovate il codice per implementare la mia GetChanges, chiaramente funziona non solo con un solo livello di gerarchia ma con una struttura di tabelle gerachicamente "innestate" a piacimento.
Il codice non è il massimo dell'eleganza anche per la necessità di scrivere codice "particolare" per gestire (navigare, estrarre) le righe in stato deleted.
public DataSet GetFullChanges(DataSet p_Originalds,string p_rootTable) { DataSet _Smartds = (DataSet)Activator.CreateInstance(p_Originalds.GetType()); // cosi' mentre costruisco il Dataset non devo preoccuparmi // dell'integrità referenziale _Smartds.EnforceConstraints = false;
DataTable _DataTable = p_Originalds.Tables[p_rootTable]; ArrayList _ModifiedObjects = new ArrayList(); // voglio trovare tutti i padri che // sono stati modificati o cancellati o hanno dei figli cancellati o modificati foreach (DataRow _Row in _DataTable.Rows) if (pf_ThisOrChildrenHaveChanges(_Row)) _ModifiedObjects.Add(_Row);
foreach (DataRow _Row in _ModifiedObjects) { pf_ImportRowAndChildren(_Smartds, _Row); }
_Smartds.EnforceConstraints = true; return _Smartds; }
private bool pf_ThisOrChildrenHaveChanges(DataRow p_Row) { // la riga è modificata esco if (p_Row.RowState != DataRowState.Unchanged) return true;
foreach (DataRelation _Rel in p_Row.Table.ChildRelations) { // original mi da anche le cancellate ma non le nuove, // devo quindi chiederla due volte con parametro diverso // al primo giro la uso solo per trovare le figlie cancellate DataRow[] _ChildRows = p_Row.GetChildRows(_Rel, DataRowVersion.Original); foreach (DataRow _row in _ChildRows) if (_row.RowState == DataRowState.Deleted) return true;
// adesso la uso per esaminare ecursivamente le righe figlie (nuove o modificate) _ChildRows = p_Row.GetChildRows(_Rel, DataRowVersion.Current); foreach (DataRow _row in _ChildRows) if (pf_ThisOrChildrenHaveChanges(_row)) return true; } // se arrivo qui la riga non è modificata, ne lo è uno dei suoi figli return false; }
// importo dai figli a salire verso il padre // anche se non è strettamento necessario poichè sono con // enforceconstraints = false private void pf_ImportRowAndChildren(DataSet p_TargetSmartDS, DataRow p_SourceRow) { p_TargetSmartDS.Tables[p_SourceRow.Table.TableName].ImportRow(p_SourceRow); foreach (DataRelation _Rel in p_SourceRow.Table.ChildRelations) { DataRow[] _ChildRows = null; if (p_SourceRow.RowState == DataRowState.Deleted) _ChildRows = p_SourceRow.GetChildRows(_Rel, DataRowVersion.Original); else _ChildRows = p_SourceRow.GetChildRows(_Rel);
foreach (DataRow _row in _ChildRows) { // ricorsivamente importo la riga ed i suoi figli pf_ImportRowAndChildren(p_TargetSmartDS, _row); } } }
di Giuseppe Dimauro
J
Problema: docking di controlli .NET direttamente nella client area di Outlook con VSTO per Visual Studio 2005
Il file contenente il codice sorgente: HtmlViewModified.zip (256.37 KB)
Impegnato per conto di un cliente che sta ricrivendo una serie di applicazioni direttamente dentro Office System con .NET 2.0 e VS2005 – e Outlook 2003 in particolare - mi sono imbattuto in un “piccolissimo” problema non di poco conto. Benche’ con gli altri prodotti di Office sia particolarmente semplice mostrare e usare form di ogni genere preparate in .NET, con Outlook 2003 ci siamo subito imbattuti in una serie di limiti che inizialmente sembravano insuperabili e decisamente antipatici. Per la precisione, a prima vista sembra che sia impossibile “dockare” le proprie form .NET all’interno dell’area client principale di lavoro di Outlook (l’area della inbox per intenderci) per scrivere applicazioni di produttivita’ utilizzando .NET, direttamente dentro Outlook. Pero’ ... con un po’ di fortuna e di intuito posso ora dire che l’empasse e’ durata relativamente poco. Infatti scaricato il preziosissimo esempio HtmlView disponibile sul sito di MSDN, che mostra come fare un po’ di report con del HTML plasmato via .NET, l’idea di ospitare le form (controlli windows form) .NET all’interno del HTML viewer (e’ il controllo del browser IE embedded dentro outlook) si e’ fatta subito strada. Devo ammettere che la mia bassissima conoscenza di HTML (che proprio non mi entra in testa - che ci volete fare) e’ stata, alla fine, la parte piu’ complessa del tutto. Per il resto il lavoro di inserimento delle form (controlli .NET) dentro Outlook e’ stato tutto in discesa e senza troppi problemi o intoppi. Descrivo brevemente per punti una serie di considerazioni e consigli da seguire per studiare il materiale allegato che mostra la soluzione del problema:
- aggiornate la connection string nel .config che fa una piccola query sul db northwind per mostrare un diagramma a torte dentro un report embedded nella form schiacciando uno dei due pulsanti che troverete in testa alla vista dentro Outlook dopo aver lanciato l’applicazione e selezionata la cartella HtmlView figlia della cartella Inbox.
- la classe BaseActiveXContainer (che ho copiato da qualche parte e modificato – l’autore mi perdoni per questo) contiene del pratico codice per registrare i controlli .net come componenti COM
- Generate tutte le volte un nuovo GUID per ogni nuovo controllo. Non usate la tecnica CCP (Cut Copy e Paste J - Copyright del mio amico Pierpaolo R.) con gli occhi bendati. Brutte sorprese vi aspettano.
- Nel file AssemblyInfo.cs (aggiunto a manina perche’ in disuso con vs2005 – decideremo una best practice spero tra pochissimo ...) ho inserito degli attributi assembly wide per evitare problemi di security per caricare dll che non sopportano di essere chiamate da assembly “partially trusted”.Senza, molte LoadAssembly dinamiche rischiano di dare parecchi problemi.
[assembly: AllowPartiallyTrustedCallers]
[assembly: FileIOPermission(SecurityAction.RequestMinimum, Unrestricted = true)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution = true)]
[assembly: RegistryPermission(SecurityAction.RequestMinimum, Unrestricted = true)]
[assembly: UIPermission(SecurityAction.RequestMinimum, Unrestricted = true)]
- In testa alla classe HtmlViewContainer trovate una serie di attributi che vi permetteranno di riesporre attravers COM i vostri controlli .NET da “agganciare” dentro Office. La stessa classe contiene una serie di proprieta’ statiche che espongono globalmente nella applicazione i puntatori alle istanze di alcuni oggetti importanti di Outlook per interoperare con lo stesso con interfacce duali COM per supportare Automation.
- Il file “.htm” del progetto allegato mostra come invocare metodi dell’oggetto ospitato dal HTML, che ospita il controllo .NET nella pagina, via jscript e automation.
- Preparatevi mentalmente ad usare caspol o l'utility presente negli administrative tools per dare full trust ad eventuali DLL esterne bisognose di essere trattate in tal modo. Sappiate che il vostro assembly dentro la sandbox di Outlook e partially trusted. Vi consiglio di firmare digitalmente le vostre DLL e fare full trust su machine con le strong name degli assembly o direttamente un certificato buono.
- altre ed eventuali ... prossimamente :) stay tuned
Una piccola schermata che mostra quello che si puo' fare dentro Outlook con .NET e VSTO per VS2005 completato in treno mentre tornavo a casa :). Nel codice trovate tutto quello che serve per far parlare il vostro controllo con Outlook dall'interno dell'explorer html di Outlook.

Enjoy !
Sto lavorando su dei proof of concept per Microsoft per Office Word ed Excel avanzatissimi. Spero di condividere al piu' presto con voi il materiale che sto producendo.
Giuseppe
di Enrico SabbadinFino a poco tempo fa, non avevo mai avuto il bisogno di andarmi a studiare le API che permettono di interagire in maniera esplicita con il DTC. Avevo quindi accettato come atto di fede che il contesto transazionale di COM+ (che wrappa una transazione DTC) non fluiva attraverso chiamate ad oggetti remoti fatte via .NET Remoting. Immaginavo che ci sarebbe stato bisogno di salti mortali e capriole in C++ (che lascio a Giuseppe :) ) per aggiungere una tale funzionalità.
Mi sbagliavo: .NET 1.1 mette a disposizione a livello Managed tre oggettini (ContextUtil, SWC e BYOT) con i quali possiamo implementare una semplice soluzione, del tutto trasparente al codice appplicativo, con la quale un contesto transazionale attivo sul chiamante viene trasferito sul server ed inserito nel contesto di esecuzione di una chiamata remota.
Ah ! nell'articolo viene anche mostrata una soluzione per chi non vuole usare ServicedComponents ma preferisce maneggiare Transazioni facendo l'enlistment esplicito delle connessioni.
Buona lettura !
di Francesco BalenaSto correggendo il capitolo dedicato al namespace My, e sto aggiungendo alcuni dettagli che avevo tralasciato nella prima stesura, come la creazione di custom setting provider (per salvare i settings su altri medium oltre al classico file di configurazione, ad esempio un database) e la possibilità di eseguire il binding delle proprietà di un controllo con le impostazioni utente. I custom setting provider sono abbastanza complessi e interessano un numero relativamente ristretto di programmatori, mentre il binding dei setting è un argomento decisamente molto più semplice e che non mancherà di suscitare interesse di chiunque lavori con i Windows Forms. Insomma, l'argomento ideale per un post sul blog di dotnet2themax.
Ho trovato molti articoli e post sull'oggetto My.Settings di Visual Basic 2005 (nonchè dell'omologo Settings di C#), ma quasi tutti omettono di sottolineare il fatto che è possibile eseguire il binding di uno user setting con una proprietà di un form, incluso le proprietà Size e Location. Questa feature permette - tra le tante cose - di ritrovare ciascun form della applicazione nella posizione e nella forma in cui l'utente lo aveva lasciato la volta precedente (nella stessa sessione di lavoro o in una sessione precedente). L'infrastruttura di .NET 2.0 si prende carico di assegnare la proprietà quando il form viene caricato e di salvarla nel file di configurazione quando la proprietà è modificata.

Figura 1: Il designer di Visual Studio 2005 permette di definire settings a livello utente e applicazione.
|

Figura 2: Eseguire il binding di una proprietà con una impostazione utente.
|
Il bello è che non dovete scrivere neanche mezza riga di codice. Infatti, è sufficiente definire le proprietà in questione nella pagina Settings del designer My Project (Visual Basic) oppure Properties (C#), ad esempio MainFormLocation e MainFormSize (Figura 1); è essenziale che lo scope di queste impostazioni sia User, poichè le impostazioni di tipo Application non sono modificabili. A questo punto potete selezionare il form, passare alla finestra Properties, aprire la sezione (Application Settings), cliccare sulla freccia accanto al nome delle proprietà ClientSize e Location, e selezionare lo user setting da mettere in binding. Se non avete ancora create il setting in questione, basta cliccare sull'elemento New. (Figure 2)
Come ho già accennato, il dettaglio notevole è che il valore di queste impostazioni viene aggiornato automaticamente quando spostate e ridimensionate il form. Ovviamente si possono mettere in binding altre proprietà, ad esempio Text, BackColor, ecc. Facendo la stessa cosa per tutti i form della applicazione abbiamo a disposizione un meccanismo di persistenza delle impostazioni dei form semplice e potente, ancora una volta senza scrivere codice!
Ovviamente è possibile fare lo stesso con le proprietà dei singoli controlli. Non tutte le proprietà sono in grado però di notificare a .NET il fatto che sono state modificate. Perchè ciò avvenga il controllo deve implementare l'interfaccia IBindableComponent e deve implementare un evento XxxxChanged per la singola proprietà, oppure implementare la nuova interface INotifyPropertyChanged. La maggior parte dei controlli Windows Forms implementano queste interfacce, ma non tutti. Ad esempio, il controllo ToolStripItem non la implementa. In questo caso la proprietà sarà impostata correttamente quando il form viene mostrato, ma il valore del setting deve essere aggiornato via codice. |
di Giuseppe DimauroAlcuni sample recuperabili via Internet per provare e studiare VSTO (Visual Studio Tools for Office - pronunciato VISTO) per Outlook potrebbero dare problemi di compilazione. Io ho scaricato ad esempio questi sample da msdn.microsoft.com e ho dovuto apportare le seguenti modifiche nell'evento di startup utilizzando il metodo protetto GetPrimaryControl per sistemare il tutto:
Prima della modifica:
private void ThisApplication_Startup(object sender, System.EventArgs e) { // Initialize the event tracker object. _eventTracker = new EventTracker(this); // Create custom menu items. CreateMenu(); }
Dopo la modifica, utilizzando il metodo GetPrimaryControl:
private void ThisApplication_Startup(object sender, System.EventArgs e) { // Initialize the event tracker object. _eventTracker = new EventTracker(this.GetPrimaryControl()); // Create custom menu items. CreateMenu(); }
Vi consiglio vivamente di dare una occhiata a questa tecnologia perchè i risultati sono veramente notevoli. Ovviamente le stesse modifiche valgono anche per gli esempi in VB.
di Enrico SabbadinHo cominciato il porting alla 2.0 dei miei Application Block di cui è attualmente disponibile sul sito la versione 3.0. Rispetto alla versione disponibile ho già aggiunto funzionalità interessanti come un completo supporto per uno sviluppo Database agnostico. Innanzitutto sono gestite le varie idiosincrasie nella definzione dei parametri dei comandi SQL (convenzioni di naming e tipo dato). Ciò è stato fatto introducendo nel mio .NET data provder l'oggetto SmartParameter che fa da mediatore verso i veri oggetti parametro reali. Ho inoltre introdotto un sistema che reperisce, da un repository di template SQL distinti per database, lo statement SQL corretto in base al database attivo, questo per gestire quei casi in cui la diversità nei vari dialetti SQL non è conciliabile.
Con queste due tecniche (anzi a essere sincero è stata sufficente la prima). Ho fatto il porting a questo framework di un'applicazione di commercio elettronico facendola girare su SQLServer, Access, MySQL, Oracle, PostGre).
Contestualmente al porting verso .NET 2.0, pensavo di aggiungere la gestione per transazioni distribuite (per saperne di + sull'argomento aspettatevi un mio articolo tra breve sul sito) ed un supporto per un ResourceManager custom che reperisce le informazioni da file XML. Chiunque abbia dato un'occhiata a Sabbasoft Applicaion Blocks (o magari anche no), è invitato a contattarmi per aggiungere sue idee e desiderata alla wish list delle features.
di Francesco BalenaCom'è noto, Visual Studio 2005 include un designer per creare automaticamente una classe My.Settings, che contiene le impostazioni sia a livello di applicazione che di utente. Anche se il designer è decisamente comodo, non è chiaro perchè usare un designer debba essere più semplice che scrivere una classe che esponga quelle impostazioni sotto forma di campi e proprietà, che le carichi automaticamente da disco e che, se modificate, le salvi automaticamente al momento di chiudere l'applicazione. Dal mio punto di vista, l'approccio scelto da Microsoft per VS2005 è la (solita?) esasperazione del concetto RAD, solo che in questo caso le operazioni necessarie per definire la classe My.Settings non fanno in realtà risparmiare tempo rispetto alla definizione via codice delle stesse entità.
Queste considerazioni mi sono venute spontanee non ragionando in astratto, ma mentre scrivevo le mie prime applicazioni.NET 2.0. Avevo necessità di un meccanismo per salvare tutte le impostazioni del programma in un file, un po' come quando in Visual Studio salviamo tutte le impostazioni di un progetto in un file .sln. A pensarci bene, il problema è simile (o meglio, identico) al problema di salvare le impostazioni relative all'utente e alla applicazione: si tratta di salvare e poi ricaricare un gruppo di variabili su disco, con la differenza che nel caso delle impostazioni di progetto la posizione del file non è fissa.
Insomma, alla fine ho scritto un meccanismo di persistenza per le variabili di progetto, basato su una classe base SettingsBase. Quando ho una serie di variabili globali in un progetto che devono essere salvate e ricaricate su richiesta, non devo fare altro che derivare una classe da SettingsBase, chiamandola ad esempio Globals:
Public Class Globals Inherits SettingsBase
Public Documents() As String ' Un array di nomi di documenti Public WordWrap As Boolen ' True se occorre applicare il word wrapping
' ...
End Class
Tutte le variabili Public in questa classe possono essere salvate e ricaricate da file semplicemente usando i metodi Load e Save che la classe eredita da SettingsBase. Poichè un overload di questi metodi accetta uno Stream, è anche possibile salvare e ricaricare da altri medium, ad esempio un campo di database.
Usare questo approccio per implementare anche il salvataggio e il caricamento delle impostazioni di utente e di applicazioni è banale, e infatti ho anche definito delle classi UserSettingsBase e ApplicationSettingsBase. Ereditando da queste classi astratte è possibile creare insiemi di variabili distinte per ciascun utente oppure condivise da tutti gli utenti della applicazione. Come accade per la classe My.Settings, le variabili sono automaticamente caricate alla partenza della applicazione e salvate prima di uscire. A differenza di My.Settings, è possibile salvare le impostazioni su database centralizzati (cosa molto comoda per supportare applicazioni smart client con centinaia di utenti che possono fare il login da qualunque stazione della rete). Ma soprattutto, a differenza di My.Settings, queste classi sono utilizzabili anche in Visual Basic 2003 (e più in generale in .NET 1.1).
Il codice delle classi SettingsBase, UserSettingsBase e ApplicationSettingsBase non è proprio banale, quindi ho pensato di commentarlo a dovere e di scriverci un articolo di accompagnamento. Alla fine ne è venuto fuori un testo di oltre 30K, che non era quindi molto adatto a un post di blog, per cui ho deciso di pubblicarlo nella sezione articoli del sito, insieme al codice sorgente completo, al seguente link L'articolo "Application e User Settings in Visual Basic 2003"
di Enrico SabbadinCercando di dare una mano ad Alberto con un problema su Remoting, ho scoperto oggi una cosa poco nota.
La documentazione afferma che quando si ospitano componenti remoti in IIS è possibile mettere le informazioni di configurazione nel file web.config e queste saranno automaticamente lette ed applicate dal runtime di ASP.NET.
La cosa non è totalmente corretta: in realtà sono lette solo le registrazioni dei tipi (blocco <service>) .. il resto, cioè sezione client e sezione channels è ignorata (Questo articolo lo chiarisce per la sezione client ma non per la sezione channels http://support.microsoft.com/default.aspx?scid=kb;en-us;323490 ) .
La configurazione dei canali puo' servire per impostare il typefilterlevel a full o a registrare dei sink lato server prima o dopo il formatter.
La soluzione ? mettere la sezione channels e client (e a sto punto tutta la configurazione di remoting) in un file specifico e caricarlo esplicitamente con RemotingConfiguration.Configure in applicationstart del global.asax.
protected void Application_Start(Object sender, EventArgs e) { System.Runtime.Remoting.RemotingConfiguration.Configure (HttpContext.Current.Server.MapPath ("Remoting.config")); }
di Marco BellinasoO almeno questo è il titolo di questa pagina su MSDN In realtà per ora io ne ho contate 47 di applicazioni di esempio, ma forse ne ne sono perse 3 perchè in fondo alla pagina c'è scritto "Coming soon - The remaining 51 samples are coming soon. Stay tuned." 
Ma non andiamo a pignolare, anche quello che c'è già è decisamente benvenuto! Le applicazioni sono fornite in C# o VB.NET e sono divise nelle seguenti categorie:
- Base Class Libraries: come cambiare le ACL di un file, implementare animazioni su console, scaricare file da FTP, comprimere e decomprimere file, usare le generic collections, ricavare informazioni sui drive, usare la classe Stopwatch, eseguire il PING e qualche altra operazione di networking.
- Data Access: query asincrone, uso e confronto di Datareader con DataSet, update batch, paginazione, bulk update, salvataggio e recupero di immagini dal DB, classi factory, uso di MARS, Notification Services, e UDT di SQL Server 2005.
- Web Development: master pages, API e controlli di membership, profili, controlli menu e TreeView, web part, data binding con i vari componenti xxxDataSource.
- Windows Forms: task asincroni, componenti BindingNavigator e BindingSource, estensioni alla DataGridView standard, i nuovi controlli MaskedTextBox, WebBrowser, StatusStrip, ToolStrip, SplitContainer e LayoutPanel.
Insomma, se ancora non avete dato un'occhiata a .NET 2.0 e VS.NET 2005, questi esempi pronti per essere eseguiti sono un'ottima occasione per vedere velocemente alcune delle novità introdotte. Complimenti per l'iniziativa, e come dice la pagina stessa: tenete d'occhio la pagina per gli ulteriori 51 (o 54?) esempi che hanno già promesso.
di Francesco BalenaIn questi giorni sto lavorando al capitolo sulla serializzazione con .NET 2.0 e mi sono subito imbattuto in un problema imprevisto. La nuova versione del framework supporta un nuovo attributo chiamato OptionalField, che serve a marcare i campi aggiunti ad una nuova versione di una classe. Ecco in pratica come questo attributo dovrebbe funzionare (il condizionale è d'obbligo, come vedrete). Supponiamo di avere una classe Person nella versione 1.0.0.0 di un assembly BOLibrary (marcato con uno strong-name).
Class Person Public Name As String ' altri campi omessi... End Class
Supponiamo ora di aggiornare il nostro assembly alla versione 1.1.0.0 e di aggiungere un nuovo campo ID. Se però tentiamo di deserializzare con la nuova versione uno stream serializzato con la prima versione avviene un SerializationException. Questo errore può essere aggirato proprio grazie al nuovo attributo:
Class Person Public Name As String <OptionalField()> Public ID As String ' altri campi omessi... End Class
Il primo problema da risolvere, però, è che in realtà il .NET runtime tenterà comunque di deserializzare la versione 1.0.0.0 della classe, quindi l'unico modo per evitare un'altra SerializationException è di impostare la proprietà AssemblyFormat dell'oggetto BinaryFormatter al valore Simple, in questo modo:
Dim p As New Person() Dim bf As New BinaryFormatter() ' non inserire il nome completo dell'assembly nello stream bf.AssemblyFormat = FormatterAssemblyStyle.Simple Dim fs As New FileStream("c:\person.dat", FileMode.Create) bf.Serialize(fs, p)
Questo è almeno il codice che si trova in giro su Internet, in numerosi blog e articoli online. Peccato che con la Beta 2 questo codice non funziona più :-( Il motivo è semplice: in questa beta la proprietà AssemblyFormat viene ignorata per il BinaryFormatter, e viene onorata solo dal SoapFormatter (che però nel frattempo è stato dichiarato obsoleto e non si dovrebbe più usare). Quindi l'attributo OptionalField funziona come descritto nei vari esempi solo con gli assembly non segnati con uno strong-name, in cui il problema del versioning non esiste neanche. La cosa strana è che questo "dettaglio" non è documentato da nessuna parte e solo grazie a qualche "amicizia altolocata" a Redmond ho avuto la conferma che questo comportamento è by-design, non è un bug della Beta 2, e quindi lo ritroveremo pari pari nella versione definitiva (se non arrivano cambi di rotta nel frattempo).
Poichè è buona norma marcare con uno strong-name tutti i propri assembly - sia EXE che DLL - sia per applicare un numero di versione che per attivare la CAS che per lavorare con ClickOnce, è necessario quindi trovare un metodo per usare assembly con strong-name e allo stesso tempo poter deserializzare una istanza serializzata con una versione precedente. Altrimenti tutto il meccanismo della version tolerant deserialization (VTS) va a farsi benedire. Per fortuna la soluzione è a portata di mano, sotto forma della proprietà Binder.
La prima cosa da fare per poter usare questa proprietà è definire un tipo che deriva dalla classe astratta SerializationBinder. Questa classe ha un metodo astratto BindToType, che viene chiamato quando il BinaryFormatter deve deserializzare un tipo. Ecco una classe di questo tipo, che reindirizza tutti i tipi presenti nella versione 1.0.*.* della libreria BOLibrary alla versione 1.1.0.0 della stessa libreria:
Class MySerializationBinder Inherits SerializationBinder
Public Overrides Function BindToType(ByVal assemblyName As String, ByVal typeName As String) As Type ' Read the version of the assembly. Dim an As New AssemblyName(assemblyName) If an.Name = "BOLibrary" AndAlso an.Version.Major = 1 AndAlso an.Version.Minor = 0 Then ' Replace the version number with the current version number. an.Version = New Version("1.1.0.0") ' Load the new assembly. Dim asm As Assembly = Assembly.Load(an.FullName) ' Return the type taken from that assembly Return asm.GetType(typeName) Else ' Otherwise, tell the runtime to apply the default binding policy. Return Nothing End If End Function End Class
Ecco come si usa questa nuova classe:
Dim bf As New BinaryFormatter() bf.Binder = New MySerializationBinder()
Gli oggetti SerializationBinder risolvono il problema in questione, ma in realtà sono molto più potenti di tanto. Ad esempio, potete usarli se avete spostato una classe in un assembly differente, oppure potete dire al .NET runtime di deserializzare lo stream in una classe con nome differente (ma con dei campi con lo stesso nome e tipo della classe originaria), ecc. E' un meccanismo molto potente che offre una enorme flessibilità durante la fase di deserializzazione. E senza il quale non potrete mai sfruttare il nuovo attributo OptionalField.
di Enrico SabbadinMentre aspetto la risposta al sesto quizzettino :) (lo trovate come decimo commento al post precedente) ..
Lo sapevate che l'infrastruttura del ResourceManager è estendibile ? , cioè che potete far si che le risorse non siano caricate dagli assembly satellity ma da un qualunque altro storage di vostro piacimento ? .. behh io lo so da poco :) : tutto è cominciato quando ho approfondito il significato del costruttore più complesso della classe ResourceManager il quale accetta come ultimo parametro un Type di nome usingResourceSet.
La documentazione fa capire vagamente che quella è la via per estendere l'infrastruttura di .NET preposta al reperimento delle risorse a runtime. Per fortuna ho trovato questo post http://weblogs.asp.net/cnagel/archive/2003/07/06/9751.aspx in cui è spiegato come fare (ed è fornito anche del codice di esempio). L'implementazione che si può scaricare dal post che vi ho linkato prevede di andare a reperire le risorse da un database. Nell'implementazione mia la classe che derivo da ResourceManager vi fa scegliere se andare per la strada standard o usare uno storage alternativo che è un file XML (lo si decide in base al costruttore usato).
La cosa "carina" è che mi metto in ascolto delle modifiche al file XML e quando questo è modificato invalido la cache del ResurceManager così che alla prossima richiesta le risorse sono caricate fresche (chiamate la Dispose quando "avete finito" o il ResourceManage non va giù perchè un suo metodo si è sottoscritto alla notifica della modifica del file). SmartResources.zip (4.76 KB)
di Enrico SabbadinPensate di sapere tutto su cosa significa ByVal e ByRref, di come queste keyword interagiscono con i value type e i reference type?
Bene allora considerate questo metodo definito nella classe "pippo":
public void ChangeName(Person p) { p.Name= "Enrico";}
Il chiamante attiva la classe che contiene questo metodo e chiama il metodo ChangeName:
Person p = new Person(); p.Name = "Francesco"; pippo x = new pippo(); x.ChangeName(p); Console.WriteLine(p.Name);
Cosa verrà scritto sulla console? Sappiate che la proprietà Name di Person è implementata in maniera "standard", e la risposta non cambia se Name è un field pubblico.
La risposta alla domanda è: "dipende"... da cosa?
di Giuseppe DimauroUn'altra versione aggiornata a .NET 2.0 della straordinaria tecnologia User Interface Application Block e' stata rilasciata. In passato era più che altro un buon esempio ed un ottimo punto di partenza per organizzare un po' più seriosamente i propri progetti e applicazioni di bassa e media complessità avvalendosi di tecniche di organizzazione di grafi navigazionali esterni alla propria applicazione (per qualsiasi approfondimento "in lingua" leggete gli articoli pubblicati su dotnet2themax da Fabio Carucci). Questa volta, a quanto pare, la soluzione proposta sembra decisamente più impegnativa ed elaborata. Infatti il suo nome, in questa rinnovata versione, è Composite UI Application Block ed è indirizzata principalmente a problematiche di tipo Windows Based (la precedente prevedeva anche il web) di una certa complessità. I requisiti minimi sono VB.NET o C#, .NET Framework 2.0 e ovviamente Windows Forms. L'architettura e gli oggetti disponibili sono aumentati arricchendosi di una serie di building block pronti per essere utilizzati in applicazioni reali tanto che esistono già dei prodotti commerciali che ne fanno largamente uso (http://www.microsoft.com/serviceproviders/solutions/ccf.mspx).

Sono sicuro che su questo stesso sito vedrete apparire presto articoli in lingua italiana. Come Code Architects siamo già da tempo attivi su questo fronte includendo spesso questa tecnologia nei nostri piani (abbiamo prodotto anche applicazioni webform over http con questa tecnologia), prototipi, consulenze e quant'altro. Per approfondimenti, nel frattempo, vi consiglio di seguire il seguente link per la Community Technical Preview ufficiale MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/CABCTP.asp
Enjoy :) Giuseppe Dimauro
di Fabio CarucciL'altra settimana ho avuto un po' da fare con il .net remoting per l'implementazione di una piccola applicazione distribuita. Dovendo gestire eventi remoti ho scelto di farlo con le interfacce condivise perchè le ritengo, a mio avviso, molto semplici da scrivere. Tuttavia ho incontrato una piccola difficoltà in fase di notifiche da eventi, può accadere che il componente server non sia in grado di trovare l'assembly condiviso del componente client, sollevando una exception del tipo FileNotFoundException.
Un modo per aggirare il problema, sicuramente non sarà l'unico, è quello di costruire un wrapper di eventi; nello stesso assembly che contiene le interfacce si crea una classe che eredita da MarshalByRefObject, si dichiara un delegato e gli eventi che andranno wrappati al client.
Ecco un breve esempio:
- Assembly General (va distribuito lato server e lato client)
// classe serializzabile che eredita da EventArgs e contiene il messaggio da scambiare using WRemoteEventArgs;
namespace General { // Definisco il delegato public delegate void ServerEventHandler (object sender, RemoteEventArgs e);
// interfaccia public interface IRemoteServer { // evento da wrappare event ServerEventHandler InvokeHostForm;
// metodi vari implementati nella classe che erediterà da IRemoteServer... string SinkClientObj(Object obj, string srvType, out string rtnMsg); void UnSinkObj(string Cookie, string srvType, bool client); void SendMsgToClient(int msgType, string title, string contents, string msg); void News(string title, string contents, int NewsType, out string strOut); void SendRealTimeMsgToClient(); }
// questa è la nostra classe wrapper public class EventWrapper : MarshalByRefObject { // dichiaro l'evento wrapper dello stesso tipo di quello dichiarato nell'interfaccia public event ServerEventHandler InvokeHostFormWrapper;
// dichiaro un metodo public che verrà invocato dall'esterno per gestire gli eventi, // al posto di quello dichiarato nell'interfaccia public void InvokeHostFormX(object sender, RemoteEventArgs e) { InvokeHostFormWrapper(sender, e); } public override object InitializeLifetimeService() { return null; } } }
e nel client si richiama in questo modo:
//...inserire prima di effettuare la registrazione al server...
// istanzio l'oggetto wrapper EventWrapper ewrap = new EventWrapper(); // inizializzo gli eventi ewrap.InvokeHostFormWrapper +=new ServerEventHandler(OnMsgHandler);
Non vi sarà sicuramente sfuggito l'evento dichiarato nella interfaccia, esplicita violazione delle guidelines sulle interfacce descritte nel libro di Francesco e Giuseppe (vedi Cap.8 punto 4 "Eventi nei tipi interfaccia"). D'altronde è uno dei rari casi in cui, per ovviare alla exception, ho dovuto costruire la classe wrapper e, quindi, esporre l'evento al suo esterno.
Un'altra alternativa al problema è quella di utilizzare la tecnica dell'interfaccia di callback, suggeritami da Enrico Sabbadin, inserita nell'assembly "General" e che il client deve implementare e invocare in maniera esplicita; magari ne parlerà lui stesso in un suo post futuro.
Se avete trovato alternative al problema attendo con piacere i vostri commenti.
di Enrico SabbadinUn Web Service che ha scelto il DataSet come formato di scambio dati è un pessimo esempio di interoperabilità. Esso in realtà non è utlizzabile da client non .NET (se non in forma de-tipizzata lavorando a livello di XML).
Come fare però se la nostra applicazione usa già internamente i DataSet e si vogliono pubblicare alcuni dei suoi servizi come Web Services ? Occorre travasare in qualche modo i dati avanti e indietro nei due formati: i dataset sono convertiti in oggetti tipizzati che sono restituiti dal Web Service; parimenti, un oggetto tipizzato ricevuto da un Web Service, viene convertito in un Dataset prima di essere passato ai livelli sottostanti dell'applicazione.
Fortunatamente la cosà puo' essere automatizzata e generalizzata sfruttando le similutidini tra la rappresentazione XML dei dataset, così come è restituita dai metodi ReadXml/WriteXml ed il formato con cui il serializzatore XML rappresenta proprietà e metodi pubblici delle classi.
Occorre prima di ciò però affrontare ancora un altro problema: come generare tali classi tipizzate ? Escludendo l'ipotesi di farlo manualmente esistono due soluzioni : a) usare il tool XSD b) creare un proprio generatore di codice che genera il codice delle classi tipizzate basandosi sullo schema del Dataset. Per avere il pieno controllo sul codice generato ho percorso la seconda alternativa ottenendo la seguente classe (ai volenterosi l'uso di CodeDom per fare la stessa cosa :) ) :
using System; using System.Text ; using System.Data ;
namespace ds2ws { public class GenerateTypedClass { private GenerateTypedClass() {}
public static string Go(DataSet p_ds,string p_rootnamespace) { StringBuilder l_sb = new StringBuilder (); pf_emitDSEnterBlock(p_ds,l_sb,p_rootnamespace); foreach(DataTable l_dt in p_ds.Tables) { if(l_dt.ParentRelations.Count >1) throw new Exception ("Table " + l_dt.TableName + " has more than one incoming relation"); if(l_dt.ParentRelations.Count ==0) pf_EmitArrayContainer(l_dt,l_sb); }
foreach(DataTable l_dt in p_ds.Tables) { if(l_dt.ParentRelations.Count >1) throw new Exception ("Table " + l_dt.TableName + " has more than one incoming relation"); if(l_dt.ParentRelations.Count ==0) pf_EmitTable(l_dt,l_sb); } l_sb.Append (c_ExitBlock); l_sb.Append (c_ExitBlock); return l_sb.ToString (); }
private static void pf_EmitArrayContainer(DataTable p_dt, StringBuilder p_sb) { p_sb.Append (c_MemberList.Replace("%dsname%",p_dt.DataSet.DataSetName).Replace("%tablename%",p_dt.TableName.Replace(' ','_'))); }
private static void pf_EmitTable(DataTable p_dt, StringBuilder p_sb) { p_sb.Append (c_TableEnterBlock.Replace("%dsname%",p_dt.DataSet.DataSetName).Replace("%tablename%",p_dt.TableName. Replace(' ','_')).Replace("%namespace%",p_dt.DataSet.Namespace)); foreach(DataColumn l_column in p_dt.Columns) p_sb.Append (c_Property.Replace("%columnname%",l_column.ColumnName.Replace (' ','_')) .Replace("%datatype%",l_column.DataType.Name));
foreach(DataRelation l_rel in p_dt.ChildRelations) pf_EmitArrayContainer(l_rel.ChildTable ,p_sb); p_sb.Append (c_ExitBlock); foreach(DataRelation l_rel in p_dt.ChildRelations) pf_EmitTable(l_rel.ChildTable ,p_sb); }
static string c_DSEnterBlock = @" using System; namespace %rootnamespace%.WS { using System.Xml.Serialization; [System.Xml.Serialization.XmlTypeAttribute(Namespace=""%namespace%"")] [System.Xml.Serialization.XmlRootAttribute(Namespace=""%namespace%"", IsNullable=false)] public class %dsname% {" + Environment.NewLine + @" private string m_version; public string Version { get { return m_version;} set { m_version=value; }}" + Environment.NewLine + Environment.NewLine ;
static string c_TableEnterBlock = @"[System.Xml.Serialization.XmlTypeAttribute(Namespace=""%namespace%"")] public class %dsname%%tablename% {"+ Environment.NewLine ;
static string c_ExitBlock = @"}" + Environment.NewLine ;
static string c_Property = @"private %datatype% m_%columnname%; public %datatype% %columnname% { get { return m_%columnname%;} set { m_%columnname%=value; }}" + Environment.NewLine ;
static string c_MemberList =@"[System.Xml.Serialization.XmlElementAttribute(""%tablename%"", typeof(%dsname%%tablename%))] public %dsname%%tablename%[] %dsname%%tablename%Items;" + Environment.NewLine ;
private static void pf_emitDSEnterBlock(DataSet p_ds, StringBuilder p_sb,string p_rootnamespace) { p_sb.Append (c_DSEnterBlock.Replace("%dsname%",p_ds.DataSetName).Replace("%namespace%",p_ds.Namespace).Replace("% rootnamespace%",p_rootnamespace)); } } }
Il metodo di entrata ha nome Go. Riceve un Dataset ed una stringa che rappresenta il namespace all'interno del quale deve essere creata la classe tipizzata. Del dataset è evidentemente rilevante la sua struttura e non i dati eventualemnte contenuti.
Salvato come file la stringa restituita dal metodo GO ed incluso tale file nel progetto, andiamo a vedere come travasare i dati da un dataset alla sua classe tipizzata corrispondente e viceversa:
demo _demo = new demo(); //Fill the Dataset System.Xml.Serialization.XmlSerializer _XmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(mynamespace.WS.demo)); foreach(DataRelation _rel in _demo.Relations) _rel.Nested =true; System.IO.MemoryStream _MemoryStream = new System.IO.MemoryStream (); _demo.WriteXml(_MemoryStream); _MemoryStream.Position=0; // move data to the typed class generated by the code generator shown above mynamespace.WS.demo _TypedDemo= (mynamespace.WS.demo) _XmlSerializer.Deserialize(_MemoryStream);
//Move data of the Typed class to a new instance of the demo DataSet demo _demo2 = new demo(); System.IO.MemoryStream _MemoryStream2 = new System.IO.MemoryStream (); _XmlSerializer.Serialize (_MemoryStream2,_TypedDemo); _MemoryStream2.Position =0; _demo2.ReadXml(_MemoryStream2);
I metodi di mappatura da dataset ad oggetto tipizzato e viceversa possono essere facilemente essere resi generici. Qui di seguito abbiamo per esempio il metodo per travasare i dati dal dataset alla sua corrispondente classe tipizzata.
public object fromdstoobj (Type thetype, DataSet theds) { System.Xml.Serialization.XmlSerializer _XmlSerializer = new System.Xml.Serialization.XmlSerializer(thetype); System.IO.MemoryStream _MemoryStream = new System.IO.MemoryStream (); foreach(DataRelation _rel in theds.Relations) _rel.Nested =true; theds.WriteXml(_MemoryStream); _MemoryStream.Position=0; return _XmlSerializer.Deserialize(_MemoryStream); }
Notare che occorre impostare la proprietà Nested delle relazioni tra tabelle a True affinchè l'XML emesso rispetti la gerarchia espressa dalle relazioni (se non si fa ciò i dati di ogni tabella sono emessi in blocchi indipendenti)
|
|
Feed di Blog2theMax
Cerca nel blog
Archivio
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 28 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | | 28 | 29 | 30 | 31 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Categorie
Powered by: newtelligence dasBlog 1.7.5016.2
|