2. Quale versione del Framework.NET?
Questo corso sarà incentrato sulla versione 2 del dotNet
Framework,nonostante sia stata rilasciata in versione definitiva la 3 e
sia quasi pronta la 3.5.
Ciò perché la versione 3 non modifica, sostanzialmente, l’architettura
della versione 2, ma aggiunge uno strato superiore con funzionalità
specifiche. Inoltre la maggior parte del software attualmente sviluppato,
la maggior parte del materiale didattico, così come le Certificazioni
Microsoft (MCTS e MCPD) si basano proprio sulla versione 2.
Anche se non esplicitamente evidenziato tutto quanto presentato di
seguito sarà comunque riferito alla versione 2.
A conclusione del corso verrà dedicato uno spazio proprio per presentare
le novità del dotNet 3.5, la cui uscita è prevista per gli inizi del 2008.
3. Certificazioni Microsoft: i percorsi
Application
Development
Foundation
Esame 70-536
Windows Based
Client Development
Esame 70-526
Design e Developing
Windows Based
Application
Esame 70-548
Web Based Client
Development
Esame 70-528
Design e Developing
Web Based
Application
Esame 70-547
Distribuited
Applicatin
Development
Esame 70-529
Design e Developing
Enterprise
Application
Esame 70-549
8. Cos’ è il dotNet Framework
Il Framework .NET e' la piattaforma di sviluppo di riferimento per il
mondo Windows, grazie alla quale e' possibile realizzare soluzioni
software a 360° in grado di rispondere alle esigenze più complesse.
I Punti di forza sono da
ricercarsi nella sconfinata
libreria di base, in un
architettura a strati che
permette una evoluzione
natura (.Net 3/3.5),
nell’indipendenza dal
linguaggio di
programmazione e nella
disponibilità del più evoluto
ambiente di sviluppo
attualmente esistente
(Visual Studio)
9. dotNet presenta un’architettura a strati. Al suo interno possiamo
distinguere gli elementi portanti dell’intero framework:
Il dotNET Framework
CLR - Common Language Runtime
BCL - Base Class Library
ADO.NET & XML
Windows Forms
ASP.Net, Web Forms & Web
Services
CLS – Common Language
Specification
CTS – Common Type System
10. Il dotNET Framework: il CLR (1)
Il COMMON LANGUAGE RUNTIME (CLR) è uno strato posto al di sopra dei
servizi del sistema operativo. Esso è responsabile dell'esecuzione vera e propria
delle applicazioni: assicura che vengano rispettate tutte le dipendenze, gestisce la
memoria, la sicurezza, l'integrazione del linguaggio e così via.
Il runtime fornisce numerosi servizi che consentono di semplificare la stesura del
codice, la distribuzione dell'applicazione e di migliorare l'affidabilità della stessa.
11. Il .NET Framework: il CLR (2)
•Semplificare lo sviluppo
• Definire standard per il riuso del codice
• Fornire servizi come gestione della memoria e garbage collection
• Semplificare deployment delle applicazioni
•I componenti usano i metadati anzichè la registrazione
•Supportare più versioni
• Deployement da riga di comando XCOPY e disinstallazione DEL
• Eliminare l’uso del registro per i componenti e la necessità di codice extra
per la loro gestione.
• Supportare i linguaggi di sviluppo
•Fornendo classi di base per gli strumenti e i linguaggi degli sviluppatori
• Supportare più linguaggi di programmazione
•Definire i CTS utilizzati da tutti i linguaggi .NET
Con il CLR, Microsoft ha posto alcuni obiettivi cardine della propria tecnologia:
12. Il dotNET Framework: il CLR (3)
Volendo fare un paragone con il mondo JAVA, il CLR è l’equivalente .NET della
Java Virtual Machine (JVM): attiva gli oggetti, li sottopone a controlli di
sicurezza, li dispone in memoria.
Gli eseguibili .NET sono composti da Codice e Metadati. Questi ultimi contengono
la definizione di tipo, informazione sulla versione e riferimenti ad Assembly esterni.
Il Class Loader utilizza i metadati per il caricamento
delle classi .NET, mentre i compilatori Just in Time (JIT)
li utilizzano per compilare l’ Intermediate Language (IL)
13. Tutti i linguaggi .NET, attraverso il CLR, accedono al medesimo sistema di tipi e
alle stesse classi base, ottenendo cosi una convergenza tra Linguaggi e Modelli di
Programmazione
Il codice eseguito dal CLR viene detto CODICE GESTITO (MANAGED CODE) ed
è memorizzato come Codice+Metadati in un formato standard Windows PE
(portable executable) che consente di:
• Leggere i metadati
• Fornire lo stack del codice
• Gestire le eccezioni
• Recuperare informazioni di sicurezza
• Esistono “versioni del CLR” anche sotto
Linux e Mac Os X (vedi MONO) che
consentono di eseguire il codice in formato
PE anche in macchine non Windows
rendendo .NET un framework
multipiattaforma
Il dotNET Framework: il CLR (4)
14. Il dotNET Framework: la BCL
Difatti, prima del dotNet Framework, gli
Ingegneri del Software dovevano
pianificare la realizzazione di una
applicazione scegliendo preventivamente
il linguaggio e di conseguenza si
limitavano le librerie in base a quelle
supportate dallo stesso.
La BCL permette invece di scegliere,
teoricamente anche in fase di sviluppo, il
linguaggio più opportuno per il task da
eseguire, permettendone una sostanziale
interoperabilità, al patto di rispettare le
specifiche minime necessarie.
La Base Class Library (BCL) è l’insieme delle librerie funzionali comuni ad ogni
linguaggio basato sulla piattaforma dotNet, e comprende:
Tipi dato complessi (collezioni), Networking, Accesso al file system,
Interfaccia utente, Sicurezza,Programmazione concorrente, XML e molto
altro
15. Il dotNET Framework: ADO.NET
Praticamente tutte le applicazioni hanno la necessità di interrogare o aggiornare
dati persistenti memorizzati in file piatti, database relazionali o altri tipi di supporto
di memorizzazione.
Per ovviare a tale necessità, il .NET Framework include ADO.NET, un
sottosistema per l'accesso ai dati ottimizzato per ambienti N-tier ed interoperabile
con l'XML ed i documenti XML.
ADO.NET è stato progettato per gli ambienti debolmente accoppiati e per fornire
servizi per l'accesso ai dati ad applicazioni scalabili e servizi basati sul Web.
ADO.NET mette a disposizione API ad elevate
Prestazioni per modelli di dati sia connessi sia
Disconnessi particolarmente adatti alla
restituzione di dati alle applicazioni Web.
16. Il dotNET Framework: XML
XML svolge un ruolo fondamentale in nel .NET framework, sin dalla sua prima
versione, essendo utilizzato per descrivere la applicazioni (metadati) in modo
analogo ai deployment descriptor di Java. Inoltre è spesso la base di
comunicazione per lo scambio dei dati tra due applicazioni (marshaling e
serializzazione)
17. ASP.NET fornisce un modello applicativo sotto forma di CONTROLLI che
semplifica la creazione di applicazioni Web.
L’infrastruttura include un insieme di Controlli Lato Server che ricalcano gli
oggetti Widget delle tipiche interfacce utente HTML (tra cui, caselle di riepilogo,
caselle di testo e pulsanti), ed un insieme aggiuntivo di Controlli Web Evoluti
(come calendari e rotator). Essi sono posizionati attraverso drag&drop sulle
pagine, indicate in gergo Web Form.
I controlli vengono eseguiti in realtà sul server Web ed inviano la propria
interfaccia utente ad un browser sotto forma di HTML. Sul server, i controlli
espongono un modello di programmazione orientato all'oggetto che mette a
disposizione degli sviluppatori Web tutta la ricchezza di questo tipo di
programmazione.
Il dotNET Framework: ASP.NET
18. Il dotNET Framework: WindowsForms
Le WindowsForms sono praticamente le classiche finestre di
un’applicazione windows che contengono i classici elementi come
bottoni,combobox, aree di testo ecc.
Tutti i controlli posizionati su una WindowsForm (e la WindowsForm
stessa, che è ugualmente un controllo) risponde ad una serie di eventi
che lo sviluppatore può gestire attraverso l’implementazione di appositi
Handler.
19. Il dotNET Framework: ASP.NET - WebForms
Le WebForms sono l’equivalente delle WindowsForms,
eccetto che il loro ambiente di esecuzione è il Browser
Web. Esse vengono elaborate sul server e
successivamente passate al browser nel classico formato
HTML
La fase di implementazione di una WebForm non si
discosta molta da quella di una classica WindowsForm.
20. Il dotNET Framework: ASP.NET – XML Web Services
I Web Services sono una sorta di piccoli
servizi disponibili in rete, che forniscono
funzionalità specializzate. Essi vengono
interrogati attraverso il protocollo SOAP
basato su XML.
21. Il dotNET Framework: CTS
Il COMMON TYPE SYSTEM (CTS) definisce un insieme standard di tipi di dato e di regole
necessarie per la realizzazione di nuovi tipi, consentendo ai vari linguaggi di interoperare
correttamente tra loro
CTS fornisce due tipi principali entrambi ereditati da System.Object:
•Tipi a Valore, utili per rappresentare tipi di dati semplici e quelli definiti dall’utente
(ovvero le strutture);
•Tipi a Riferimento, ovvero: tipi di oggetti, tipi di interfacce e tipi di puntatori
Da notare che in .NET tutti i tipi sono orientati agli oggetti (ogni elemento è un oggetto) e i
tipi a valore possono essere convertiti in tipi a riferimento mediante il boxing
CTS definisce, inoltre, un modello uniforme per la gestione delle eccezioni,
indipendentemente dal linguaggio utilizzato (ovviamente con sintassi diverse).
Si tratta in praticamente di definire i seguenti passi:
Lanciare un’eccezione
Catturare un’eccezione
Codice di uscita da un blocco controllato (finally)
22. Il dotNET Framework: CTS, Tipi a Valore
I Tipi a Valore non possono essere null (tranne i NULLABLE che sono un
discorso a parte) e devono sempre contenere dei dati. Possono essere: :
primitivi, strutture e enumerazioni;
Per creare un tipo a valore personalizzato anche derivandolo da una classe
System.ValueType, proprio come fa dotNet per i tipi primitivi. Prendendo ad
esempio il tipo int, si vede che esso non è nient’altro che un alias di
System.Int32 derivato appunto da Sysem.ValueType.
E’ utile sottolineare che il passaggio di un Tipo a Valore in una funzione
avviene per copia
23. Il dotNET Framework: CTS, Tipi a Riferimento
I Tipi a Riferimento rappresentano il riferimento ad oggetti allocati nell’Heap (a
differenza dei tipi a valore allocati sullo Stack), ed ammettono il valore Null.
Il passaggio a funzioni avviene per riferimento, cioè viene passato un indirizzo o
un puntatore ad un oggetto.
Questi tipi vengono gestiti dal CLR e sottoposti a GARBAGE COLLECTION.
CTS promuove la sicurezza di tipi in modo da
migliorare la stabilità del codice, attraverso
definizioni dei tipi completamente note e
che non possono essere alterate.
Ciò si traduce nel fatto che i riferimenti agli
oggetti siano strongly-typed,ovvero durante
la fase di assegnazione di un oggetto ad un
reference viene verificato se sono di tipo
compatibile.
24. Il dotNET Framework: CLS
Le COMMON LANGUAGE
SPECIFICATION, definiscono le
regole che un linguaggio deve
avere per essere gestito dal
framework, permettendo
l’interoperabilità tra i vari linguaggi
supportati dal dotNet Framework.
In pratica descrivo come il
compilatore deve trasformare il
codice sorgente nel codice
intermedio (Intermediate Language
– IL), per essere compatibile con le
specifiche del framework.
Il CLS, inoltre, definisce le API dei
componenti .NET
26. Source code
C++, C#, Visual
Basic o Altria
language .NET
es: Csc.exe o Vbc.exe
Compilatore
Assembly
DLL o EXE
L’applicativo, scritto in uno dei linguaggi supportati, viene compilato, generando così
un ASSEMBLY, che nonostante l’estensione,è un packaging composto da più elementi.
Compilazione e Generazione dell’Assembly
27. Un ASSEMBLY rappresenta il costituente fondamentale di un'applicazione .NET.
Si tratta dell’unità elementare di rilascio del codice e di gestione delle versioni, ed
è composto da:
•Un manifesto (Manifest);
•Un insieme di uno o più moduli (dll o exe);
•Un insieme opzionale di risorse
Tutti i tipi e le risorse gestiti sono contrassegnati in uno dei seguenti modi:
• accessibili solo all'interno della propria unità implementativa
• esportabili per essere utilizzati dal codice al di fuori di tale unità.
Assembly dotNet
28. Type Descriptions
Classes
Base classes
Implemented interfaces
Data members
Methods
Name
Version
Culture
Assembly Manifest
Other assemblies
Security permissions
Exported types
Assembly Manifest: Descrizione
• Stabilisce l'identità dell'assembly in
termini di nome, versione, livello di
condivisione tra applicazioni diverse,
firma digitale.
•Definisce quali file (nome e file hash)
costituiscono l'implementazione
dell'assembly.
•Specifica le dipendenze in fase di
compilazione da altri assembly.
•Specifica i tipi e le risorse che
costituiscono l'assembly, inclusi quelli
che vengono esportati dall'assembly.
•Specifica l'insieme dei permessi
necessari al corretto funzionamento
dell'assembly.
Gli assembly si autodescrivono tramite il proprio MANIFEST (manifesto), che
costituisce una parte integrante di ogni assembly stesso:
29. Assembly Manifest: Uso dei Metadati
I METADATI sono indipendenti dal linguaggio, consentendo a quelli che vi accedono
di interpretarli in modo univoco.
Questo permette di creare progetti (assistiti da IDE come Visual Studio) che
contengono al loro interno codice sorgente di linguaggi diversi, proprio grazie alle
informazioni contenute nei metadati dei componenti;
Il compilatore genera i metadati di un componente dal codice sorgente che
vengono memorizzati nel codice compilato in un formato PE (Portable Executable);
Un tools per la visualizzazione dei metadati è ildasm.exe
30. Assembly: Execution Model, Intermediate Language
Come detto il Codice Sorgente viene compilato nel linguaggio intermedio IL,
spesso indicato anche come MSIL (Microsoft IL) e CIL (Common IL).
Vediamo un esempio di IL:
.method private hidebysig static void Main()
cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr "Hello, world!"
IL_0005: call void
[mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method HelloWorld::Main
31. Assembly: Execution Model
Gli assembly sono caricati in memoria dal CLR solo all’occorrenza:
• prima viene determinata la versione
• poi viene cercato l’assembly nella Global Assembly Cache (GAC) oppure nel
percorso locale indicato dal codice base;
Prima dell’esecuzione il CLR deve procedere alla compilazione dell’ IL (non eseguibile
direttamente dal processore) per la generazione di codice nativo.
Ci sono due possibilità:
• Compilazione del Metodo a Tempo di Esecuzione, Just In Time (JIT);
• Compilazione di tutto l’Assembly prima dell’esecuzione
Come già accennato in precedenza, il CLR permette di limitare le funzionalità del codice
eseguito.
32. Assembly: Execution Model Schema
Compilazione
Esecuzione
JIT
Compiler
Native
Code
MSIL
Code
Metadata
Source
Code
Language
Compiler
Assembly
(.exe o .dll)
Compilazione
Just in Time
33. Assembly: Deployment semplificato
Sicuramente il Deployment rappresenta uno dei grossi vantaggi di dotNet,
permettendo:
• Un’installazione senza effetti collaterali (dll Hell), le applicazioni e i
componenti possono sempre essere condivisi o privati;
• L’esecuzione Side-by-Side, diverse versioni dello stesso componente
possono coesistere, anche nello stesso processo
Il Deployment può avvenire in modi
diversi:
• XCOPY, per le applicazioni
ASP.NET
• .CAB, per le applicazioni
• Windows Forms - Code Download
• .MSI (Windows Installer), per le Applicazioni Windows Forms
l’ installazione in GAC di assembly condivisi, la
Configurazione di shortcut
34. Assembly Location
Avendo parlato di deployment è utile specificare le location che gli assembly
possono assumere in base alla loro tipologia:
Assembly privati, directory applicazione (e sub-directory)
Assembly condivisi, Global Assembly Cache (GAC)
(c:windowsassembly)
Assembly scaricati da URL, download cache
(c:Documents and Settings%USERNAME%
Local SettingsApplication Dataassemblydl2)
Il Tool per esaminare GAC e download cache è GACUTIL.EXE
35. Oggetti “vivi” Oggetti non raggiungibili Spazio libero
Garbage Collection(1)
Fase 1: Mark
Durante l’esecuzione del codice gli oggetti non più utilizzati come vengono
deallocati dalla memoria?
dotNet distrugge automaticamente gli oggetti quando non sono più referenziati,
utilizzando un sofisticato algoritmo di Mark-and-Compact
NextObjPtr
Root set
37. Non sempre ci si può affidare in modo “cieco” al Garbage Collector, ma in alcuni
casi serve un comportamento di finalizzazione deterministica. In particolare
quando si hanno:
Riferimenti a oggetti non gestiti
Utilizzo di risorse che devono essere rilasciate appena termina il loro
utilizzo
Non si possono usare i finalizzatori (come si fa in C++), che sono richiamabili
direttamente, ma bisogna utilizzare l’interfaccia IDisposable, implementando
il metodo Dispose.
Garbage Collection(3)
38. Linguaggi del dotNet Framework
Come più volte ribadito fin ora , il dotNet Framework è LANGUAGE-
INDEPENDED, ovvero non dipende dal linguaggio scelto. Microsoft fornisce
direttamente il supporto (ed il compilatore) per:
•C++, C#, J#, VB 2005, Jscript
Esistono, comunque, implementazione di terze parti che spesso permettono di
portare sulla piattaforma dotNet linguaggi molto utilizzati:
•Perl, Ruby, Python, Pascal, APL, COBOL, Eiffel, Haskell, ML, Oberon,
Scheme, Smalltalk…
Chiunque, rispettando le CLS può realizzare (in teoria) il proprio linguaggio
dotNet Ready
39. Base Class ed Interfacce
Le Classi e le Interfacce della BCL
40. Base Class ed Interfacce: Namespaces
Base Class Library
Data Xml
Web Services User Interface
41. Base Class ed Interfacce: Namespaces
System
System.Data System.Xml
System.Web
Globalization
Diagnostics
Configuration
Collections
Resources
Reflection
IO
Threading
Text
Security
SqlClient
OleDb
SQLTypes
Common
Runtime
InteropServices
Remoting
Serialization
Configuration SessionState
Caching Security
UI
HtmlControls
WebControls
System.Drawing
Imaging
Drawing2D
Text
Printing
System.Windows.Forms
Design ComponentModel
XPath
Xsl
Serialization
Schema
Hosting
Handlers
Compilation
42. Base Class ed Interfacce: Namespaces
System
Threading
Text
Security
Resources
Reflection
IO
Globalization
Diagnostics
Configuration
Collections
Runtime
Serialization
Remoting
InteropServices
43. Base Class ed Interfacce: Namespaces
System.Data
System.Xml
OracleClient
OleDb
Sql/SqlClient
Common
Serialization
Schema
XPath
Xsl
Odbc Messaging
47. dotNet Framework: Input / Output
Una delle principali attività che ci si trova a realizzare è l’interazione con il (i) file
system.
dotNet mette a disposizione un insieme di classi raccolte nel NameSpace
System.IO, che permettono di effettuare operazioni di vario genere su file,
directory,ecc:
FILEINFO and DIRECTORYINFO (base class: FileSystemInfo),
permettono di interrogare ogni elemento del file system per ottenerne
informazioni di vario genere
DRIVEINFO, permette di ottenere informazione sulle periferiche di I/O
FILE, DIRECTORY, PATH sono le “utility class” che permettono,
attraverso metodi statici, di effettuare varie operazioni sui corrispettivi
elementi
48. dotNet Framework: I/O, ottenere informazioni dal File System
FileInfo ourFile = new FileInfo(@"c:boot.ini ");
if (ourFile.Exists)
{
Console.WriteLine("Filename : {0}", ourFile.Name);
Console.WriteLine("Path : {0}", ourFile.FullName);
}
Ottenere informazione su un file
FileInfo ourFile = new
FileInfo(@"c:boot.ini");
ourFile.CopyTo(@"c:boot.bak");
Copiare un file Enumerare I file in una directory
DirectoryInfo ourDir = new
DirectoryInfo(@"c:windows");
Console.WriteLine("Directory: {0}",
ourDir.FullName);
foreach (FileInfo file in ourDir.GetFiles())
{
Console.WriteLine("File: {0}",
file.Name);
}
49. Per effettuare le operazioni di lettura e scrittura, il.Net Framework fornisce gli
STREAM (letteralmente flussi), che permettono un accesso sequenziale e
random ai dati.
La classe base (di tipo astratta) è Stream,che fornisce una serie di funzionalità
comuni a tutti gli stream specializzati.
Tra gli Stream Personalizzati:
•FileStream, specializzato nelle operazioni di lettura/scrittura dei file
•Memory Stream, crea un flusso in memoria (utili per operazioni
temporanee)
•Buffered Stream, crea un wrapper per lo stream specifico allo scopo di
migliorarne le performance;
•StreamReader (base class: TextReader), consente operazioni di lettura su
stream generici
•StreamWriter (base class: TextWriter), consente operazioni di scrittura su
stream generici
dotNet Framework: Gli Stream
50. dotNet Framework: aprire uno Stream su file
L’apertura degli Stream, relativi alla lettura/scrittura file, avviene attraverso una
serie di classi statiche ed enumeratori presenti nel Namespace System.IO:
•File, fornisce le funzionalità di base per leggere e scrivere da file;
•Directory, per effettuare operazioni sulle directory
•FileAccess Enumeration, che specifica i diritti (Read, Write, ReadWrite) da
applicare all’apertura di un file;
•FileMode Enumeration specifica i diritti sui file che si andranno ad aprire.
File.Open(@"C:boot.ini", FileMode.Open, FileAccess.Read);
File.Create(@"c:somefile.txt");
Aprire un file per la lettura
Creare un file
51. dotNet Framework: Leggere/Scrivere da un file
FileStream theFile = File.Open(@"C:boot.ini", FileMode.Open, FileAccess.Read);
StreamReader rdr = new StreamReader(theFile);
Console.Write(rdr.ReadToEnd());
rdr.Close();
theFile.Close();
StreamReader rdr = File.OpenText(@"C:boot.ini");
Console.Write(rdr.ReadToEnd());
rdr.Close();
FileStream theFile = File.Create(@"c:somefile.txt");
StreamWriter writer = new StreamWriter(theFile);
writer.WriteLine("Hello");
writer.Close();
theFile.Close();
File.WriteAllText(@"c:somefile.txt", "Hello");
Leggere da file
Scrivere su file
52. dotNet Framework: Usare il MemoryStream
MemoryStream memStrm = new MemoryStream();
StreamWriter writer = new StreamWriter(memStrm);
writer.WriteLine("Hello");
writer.WriteLine("Goodbye");
writer.Flush();
FileStream theFile = File.Create(@"c:inmemory.txt");
memStrm.WriteTo(theFile);
writer.Close();
theFile.Close();
memStrm.Close();
Scrittura su uno Stream in Memoria e successivo riversamento su file
53. dotNet Framework: Stream Speciali
Oltre agli stream che permetto una normale lettura/scrittura da file o memoria,
esistono una speciale categoria di stream definiti Compression Stream
Attualmente sono disponibili due Compression Stream:
•GZipStream, che permette la compressione compatibile con lo standard de-
facto zip;
•DeflateStream, che crea una compressione proprietaria.
In realtà entrambi gli stream usano lo stesso algoritmo per la compressione. La
differenza sta nel fatto che la compatibilità con lo standard zip richiede un header
apposito e quindi un ulteriore (anche se lieve) aggravio di risorse.
Una sostanziale differenza con gli stream classici, come vedremo dagli esempi, è che
i compression stream scrivono i dati su stream di appoggio (file, memoria).
Infine, è da precisare, che entrambi i compression stream possono gestire al
massimo 4Gb di dati.
L’esempio che segue mostra la compressione/decompressione con la classe GZipStream. Per
utilizzare il DelfateStream basta sostituire l’istruzione di creazione dell‘oggetto
54. FileStream sourceFile = File.OpenRead(inFilename);
FileStream destFile = File.Create(outFilename);
GZipStream compStream = new GZipStream(destFile, CompressionMode.Compress);
int theByte = sourceFile.ReadByte();
while (theByte != -1)
{
compStream.WriteByte((byte)theByte);
theByte = sourceFile.ReadByte();
}
FileStream sourceFile = File.OpenRead(inFilename);
FileStream destFile = File.Create(outFilename);
GZipStream compStream = new GZipStream(sourceFile, CompressionMode.Decompress);
int theByte = compStream.ReadByte();
while (theByte != -1)
{
destFile.WriteByte((byte)theByte);
theByte = compStream.ReadByte();
}
dotNet Framework: Comprimere/Decomprimere con gli stream
Compressione
Decompressione
56. dotNet Framework: Working with Text
Lavorare con le stringhe di testo è un task comune per tutti
gli sviluppatori
.Net mette a disposizione un insieme di classi raccolte nel
NameSpace System.Text, che permettono di effettuare
operazioni di vario genere sulle stringhe:
StringBuilder, permette una manipolazione efficiente delle stringhe;
Regex, Match, Group, permettono l’elaborazione delle stringhe attraverso le
Espressioni Regolari;
Encode/Decode, sono specializzate nella codifica/decofica delle stringhe nei
vari formati internazionali.
57. dotNet Framework: La classe StringBuilder e la classe String
• Normalmente si è portati ad utilizzare la classe String per le operazioni sulle
stringhe.
• In realtà un oggetto di tipo String è un oggetto atipico, perché ogni operazione
su di esso (tipo l’aggiunta di nuovi caratteri alla stringa), non modifica l’oggetto
esistente, bensì ne crea uno nuovo aggiornando il reference.
• Per migliorare le performance (ed evitare inutili sprechi di memoria) il .Net
framework prevede la classe StringBuilder, che crea un oggetto stringa dinamico
System.Text.StringBuilder sb = new System.Text.StringBuilder(30);
sb.Append("wombat");
sb.Append(" kangaroo");
sb.Append(" wallaby");
sb.Append(" koala");
string s = sb.ToString();
Console.WriteLine(s);
58. dotNet Framework: Regular Expression
• Le Espressioni Regolari (Regular Expression) sono una eredità del mondo Unix
e del linguaggio PERL.
• Rappresentano un modo efficiente per effettuare elaborazioni complesse sulle
stringhe di testo. Come esempio, la seguente Regular Expression, permette di
validare un indirizzo email:
• Nella pratica l’utilizzo di Espressioni Regolari può risultare anche molto
complesso e non approfondiremo uteriormente l’argomento.
•Comunque il modo più semplice per verificare se una stringa supera la verifica
rispetto ad una Espressione Regolare (MATCH) è quello di usare la classe Regex
e il metodo statico IsMatch:
^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.)|(([w-]+.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$
Regex.IsMatch("pattern", @"ApatternZ")
59. dotNet Framework: Codifica del Testo
• Vista l’eterogeneità dei sistemi informatici esistenti, non meraviglia che esistano
diversi modi di codificare (o meglio, rappresentare) le stringhe.
• La codifica più utilizzata nello scorso decennio è stata quella ASCII (7bit), ormai
insufficiente per codificare adeguatamente anche caratteri speciali (come le
lettere degli alfabeti orientali).
•Oggi la codifica più utilizzata è la Unicode UTF-16 (16 bit), che è anche la
codifica base utilizzata dal framework .Net.
•Nel caso si renda necessario lavorare con codifiche differenti, è possibile
utilizzare le classi Encode/Decode come mostrato dall’esempio seguente:
// Get Korean encoding
Encoding e = Encoding.GetEncoding("Korean");
// Convert ASCII bytes to Korean encoding
byte[] encoded;
encoded = e.GetBytes("Hello, world!");
// Display the byte codes
for (int i = 0; i < encoded.Length; i++)
Console.WriteLine("Byte {0}: {1}", i, encoded[i]);
61. dotNet Framework: Collection & Generics
Ogni framework che si rispetti deve avere un’ampia serie di collezioni (collection)
che permettono di gestire un insieme di oggetti, più o meno omogeneei,
attraverso i classici costrutti dei moderni linguaggi.
dotNet contempla al suo interno l’implementazione delle COLLECTION
CLASSICHE e dei GENERICS
La differenza sostanziale tra le due è che le Collection Classiche memorizzano al
loro interno gli oggetti attraverso il casting, mentre nel caso dei Generics la
categoria di oggetti contenuti nella collection è tipizzata.
Le Generics portano in dote vantaggi di performance (molto limitati in realtà), ma
soprattutto una programmazione type-safe, in cui è il compilatore a verificare il
corretto utilizzo della collezzione.
62. dotNet Framework: Collection Classiche
Le Collection Classiche sono contenute nel NameSpace System.Collections e
annoverano:
•ArrayList, una semplice collezione ridimensionabile ed indicizzata;
•SortedList, una collezione ordinata composta da coppie nome/valore;
•Queue, la classica coda; struttura FIFO (first in first out);
•Stack, ovvero last in last out, LIFO;
•Hashtable, una collezione nome/valore;
•BitArray, una collezione compatta di valorie Boolean;
•StringCollection, collezione specializzata per le stringhe;
•StringDictionay, ovvero una hashtable specializzata per le stringhe;
•ListDictionary, una collezione nome/valore ottimizzata per piccole quantità
di dati;
•HybridDictionary, una collezione nome/valore ibrida. Per piccole quantità di
dati si comporta come una ListDictionary, altrimenti come una Hashtable
La System.Collctions prevede tre interfacce di base:
•IEnumerable (rende enumerabile la lista),
•ICollection (rende omogenee le funzionalità di base),
•IList (estende le funzionalità di base comuni).
63. La System.Collctions prevede tre interfacce di base:
•IEnumerable (rende enumerabile la lista),
•ICollection (rende omogenee le funzionalità di base),
•IList (estende le funzionalità di base comuni).
Grazie a queste tre interfacce è possibile operare uniformemente sulle varie
Collection Classiche, attraverso i classici metodi: insert, remove, add, e così via.
dotNet Framework: Collection Classiche
Nota a parte merita la possibilità di effettuare l’ordinamento (collection.Sort) di una
Collezione. Infatti l’ordinamento varia in base al tipo di oggetto inserito nella
collezione Per questo è possibile definire un propria classe di ordinamento (che
deve implementare l‘interfaccia IComparer) e della quale un’istanza può essere
passata al metodo Sort:
coll.Sort(new CaseInsensitiveComparer());
64. I generics sono stati introdotti
dalla versione 2 del framework
e sono formati da classi e
metodi che lavorano in modo
uniforme su tipi differenti
Benefici
Le variabili sono di un tipo
ben preciso e non Object
Non è necessario un cast
(errori in fase di
compilazione)
Riutilizzo reale del codice
class Stack<T>
{
private T[] store;
private int size;
public Stack() {
store = new T[10];
size = 0;
}
public void Push(T x) {
// push code goes here
}
public T Pop() {
return store[--size];
}
}
void Add(Stack<int> s) {
int x = s.Pop();
int y = s.Pop();
s.Push(x+y);
}
dotNet Framework: Generics
65. dotNet Framework: Generics
Tra le implementazioni si annoverano:
• List<>, ovvero una semplice lista di elementi;
• Queue<>, la coda (FIFO);
• Stack<>, lo stack (LIFO);
• Dictionary<>, collezione nome/valore;
• SortedList<>, lista di elementi ordinati;
• SortedDictionay <>, collezione nome/valore ordinata;
• NameValuePair<>, coppia nome/valore (in pratica è ritornato dall’iterazione sul
dictionary);
• LinkedList<>, lista linkata;
Tra le interfacce troviamo:
•IList<ItemType>
•IDictionary<K, V>
•IEnumerable<ItemType>
•IComparable<OperandType>
66. ArrayList myList = new ArrayList();
myList.Add(1); // Viene effettuato il boxing (value -> refernce)
myList.Add(2); // Viene effettuato il boxing (value -> refernce)
myList.Add(“3"); // Provocherà un errore a runtime
int i = (int)myList[0]; // Necessario effettuare il cast
List<int> myList = new List<int>();
myList.Add(1); // Nessun boxing
myList.Add(2); // Nessun boxing
// myList.Add(“3"); // Errore a tempo di compilazione
int i = myList[0]; // Nessun casting richiesto
Non-generic
Generic
dotNet Framework: Generics vs. Collection Classiche
67. Rappresentazione senza Generics Rappresentazione con Generics
intobject
intobject
intobject
intobject intobject
int int
int
int
int
int int
Push Pop
Box Unbox
Push Pop
dotNet Framework: Generics vs. Collection Classiche
69. dotNet Framework: Serialization
La Serializzazione permette di salvare un oggetto (e, quindi, il suo attuale stato)
in modo persistente su un supporto di memorizzazione, ma anche di inviare lo
stesso oggetto attraverso le reti per un utilizzo remoto.
Il dotNet Framework permette la serializzazione attraverso opportune classi
contenute nel namespace System.Runtime.Serialization:
•BinayFormatter, permette di serializzare un oggetto in formato binario e
trasferirlo su uno stream;
•SoapFormatter , permette di serializzare un oggetto in formato XML
aderente allo standard SOAP
e nel namespace System.Xml.Serialization
•XmlSerializer, permette di serializzare un oggetto in formato XML
70. dotNet Framework: Serialization
Il dotNet framework utilizza un modo semplice ed immediato per rendere
serializzabile un oggetto:
[Serializable]
class ShoppingCartItem
{
public int productId;
public decimal price;
…
…
<Serializable()> Class ShoppingCartItem
Public productId As Integer
Public price As Decimal
…
… VBC#
basta, in pratica, inserire l’attributo [serializable] per rendere la classe
serializzabile.
La serializzazione comunque può essere parziale, escludendo alcuni elementi,
come di seguito mostrato:
' VB
<NonSerialized()> Public total As Decimal
// C#
[NonSerialized] public decimal total;
71. dotNet Framework: Serialization, IDeserializationCallback
Nel caso in cui si abbiano dei membri (variabili di istanza) calcolati, si può decidere di non
serializzarne il valore (per risparmiare spazio), facendolo ricalcolare durante la
serializzazione attraverso l’interfaccia IDeserializationCallback ed il relativo metodo
IDeserializationCallback.OnDeserialization
[Serializable]
class ShoppingCartItem : IDeserializationCallback {
public int productId;
public decimal price;
public int quantity;
[NonSerialized] public decimal total;
public ShoppingCartItem(int _productID, decimal _price, int _quantity)
{
productId = _productID;
price = _price;
quantity = _quantity;
total = price * quantity;
}
void IDeserializationCallback.OnDeserialization(Object sender)
{
/ / After deserialization, calculate the total
total = price * quantity;
}
}
72. dotNet Framework: Binary Serialization
Come detto, la serializzazione avviene mediante le classi prima descritte e
riversando i dati ottenuti su uno stream. Vediamo alcuni esempi:
string data = "This must be stored in a file.";
FileStream fs = new FileStream("SerializedString.Data", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
// Use the BinaryFormatter object to serialize the data to the file
bf.Serialize(fs, data);
fs.Close();
FileStream fs = new FileStream("SerializedString.Data", FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
// Create the object to store the deserialized data
string data = "";
data = (string) bf.Deserialize(fs);
fs.Close();
Serializzazione
Deserializzazione
Se si volesse utilizzare la serializzazione compatibile con lo standard SOAP, basta
sostiture BinaryFormatter con SoapFormatter
73. dotNet Framework: XML Serialization
Nel caso in cui si voglia rendere il risultato della serializzazione il più portabile
possibile, si può ricorrere alla serializzazione XML.
L’oggetto da serializzare, in questo caso, deve seguire regole più stringenti:
• la classe deve essere pubblica
• tutti i membri da serializzare devono essere pubblici
• deve esistere un costruttore senza parametri espressamente dichiarato
// Create file to save the data to
FileStream fs = new FileStream("SerializedDate.XML", FileMode.Create);
// Create an XmlSerializer object to perform the serialization
XmlSerializer xs = new XmlSerializer(typeof(DateTime));
xs.Serialize(fs, System.DateTime.Now);
fs.close();
// Open file to read the data from
FileStream fs = new FileStream("SerializedDate.XML", FileMode.Open);
// Create an XmlSerializer object to perform the deserialization
XmlSerializer xs = new XmlSerializer(typeof(DateTime));
DateTime previousTime = (DateTime)xs.Deserialize(fs);
fs.close();
Serializzazione
Deserializzazione
74. dotNet Framework: Custom Serialization
Se le precedenti forme di serializzazione non sono adeguate ai nostri scopi (caso
molto raro) possiamo procedere a realizzare una serializzazione personalizzata.
Ciò si può ottenere implementando l’Interfaccia Iserializable (conseguentemente il
metodo GetObjectData) ed applicando l’attributo [serializable] alla classe.
Tuttavia esiste un metodo più immediato, ma ovviamente meno flessibile, per
controllare la serializzazione e quindi personalizzarla: i Serialization Events.
Si tratta di eventi che si scatenano utilizzando il BinaryFormatter e sono:
•Serializing, scatenato prima di inizializzare la serializzazione;
•Serialized, scatenato dopo la serializzazione;
•Deserializing, scatenato prima della deserializzazione;
•Deserialized, scatenato dopo la deserializzazione;
è possibile catturare questi eventi, e gestirli nel modo più opportuno, creando degli
appositi metodi all’interno della classe da serializzare e applicandovi l’attributo
corrispondente all’evento da catturare: [OnDeserialized]
void CheckTotal(StreamingContext sc)
{
if (total == 0) { CalculateTotal(sc); }
}
76. dotNet Framework: Graphics
dotNet offre gli strumenti base per la creazione di semplici elementi grafici (linee,
cerchi, ecc.), tutti contenuti nel namespace System.Drawing.
Attraverso gli strumenti offerti è possibile:
•Aggiungere forme all’UI in modo dinamico;
•Creare diagrammi;
•Editare e ridimensionare immagini;
•Cambiare il grado di compressione di una immagine;
•Zoommare immagini;
•Aggiungere un logo di copyright o del testo ad una immagine.
Tra le classi primarie di questo namespace troviamo: Bitmap, Brush, Font,
Graphics, Icon, Image e Pen.
Un ruolo importante rivestono inoltre le strutture di questo namespace (ad es.
Color o Point), che permettono di impostare o modificare gli elementi del disegno.
77. dotNet Framework: Graphics
Per disegnare una linea o una figura base, bisogna eseguire tre passi
fondamentali:
•Creare un oggetto Graphics, partendo dalla form o dal controllo attuale,
attraverso il metodo System.Windows.Forms.Control.CreateGraphics;
•Creare un oggetto Pen;
•Chiamare un metodo dell’oggetto Graphics per disegnare sul controllo usando
l’oggetto Pen
// Create a graphics object from the form
Graphics g = this.CreateGraphics();
// Create a pen object with which to draw
Pen p = new Pen(Color.Red, 7);
// Draw the line
g.DrawLine(p, 1, 1, 100, 100);
Graphics g = this.CreateGraphics();
Pen p = new Pen(Color.Blue, 3);
g.DrawPie(p, 1, 1, 100, 100, -30, 60);
78. dotNet Framework: Graphics
Invece di utilizzare l’oggetto Pen, in abbinamento con il metodo graphics.drawXXX,
è possibile utilizzare un oggetto Brush (e il metodo graphics.fillXXX) per poter
creare figure con riempimento.
Graphics g = this.CreateGraphics();
Brush b = new SolidBrush(Color.Maroon);
Point[] points = new Point[]
{new Point(10, 10),
new Point(10, 100),
new Point(50, 65),
new Point(100, 100),
new Point(85, 40)};
g.FillPolygon(b, points);
79. dotNet Framework: Graphics
Grazie alle implementazioni della classe astratta System.Drawing.Image, è
possibile elaborare immagini esistenti o crearne di nuove. Questa classe astratta
trova nel framework due implementazione:
•System.Drawing.Bitmp, per le immagini;
•System.Drwaing.Imaging.Metafile, per le immagini animate;
Nonostante Image sia una abastract class è possibile utilizzare i metodi
implementati per ottenerne un’istanza:
Image i = Image.FromFile(@"C:windowsgone fishing.bmp");
pictureBox1.BackgroundImage = i;
Se si vuole creare e salvare una
nuova immagine si lavoro pressappoco
come negli esempi precedenti,
eccetto che la creazione
dell’oggetto graphics avviene
attraverso un metodo apposito:
Bitmap bm = new Bitmap(600, 600);
Graphics g = Graphics.FromImage(bm);
Brush b = new LinearGradientBrush(new Point(1, 1),
new Point(600, 600), Color.White,Color.Red);
Point[] points = new Point[]
{new Point(10, 10),
new Point(77, 500),
new Point(590, 100),
new Point(250, 590),
new Point(300, 410)};
g.FillPolygon(b, points);
bm.Save("bm.jpg", ImageFormat.Jpeg);
80. dotNet Framework: Graphics
Attraverso le funzionalità di System.Drawing è possibile creare anche testo sotto
forma di immagine, utile, ad esempio, se si desidera “marchiare” una foto con un
proprio identificativo.
Graphics g = this.CreateGraphics();
Font f = new Font("Arial", 40, FontStyle.Bold);
g.DrawString("Hello, World!", f, Brushes.Blue, 10, 10);
Infine il namespace in esame prevede una classe apposita per
disegnare/visualizzare le icone di sistema:
Graphics g = this.CreateGraphics();
g.DrawIcon(SystemIcons.Question, 40, 40);
82. dotNet Framework: Threading
Grazie all’utilizzo dei Thread è possibile dotare l’applicazione di più flussi paralleli
che possono portare ad un sostanziale miglioramento prestazionale della stessa ed
a una migliore interazione utente-applicazione.
Il namespace di riferimento è il System.Threading, che annovera la classe Thread
e l’enumerazione ThreadState.
La creazione di un nuovo thread, effettuabile in qualsiasi parte del codice, avviene
attraverso i seguenti passi:
1. Creare un metodo senza parametri e senza argomenti di ritorno:
2. Creare un delegato ThredStart, specificando il metodo creato nel passo 1
3. Creare un nuovo oggetto Thread, specificando il treadstart creato al passo
2
4. Chiamare il metodo Thred.Start per avviare il thread
static void SimpleWork()
{
Console.WriteLine("Thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
83. dotNet Framework: Threading
In sintesi ecco il codice per avviare un thread:
Se è necessario passare informazioni al thread è possibile utilizzare il delegate
ParameterizedThreadStart:
ThreadStart operation = new ThreadStart(SimpleWork);
// Creates, but does not start, a new thread
Thread theThread = new Thread(operation);
// Starts the work on a new thread
theThread.Start();
ThreadStart operation = new ThreadStart(SimpleWork);
for (int x = 1; x <= 5; ++x)
{
Thread theThread = new Thread(operation);
theThread.Start();
}
static void WorkWithParameter(object o)
{
string info = (string) o;
for (int x = 0; x < 10; ++x)
{
Console.WriteLine("{0}: {1}", info,
Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(10);
}
}
ParameterizedThreadStart operation =
new ParameterizedThreadStart(WorkWithParameter);
Thread theThread = new Thread(operation);
theThread.Start("Hello");
hread newThread = new Thread(operation);
newThread.Start("Goodbye");
Il parametro passato deve essere sempre un oggetto.
84. dotNet Framework: richiamo
I DELEGATI (Delegate) sono una sorta di CallBack (per chi conosce C++) o più
semplicemente puntatori a funzione (C).
Essi vengono ampiamente usati in .Net per la gestione degli eventi, quindi per la
definizione dell’Handler.
La grande differenza rispetto ai linguaggi non gestiti è che i Delegati sono type-
safe, ovvero vengono gestiti esattamente come un oggetto la cui signature è
ben definita.
85. dotNet Framework: Threading
La classe Thread fornisce i metodi necessari per gestire in modo corretto il flusso di
eleborazione:
• Thread.Start, avvia il thread
• Thread.Join, ferma l’esecuzione del chiamante finchè il nuovo thread non
termina;
• Thread.Abort, abortisce l’esecuzione del thread e solleva una
ThreadAbortException;
• Thread.IsAlive, verifica se il thread corrente è in esecuzione;
• Thread.Priority, ritorna/setta la priorità del thread;
• Thread.IsBackground, ritorna/setta lo stato di background del thread;
86. dotNet Framework: Threading
Per consentire la condivisione di memoria (quindi dei parametri) tra i vari thread
evitando collisioni, il dotNet framework fornisce opportuni strumenti che ne
regolano l’accesso.
Se stiamo lavorando con variabili condivise ed abbiamo necessità di fare
operazioni di in(de)cremento possiamo avvalerci delle funzionlità della classe
Interlocked:
la classe interlocked “blinda” il parametro su cui opera, rendendo le operazioni
atomiche
static void UpdateCount()
{
for (int x = 1; x <= 10000; ++x)
{
Interlocked.Increment(ref Counter.Count);
}
}
87. dotNet Framework: Threading
Se abbiamo bisogno di “blindare” più istruzioni in modo da renderle atomiche,
possiamo ricorre al lock:
in realtà il lock è basato sulla classe Monitor che permette un più fine controllo sul
codice da blindare.
Il dotNet framework supporta inoltre i seguenti metodi di sincronizzazione:
• Mutex class
• Semaphore class
• AutoResetEvent class
• ManualResetEvent class
public void UpdateCount()
{
lock (this)
{
count = _count + 1;
if (Count % 2 == 0)
evenCount = evenCount + 1;
}
}
88. dotNet Framework: Threading
Non è sempre necessario creare un nuovo thread per avviare attività in parallelo.
Infatti il framework dotNet implementa l’ Asynchronous Programming Model (APM), che
permette l’esecuzione di porzioni di codice in thread separati.
Molte classi supportano APM fornendo le versioni BeginXXX e EndXXX di metodi classici.
Come esempio immaginiamo la lettura di un file: potremmo voler realizzare la lettura (lenta)
attraverso un flusso parallelo per non bloccare la fruibilità dell’applicativo. Invece di creare un
thread separato, possiamo procedere come di seguito:
byte[] buffer = new byte[100];
string filename = string.Concat(Environment.SystemDirectory, "mfc71.pdb");
FileStream strm = new FileStream(filename, FileMode.Open, FileAccess.Read,
FileShare.Read, 1024, FileOptions.Asynchronous);
// Make the asynchronous call
IAsyncResult result = strm.BeginRead(buffer, 0, buffer.Length, null, null);
// Do some work here while you wait
// Calling EndRead will block until the Async work is complete
int numBytes = strm.EndRead(result);
// Don't forget to close the stream
strm.Close();
Console.WriteLine("Read {0} Bytes", numBytes);
Console.WriteLine(BitConverter.ToString(buffer));
89. dotNet Framework: Threading
L’utilizzo del modello APM pone una domanda fondamentale: quando verificare la
fine del flusso parallelo avviato?
Possiamo individuare tre stili:
•Wait-until-done, consiste nell’avviare la richiesta, eseguire (eventualmente)
altre operazioni ed attendere il termine della richiesta;
•Polling, simile al precedente solo che c’è un controllo ciclico del termine della
richiesta;
•Callback, si crea un delegato che viene passato alla BeginXXX e viene
invocato dal framework al termine della richiesta.
L’ultimo aspetto da rilevare è che eventuali exception vengono sollevate solo in
seguito alla chiamata della EndXXX e non nel momento in cui si verificano
90. dotNet Framework: Threading
…..
// Make the asynchronous call
strm.Read(buffer, 0, buffer.Length);
IAsyncResult result = strm.BeginRead(buffer, 0, buffer.Length, null, null);
// Do some work here while you wait
// Calling EndRead will block until the Async work is complete
int numBytes = strm.EndRead(result);
…..
// Make the asynchronous call
IAsyncResult result = strm.BeginRead(buffer, 0, buffer.Length, null, null);
// Poll testing to see if complete
while (!result.IsCompleted)
{
// Do more work here if the call isn't complete
Thread.Sleep(100);
}
// Finished, so we can call EndRead and it will return without blocking
int numBytes = strm.EndRead(result);
….
91. ….
// Make the asynchronous call
IAsyncResult result = strm.BeginRead(buffer, 0, buffer.Length, new
AsyncCallback(CompleteRead), strm);
}
…
//Callback Metod
static void CompleteRead(IAsyncResult result)
{
Console.WriteLine("Read Completed");
FileStream strm = (FileStream) result.AsyncState;
// Finished, so we can call EndRead and it will return without blocking
int numBytes = strm.EndRead(result);
// Don't forget to close the stream
strm.Close();
Console.WriteLine("Read {0} Bytes", numBytes);
Console.WriteLine(BitConverter.ToString(buffer));
}
dotNet Framework: Threading
93. Spesso è necessario lavorare con assembly esterni, che possono impattare sia
sulle performace/stabilità del nostro applicativo, sia sulla sicurezza.
Il modo migliore per utilizzare un assembly esterno è quello di utilizzare un
Application Domain.
dotNet Framework: Application Domain & Services
Processo
AppDomain
Assembly
Shared Assembly
AppDomain
Assembly
Shared Assembly
Grazie agli Application Domain è possibile caricare, all’interno dello stesso
processo, più assembly separatamente tra di loro, in modo che eventuali anomalie
non vadano ad impattare l’intero processo o gli altri assembly
94. La classe che permette la gestione degli Application Domain è la
System.AppDomain. Vediamo ora alcuni esempi concreti di utilizzo degli
Application Domain:
dotNet Framework: Application Domain & Services
AppDomain d = AppDomain.CreateDomain("NewDomain");
AppDomain d = AppDomain.CreateDomain("NewDomain");
d.ExecuteAssemblyByName("Assembly");
AppDomain d = AppDomain.CreateDomain("NewDomain");
…..
AppDomain.Unload(d)
Creazione
Caricamento di un Assembly
Unload di un Assembly
E’ bene notare che se sa in anticipo di voler effettuare l’unload dell’assembly che si
va a caricare, l’unico modo per farlo è attraverso un Application Domain
95. Come ci si aspetterebbe, per poter controllare il livello di isolamento di un
Application Domain (quindi utilizzarlo come una sandbox) il dotNet framework
mette a disposizione una serie di strumenti per configurarlo, attraverso gli
Evidence, di cui discuteremo in seguito. Basti sapere che essi permettono di
classificare l’AppDomain ed assegnargli delle restrizioni e dei permessi sulle attività
da svolgere.
Possiamo applicare le restrizioni sia per un singolo assembly e sia per l’intero
Application Domain:
dotNet Framework: Application Domain & Services
object [] hostEvidence = {new Zone(SecurityZone.Internet)};
Evidence internetEvidence = new Evidence(hostEvidence, null);
AppDomain myDomain = AppDomain.CreateDomain("MyDomain");
myDomain.ExecuteAssembly("SecondAssembly.exe", internetEvidence);
object [] hostEvidence = {new Zone(SecurityZone.Internet)};
Evidence appDomainEvidence = new Evidence(hostEvidence, null);
AppDomain d = AppDomain.CreateDomain("MyDomain", appDomainEvidence);
d.ExecuteAssembly("SecondAssembly.exe");
Attraverso la classe System. AppDomainSetup è infine possibile controllare ogni
ulteriore aspetto di configurazione (dir di base, configuration file, ecc.)
96. I Windows Services (servizi di windows) sono una gamma di applicativi privi di UI
che, funzionando in background, svolgono attività cicliche monitorando lo stato del
sistema.
La creazione di un servizio avviene utilizzando il template di VS ServiceBase, ed
implementando il metodi OnStart e OnStop come minimo.
Successivamente bisogna creare un’Installer per il servizio che non può essere
avviato come semplici applicativo.
Una volta installato, il servizio può essere gestito sia attraverso il classici Strumenti
di Amministrazione di Windows che attraverso codice (quindi da un assembly
esterno) attraverso la classe System.ServiceProcess.ServiceController:
dotNet Framework: Application Domain & Services
// Connect to the Server service
ServiceController sc = new ServiceController("Server");
sc.Stop(); // Stop the service
// Wait two seconds before starting the service
Thread.Sleep(2000);
sc.Start(); // Start the service
98. dotNet Framework: Configuring Application
Parola d’ordine: evitare le configurazioni HardCoding, ovvero quelle inserite
all’interno del codice e quindi impossibili da modificare se non attraverso i sorgenti.
Sarà capitato a tutti, ad esempio, di inserire direttamente all’interno della propria
classe la stringa di connessione ad un DB e di dover poi rimettere mano al codice
perché l’RDBMS di rilascio usa credenziali diverse di quelle del sistama di Test
.
Ebbene, grazie agli strumenti messi a disposizione dal namespace
System.Configuration e ai file di confirgurazione XML sarà davvero semplice
evitare queste situazioni. Questo namespace include due classi principali:
ConfigurationManager e Configuration.
Di default ogni applicazione ha un proprio file di configurazione utilizzato per la sua
inizializzazione: nomeapplicazione.exe.conf
100. dotNet Framework: Configuring Application
In esso possiamo distinguere tre sezioni principali:
• <runtime>, in cui sono riportate le impostazioni riguardanti l’esecuzione
dell’assemby . Nel caso in esempio la versione del framework supportata.
•<appSettings>, sono parametri di cui si serve l’applicazione e che vengono
recuperati durante la sua esecuzione
•<connectionStrings>, questa sezione è dedicata a contenere esclusivamente
le stringhe di connessione alle base dati (possono essere più di una)
// C#
NameValueCollection AllAppSettings = ConfigurationManager.AppSettings;
Console.WriteLine(AllAppSettings["Foo"]);
Console.WriteLine(AllAppSettings[0]);
' VB
Dim AllAppSettings As NameValueCollection = ConfigurationManager.AppSettings
Console.WriteLine(AllAppSettings("Foo"))
Console.WriteLine(AllAppSettings(0))
Vediamo ora come recuperare i valori appSettings salvati nel file di Configurazione:
101. dotNet Framework: Configuring Application
La sezione ConnectionStrings è leggermente diversa poiché può contenere
dichiarazioni multiple:
possiamo sia recuperare la prima connectionString:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<clear/>
<add name="AdventureWorksString“ providerName="System.Data.SqlClient“ connectionString="Data
Source=localhost;Initial Catalog=AdventureWorks; Integrated Security=true"/>
<add name="MarsEnabledSqlServer2005String“ providerName="System.Data.SqlClient“
connectionString= "Server=Aron1;Database=pubs;
Trusted_Connection=True;MultipleActiveResultSets=true" />
</connectionStrings>
</configuration>
ConnectionStringSettings MySettings = ConfigurationManager.ConnectionStrings[0];
sia iterare su esse ottenendole la collection:
ConnectionStringSettingsCollection ConnectionStrings = ConnectionStringsSection.ConnectionStrings;
102. dotNet Framework: Configuring Application
Oltre al file di configurazione dell’applicazione dotNET prevede la possibilità di
creare molteplici file XML per evitare proprio l’hard coding e quindi contenere
coppie parametri/valori. Tali file vengono poi gestiti direttamente da una classe
wrapper specifica.
<userSettings>
<WindowsApplication2.SampleSettings />
</userSettings>
<applicationSettings>
<WindowsApplication2.SampleSettings>
<setting name="WebServiceUrl" serializeAs="String">
<value>http://www.adatum.com/myservice.asmx</value>
</setting>
</WindowsApplication2.SampleSettings>
</applicationSettings>
nell‘esempio sono visibili 2 aree userSettings e applicationSetting che
suddividono lo scope dei parametri.
103. dotNet Framework: Configuring Application
Per definire un nuovo file di configurazione ed auto-generare la relativa classe è
conveniente affidarsi a VS:
Aggiungere un nuovo elemento al
progetto di tipo Settings File
Dopo la creazione del Settings File, VS
aprirà un apposito Application Settings
Designer, che potrà essere utilizzato per
definire i parametri in ogni loro aspetto
(nome, tipo, visibilità-scope, valore).
104. dotNet Framework: Configuring Application
Salvando il nuovo file di settaggio, troveremo due nuovi elementi nel nostro
progetto:
1. Sample.settings, il file XML
2. Sample.cs (.vb), la relativa classe autogenerata
Per usare i parametri basta creare una nuova classe del tipo al punto 2 e
richiamarne le proprietà:
' VB
Dim mySettings as new SampleSettings()
Debug.WriteLine(mySettings.WebServiceUrl)
// C#
SampleSettings mySettings = new SampleSettings();
Debug.WriteLine(mySettings.WebServiceUrl);
106. dotNet Framework: Instrumentation
Sotto il nome Instrumentation Micorsoft ha raccolto tutti gli strumenti (ovviamente
parliamo di classi, interfacce, enumeratori, ecc.) utilizzabili per:
• Gestire i Log
• Effettuare il Debugging ed il Tracing
• Monitorare le Performance
• Controllare i Device di Sistema, le Applicazioni ed il Sistema stesso
Tutti questi “strumenti” sono contenuti nel namespace System.Diagnostics
Da notare che operando direttamente a contatto con il sistema e siano di semplice
utilizzo, essi possono impattare sulle performance del sistema ed inoltre provocare
problemi di sicurezza nelle classi partial-trust (in particolare per i Log).
Quindi essi vanno utilizzati con parsimonia e molta attenzione.
107. dotNet Framework: Instrumentation
Praticamente è impossibile conoscere tutti gli scenari in cui la nostra applicazione
verrà eseguita, vista l’infinita possibilità di combinazioni Hw/Sw oggi possibili.
Inoltre nonostante ci si sforzi di creare applicazioni prive di bug, qualcuno, prima o
poi, ne verrà fuori.
Il problema è che sono sempre gli utenti finali ad accorgersi dei bug più insidiosi, e
difficilmente essi riescono a segnalare il problema in modo corretto (in fin dei conti
perché dovrebbero saperlo farlo, non sono certo dei tecnici!).
Proprio in questo scenario ci vengono in aiuto gli strumenti di Logging Events,
che permettono alla nostra applicazione di inserire informazioni specifiche
all’interno dei log (siano essi quelli di sistema o quelli proprietari dell’applicazione).
Tutto il meccanismo si basa sulla classe EventLog, attraverso la quale scrivere o
legge un evento in un log è un’operazione banale:
public static void CreateEventLog()
{
EventLog DemoLog = new
EventLog("Chap10Demo");
DemoLog.Source = "Chap10Demo";
DemoLog.WriteEntry("CreateEventLog
called", EventLogEntryType.Information);
}
public static void ReadEventLog()
{
EventLog DemoLog = new EventLog();
DemoLog.Log = "Chap10Demo";
foreach (EventLogEntry DemoEntry in
DemoLog.Entries)
{
Console.WriteLine(DemoEntry.Source +
":" + DemoEntry.Message);
}}
108. dotNet Framework: Instrumentation
La fase di Debugging è una delle più importanti nello sviluppo di un nuovo
applicativo, in quanto permette di analizzarne il comportamento alla ricerca di errori
nella logica di funzionamento.
Il dotNet framework basa l’azione di debugging su due classi fondamentali (sempre
del namespace Systems.Diagnostics): Debugger e Debug
Debugger permette di abilitare la connessione ad un debugger (operazione svolta
automaticamente dall’IDE VS). Infatti difficilmente si utilizzerà in modo diretto tale
classe, essendo possibile utilizzare gli strumenti integrati dell’IDE.
Ad esempio uno dei metodi di Debugger è debugger.Break() che permette di
inserire un punto di break-point all’interno del codice, esattamente come si fa
cliccando al lato sinistro in Visual Studio.
Prima di proseguire con la più interessante classe Debug è utile sottolineare che
tutte le operazioni connesse vengono eseguite solo se l’ambiente si è in fase di
debugging. In fase di compilazione per deployment, le righe di codice relative
vengono scartate. Nel caso si vogliano utilizzare tali funzionalità anche in ambiente
di deployment si fa ricorso alla classe Trace funzionalmente identica.
109. dotNet Framework: Instrumentation
La classe Debug consente un controllo fine sul processo di debugging, attraverso
metodi diretti, tra cui il più importante è sicuramente Debug.Assert.
Assert permette di testare se una determinata condizione è vera e, in caso
affermativo, generare un alert:
Similare ad Assert è Fail che permette di lanciare un alert senza verificare una
condizione.
Altri metodi presenti sono: debug.write, debug.writeif, debug.writeline,
debug.writelineif, debug.print, debug.flush.
NameValueCollection MySettings = ConfigurationManager.AppSettings;
Debug.Assert(MySettings != null && MySettings["Foo"] != null,
"There was a problem with either the configuration file or the Foo Setting");
Console.WriteLine(MySettings["Foo"]);
110. dotNet Framework: Instrumentation
Il dotNet framework permette di personalizzare la visualizzazione delle informazioni
che il debugger visualizza e come esso si comporta attraverso opportuni attributi,
che andiamo ad elencare:
•DebuggerBrowsableAttribute
•DebuggerDisplayAttribute
•DebuggerHiddenAttribute
•DebuggerNonUserCodeAttribute
•DebuggerStepperBoundaryAttribute
•DebuggerStepThroughAttribute
•DebuggerTypeProxyAttribute
•DebuggerVisualizerAttribute
la loro utilità dipende dal grado di dettaglio che lo sviluppatore vuole ottenere
durante il debugger e una loro padronanza è possibile solo con l’utilizzo pratico.
111. dotNet Framework: Instrumentation
Anche l’applicazione scritta in modo perfetto (teroricamente) non troverà
gradimento da parte del cliente se non risponde in modo tempestivo alle sue
richieste: ecco il concetto di performace.
Non ci addentreremo nel dettaglio di queste funzionalità (gestite attraverso le classi
Process e il componente/oggetto PerformanceCounter) anche perché si tratta di
attività di ottimizzazione dell’applicazione che vengono, purtroppo, raramente
effettuate.
La cosa da tener presente è che quando si verifica un problema non sempre esso
è dovuto ad un errore o un flusso di istruzioni errate, ma può anche essere dovuto
a scarse performance dell’ambiente che il cliente sente come errore
dell’applicativo.
112. dotNet Framework: Instrumentation
L’ultimo set di strumenti messi a disposizione dal namespece Systems.Diagnostics
sono i Windows Management Instrumentation (WMI).
Attraverso di essi è possibile ottenere informazioni riguardanti ogni aspetto delle
risorse del computer, come: Drive, Adattatori di Rete, Servizi, ecc.
WMI utilizza una serie di passi ed un linguaggio di interrogazione simile alle query
SQL, attraverso la classe DirectoryObjectSearcher. Infatti per ottenere
informazioni specifiche bisogna:
1. Dichiarare un’istanza di ConnectionOptions, e settare le proprietà UserName e
Password (per motivi di security di accesso);
2. Dichiarare un’istanza di DirectoryObjectSearcher;
3. Creare un oggetto ManagementScope e settarne le proprietà PathName e
ConnectionOptions
4. Creare un oggetto ObjectQuery e specificare la query da eseguire;
5. Creare un oggetto ManagementObjectCollection, e assegnargli il valore di ritorno
del metodo DirectoryObjectSearcher.Get;
113. dotNet Framework: Instrumentation
Vediamo un esempio concreto che consente l’enumerazione dei drive presenti nel sistema:
ConnectionOptions DemoOptions = new ConnectionOptions();
DemoOptions.Username = "Bill";
DemoOptions.Password = "mp99!!swa0";
ManagementScope DemoScope = new ManagementScope("machinename", DemoOptions);
ObjectQuery DemoQuery = new ObjectQuery("SELECT Size, Name FROM Win32_LogicalDisk
where DriveType=3");
ManagementObjectSearcher DemoSearcher = new ManagementObjectSearcher(DemoScope, DemoQuery);
ManagementObjectCollection AllObjects = DemoSearcher.Get();
foreach (ManagementObject DemoObject in AllObjects)
{
Console.WriteLine("Resource Name: " + DemoObject["Name"].ToString());
Console.WriteLine("Resource Size: " + DemoObject["Size"].ToString());
}
In modo altrettanto semplice è possibile ottenere informazioni riguardanti i Servizi di Sistema:
private static void ListPausedServices()
{
ManagementObjectSearcher DemoSearcher = new ManagementObjectSearcher("SELECT * FROM
Win32_Service WHERE Started = FALSE");
ManagementObjectCollection AllObjects = DemoSearcher.Get();
foreach (ManagementObject PausedService in AllObjects)
{
Console.WriteLine("Service = " + PausedService["Caption"]);
}}
115. dotNet Framework: Application Security
Realizzare applicazioni SICURE che non effettuino operazioni indesiderate e non
consentite sui sistemi degli utilizzatori è ormai diventato un must.
Attraverso le CAS, Code Access Security, è possibile limitare in modo granuale quello che il
managed code può fare.
Il concetto è simile ai diritti applicati ai ruoli degli utenti/gruppi (Role Based Security – RBS)
all’interno del Sistema Operativo.
Con CAS è possibile controllare l’accesso a:
• File System
• Registro
• Stampanti
• Log degli Eventi
• Accesso al Web
• …
CAS è utilizzabile solo per controllare managed code, mentre il codice non gestito
(come, tra l’altro, il codice gestito Full-Trust) non è soggetto alle restrizioni.
Inoltre in nessun caso il codice eseguito può avere più permessi di quelli
concessi all’utente che lo esegue.
CAS si compone di 5 elementi fontamentali: Evicende, Permission, Permission
Sets, Code Group e Security Policy.
116. dotNet Framework: Application Security
Prima di continuare è utili far presente che la difficoltà maggiore nell’apprendere le
CAS è nella terminologia usata, piuttosto che negli strumenti in se.
• Evidence Prove di protezione
• Strong name Nome sicuro
• Code Group Gruppi di codice
• Full trust Attendibilità totale
• Partial trust Attendibilità parziale
• Security Policy ...
• Permission Autorizzazioni
• Permission Set Set di autorizzazioni
• Stack Walk ...
• Assert ...
• Demand ...
• ... ...
117. dotNet Framework: Application Security
Per riconoscere e classificare un assembly CAS utilizza l’EVIDENCE (ovvero: chi
sei?), una sorta di identificativo dell’assembly.
Evidence è la prova oggettiva per sapere di chi è o da dove proviene il codice
managed
La CAS può identificare un assembly basandosi su:
•Cartella dove risiede
•Sito o Url da cui viene scaricato
•Zona in cui si trova
•Strong Name presente nel suo manifest
•Hash dell'assembly
•Firma digitale (Authenticode, stessa degli Activex)
Al caricamento dell'assembly in memoria, viene subito ricavata l'evidence.
È possibile da codice associare una evidence ad un AppDomain in modo da isolare
degli assembly.
118. dotNet Framework: Application Security
CAS utilizza le PERMISSION (ovvero: cosa vuoi fare?) per consentire ad un
assembly di porter effettuare determinate operazioni (per esempio l’apertura di una
File Dialog Box).
Il dotNet framework ne predefinisce un certo numero, trasversali a quelli più noti
come NTFS, SQL, etc.
Per ciascun permesso è possibile definire dei valori molto dettagliati, come mostra
l’empio relativo all’accesso ai socket:
dotNet prevede 19 permessi base contenuti nel namespace
System.Security.Permissions, realative ad altrettante attività che possono
impattare sulla sicurezza del sistema.
Questi 19 permessi vengono spesso combinati tra loro per creare i Permission
Sets ovvero set di permessi da applicare unitariamente ad un assembly.
119. dotNet Framework: Application Security
Il dotNet framework prevede 7 Permission Sets di default:
Permission Set Description
FullTrust Esenta l’Assembly dall’essere sottoposto alle restirzioni CAS
SkipVerification Permette all’Assembly di baipassare i controlli, aumentando le
performance a scapito della sicurezza.
Execution Garantisce all’Assembly solo il permesso di esecuzione e nessun altro
Nothing Nessun permesso concesso, nemmeno quello di esecuzione
LocalIntranet Garantisce un consistente numero di permessi, compreso quello di
stampa e di accesso ai logo. L’accesso al File System è invece limitato alla
sola apertura delle Open/Save dialog Box
Internet Garantisce un ristretto numero di permessi, proprio in virtù della
provenienza dell’assembly (internet) da un ambiente potenzialmente
ostile.
Everithing Garantisce tutti i permessi, ma a differenza del FullTrust l’assembly è
comunque soggetto al controllo CAS.
120. dotNet Framework: Application Security
In pratica grazie all’evidenze ed ai permission il codice gestito viene prima
controllato e se le autorizzazioni di cui dispone sono sufficiente all’attività che si
appresta a svolgere, ciò viene concesso.
Esecuzione
Permission
Evidence
CodiceAutenticazione
Autorizzazione
CAS
121. dotNet Framework: Application Security
Grazie ai Code Groups è possibile collegare indirettamente i Permission Sets agli
Assembli, consentendo in tal modo di riflettere una modifica del Pemission Sets
applicato al Code Groups a tutti gli assembly relativi.
Lo schema segue l’assegnazione degli utenti ai gruppi a cui sono associati i
permessi (RBS Role Based Security).
Permissi
on Sets
Code
Grups
Assembly
(assegnati al
gruppo
attraverso
l’evidence)
Di default esistono 5 Code Groups:
Code Group Evidence Permission Set
My_Computer_Zone Zone: My
Computer
FullTrust
LocalIntranet_Zone Zone: Local
Intranet
LocalIntranet
Internet_Zone Zone: Internet Internet
Restricted_Zone Zone: Untrusted
sites
Nothing
Trusted_Zone Zone: Trusted
sites
Internet
Ovviamente un assemby può
appartenere a più Code Grups. In tal
caso i permessi sono Il risultato
dell’unione dei permessi dei singoli
gruppi.
122. dotNet Framework: Application Security
L’ultimo elemento che compone le CAS è la SECURITY POLICY un insieme di
code group e permission sets che definiscono le politiche (policy) di sicurezza del
sistema.
Per default esistono 4 policy level di defalut: Enterprise, Machine (o Computer),
User (o Utente) e Application Domain, che consentono un controllo minuto dei diritti
concessi.
Come è facile intuire dai nomi essi vanno dal livello più alto dell’ambiente
(Enterprise) a quello più ristretto (Application Domain).
Volendo essere semplici possiamo dire che le restrizioni Enterprise si applicano a
tutto l’ambiente fino a giungere alle restrizioni di Application Domain specifiche del
dominio di esecuzione.
I diritti di cui un assembly dispone sono il risultato dell’intersezione delle policy dei
vari livelli
Enerprise
Machine
User
App. Domain
123. Al runtime viene fatto il calcolo dei permessi effettivi:
1.
2.
Unione
Intersezione
void SetAppDomainPolicy(PolicyLevel domainPolicy);
dotNet Framework: Application Security
124. dotNet Framework: Application Security
CAS può essere configurato sia tramite riga di comando, attraverso l’utility CasPol,
sia attraverso il Framework Configuration Toolkit
Code Groups
Permission Set
Security Policy
Policy Assemblies
125. dotNet Framework: Application Security
Fin ora abbiamo visto come gestire/impostare le CAS attraverso gli strumenti di sistema forniti
dal framework.
Ovviamente esiste la possibilità di impostare le permission di un assembly in modo esplicito
direttamente dal codice.
Per fare ciò è possibile ricorre a due soluzioni:
1. Declarative Security, fa uso di attributi ed è l’unico applicabile all’intero assembly;
2. Imperative Security, fa uso di apposite classi ed è utilizzabile per proteggere
specifiche porzioni di codice.
L’utilizzo degli attributi (quindi declarative) è utile anche all’amministratore di sistema per
determinare di quali diritti ha bisogno il nostro assembly.
[assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read=@"C:boot.ini")]
namespace DeclarativeExample
{
class Program {
…. Dichiarativo
Try {
// Assembly logic
}
Catch {
EventLogPermission errorPerms = new EventLogPermission(PermissionState.Unrestricted);
errorPerms.PermitOnly();
// Log event
CodeAccessPermission.RevertPermitOnly();
} Imperativo
126. Lo strumento principale sono le classi xxxPermission e relativi attributi che
permettono di chiedere alla CAS di eseguire controlli e, se necessario
lanciare la SecurityException
ISecurityEncodable
IStackWalk
IPermission
FileIOPermission UIPermission EnvironmentPermssion
PermissionSet
FileIOPermission
Attribute
UIPermission
Attribute
EnvironmentPermssion
Attribute
CodeAccessPermission
Attribute
CodeAccessPermission
dotNet Framework: Application Security
127. Nel caso si voglia applicare la CAS ad un assembly (obbligatoriamente in modo
Dichiarativo), bisogna definire la Action Property secondo uno dei seguenti valori
dell’enumerazione SecurityAction:
• RequestMinimum. La permission indicata è indispensabile per l'assembly
altrimenti l'assembly non viene caricato.
• RequestOptional. Permessi aggiuntivi rispetto al minimo indispensabile. Vengono
dati all'assembly solo se disponibili.
• RequestRefuse. Permessi che questo assembly decide di togliersi per evitare di
essere usato da codice malicious.
Se sono specificati RequestOptional e RequestRefuse allora verranno attribuiti
all'assembly solo quei permessi anche se le policy sono più alte.
Il tool PermView permette di vedere nell'assembly i requisiti di security
dotNet Framework: Application Security
128. SecurityException
• Demand
• disponibile anche come attributo
• verifica il permesso richiesto eseguendo lo
stack walk
• notare che Metodo4 non è soggetto ad alcun
controllo. Il controllo è sui chiamanti
• LinkDemand
• solo come attributo
• verifica il permesso richiesto solo sul chiamante
senza eseguire stack walk
• il controllo viene eseguito dal jitter
• InheritanceDemand
• solo come attributo
• verifica il permesso richiesto sulle classi che
derivano da quella su cui è applicato l'attributo
• il controllo viene eseguito al load
OK
Demand
Stack walk
Metodo1
Metodo2
Metodo3
Metodo4
permission
negata
Stack walk
// Richiesta imperativa ...
FileIOPermission perm = new FileIOPermission(
FileIOPermissionAccess.AllAccess, @"c:");
perm.Demand();
// ... oppure dichiarativa (classe/metodo)
[assembly:FileIOPermission(
SecurityAction.Demand, All = @"C:") ]
dotNet Framework: Application Security
Nel caso si voglia applicare la CAS a elementi del codice come classi, metodi o
parte di esso (in modo Diarativo o Imperativo), bisogna definire i cosiddetti stack
walk modifiers:
129. • Assert e RevertAssert
• Impediscono che la richiesta di permesso
si propaghi ai chiamanti
• Assert si chiama al più tardi e
RevertAssert al più presto
• Utile per evitare perdite di performance
dovute al controllo della CAS
• Deny e RevertDeny
• Impedisce che tutti i metodi chiamati da questo possano accedere alla risorsa (...)
• PermitOnly e RevertPermitOnly
• Permette a tutti i metodi chiamati da questo di poter accedere solo alla risorsa
specificata (...)
Gli stack walk modifiers vengono rimossi automaticamente alla fine del
metodo che li chiama oppure chiamando esplicitamente il corrispondente
Revert
Deny e PermitOnly si usano su un metodo chiamante mentre RequestRefuse
solo sull'assembly chiamato
OK
Demand
Stack walk
Metodo2
Metodo3
Metodo4
permission
negata
Assert
dotNet Framework: Application Security
Metodo1
130. dotNet Framework: Application Security
[FileIOPermission(SecurityAction.Demand, Write = @"C:Program Files")]
public static void createProgramFolder()
{
// Method logic
}
[WebPermission(SecurityAction.Demand, ConnectPattern = @"http://www.microsoft.com/.*")]
public static void requestWebPage()
{
public static void createProgramFolder()
{
try {
FileIOPermission filePermissions = new FileIOPermission(FileIOPermissionAccess.Write, @"C:Program Files");
filePermissions.Demand();
// Method logic
}
catch {
// Error-handling logic
}
}
public static void requestWebPage()
{
try {
Regex connectPattern = new Regex(@"http://www.microsoft.com/.*");
WebPermission webPermissions = new WebPermission(NetworkAccess.Connect, connectPattern);
webPermissions.Demand();
// Method logic
}
catch {
// Error-handling logic
}
}
Vediamo due esempi di una stessa restrizione attraverso l’uso dichiarativo e imperativo:
Dichiarativo
Imperativo
131. dotNet Framework: Application Security
Fin qui il discorso relativo alle CAS.
Poniamoci però un dubbio: cosa accade se il nostro applicativo ha bisogno di uno spazio
temporaneo su disco ma non ha sufficienti permission?
In tal caso si ricorre all’Isolated Storage, ovvero un area protetta e riservata su disco su cui
l’assembly può lavorare senza problemi.
Si può definire uno storage isolato sulla base di:
• Identità del codice dell'applicazione
• Identità dell'utente
e gestirlo attraverso il namespace dedicato: System.IO.IsolatedStorage
Come è prevedibile la lettura/scrittura nello storage isolato avviene tramite stream:
IsolatedStorageFile userStore = IsolatedStorageFile.GetUserStoreForAssembly();
IsolatedStorageFileStream userStream = new IsolatedStorageFileStream("UserSettings.set",
FileMode.Create, userStore);
StreamWriter userWriter = new StreamWriter(userStream);
userWriter.WriteLine("User Prefs");
userWriter.Close();
132. dotNet Framework: Application Security
Abbiamo detto che il CAS è applicato al codice managed e agli assembly Partially Trusted.
Se vogliamo, invece, rendere un assemply Full Trusted, dobbiamo firmarlo, ovvero
assegnargli uno Strong Name
Le specifiche ECMA raccomandano l'uso dello strong name (che dovrebbe essere "aziendale"
e ben custodito) solo per il versioning e MAI per sicurezza, questo perché esistono sistemi
per ingannare il CLR e far caricare un Assembly tampered anche se firmato con strong name.
Comunque lo Strong Name è necesarrio qualora si voglia registrare l’assembly nella GAC
(Global Assembly Cache)
Infine, se un assembly partially trusted chiama un full trusted con strong name, questo
necessita dell'attributo: [assembly:AllowPartiallyTrustedCallers]
134. dotNet Framework: Interoperation
Se si decide di utilizzare le potenzialità del dotNet Framework possiamo affidarci
tranquillamente alla sua infrastruttura e guardare con convinzione al managed code.
Esiste tuttavia la possibilità che, per svariati motivi, ci si trovi in una delle situazioni seguenti:
• necessità di riutilizzare componenti COM (Component Object Model) proprietari;
• necessità di creare componenti COM da integrare in sistemi legacy preesistenti
• necessità lavorare direttamente con le API di sistema;
Per assisterci in tutti e tre i casi, dotNet ci viene in aiuto con gli strumenti messi a disposizione
dal namespace: System.Runtime.InteropServices
135. dotNet Framework: Interoperation
Mettiamo che la nostra software house abbia impiegato mesi per sviluppare uno
specifico componente COM (analisi, progettazione, sviluppo, testing,
collaudo,ecc.), myCom.
Ora nuove necessità hanno portato l’azienda a sposare il dotNet Framework.
In questo nuovo contesto si desidera però recuperare myCom.
Come si può procedere?
Anche in questo caso il dotNet Framework ci da una mano, mettendoci a
disposizione gli strumenti per creare un wrapper chiamato Runtime Callable
Wrapper (RCW) che lavoro secondo il pattern proxy.
Detto in soldoni: si esegue un automatismo del framework (o
Visual Studio), si ottiene il wrapper e lo si usa come una normale
l libreria dotNet.
Purtroppo non sempre i tool automatizzati riescono a generare
l’RCW.In tal caso bisogna procedere manualmente come
mostrato a breve.
136. dotNet Framework: Interoperation
Per creare RCW possiamo servirci di Visual Studio o del tool da riga di comando
TlbImp.exe.
Nel caso si usi VS, basta selezionare il
progetto (nella finestra Esplora Soluzioni),
clicckare con il tasto destro e scegliere
Aggiungi Riferimento.
Nella finestra che si aprirà scegliere il Tab
“COM” e quindi il proprio componente.
Una volta confermata l’operazione VS, se tutto
va a buon fine,aggiunge il riferimento
dell’oggetto COM alla libreria e a quel punto è
possibile utilizzarlo come un normale
componete/libreria dotNET.
Se si desidera operare attraverso TlbImp, basta procedere dando il seguente comando:
il risultato sarà una libreria .dll con lo stesso nome dell’originale, da
referenziare nel nostro progetto dotNet.
tlbimp <dllname>.dll
137. dotNet Framework: Interoperation
Nell’uso di oggetti COM in .NET bisogna fare attenzione in particolare a due aspetti:
• COM supporta i parametri opzionali (così come VBASIC),mentre ciò non è vero per
C#. Per poter essere eseguito bisogna, in C#, comunque passare tutti i parametri, anche
se opzionali. dotNet in tal senso introduce un oggetto vuoto attraverso la classe
Type.Missing, che è possibile utilizzare allo scopo ed evidenziare subito il suo
significato (evitando la confusione che si creerebbe con oggetti “dummy” ovvero creati
solo per sopperire al problema). In realtà una best-practices è quella di usare tali oggetti
anche in VB, poiché Microsoft sembra intenzionata ad eliminare il supporto ai parametri
opzionali
' VB
Imports Microsoft.Office.Core
Imports Microsoft.Office.Interop.Excel
Dim NewExcelApp As New Microsoft.Office.Interop.Excel.Application
'This works fine
NewExcelApp.Worksheets.Add()
// C#
using Microsoft.Office.Core
using Microsoft.Office.Interop.Excel
Application NewExcelApp = new Application();
// This will not comipile.
NewExcelApp.Worksheets.Add();
138. dotNet Framework: Interoperation
Best practices
' VB
Module Module1
Private OptionalParamHandler As Object = Type.Missing
Sub Main()
Dim NewExcelApp As New Microsoft.Office.Interop.Excel.Application
NewExcelApp.Worksheets.Add(OptionalParamHandler, OptionalParamHandler, _
OptionalParamHandler, OptionalParamHandler)
End Sub
End Module
// C#
class Program
{
private static Object OptionalParamHandler = Type.Missing;
static void Main(string[] args)
{
Application NewExcelApp = new Application();
NewExcelApp.Worksheets.Add(ref OptionalParamHandler, ref OptionalParamHandler,
ref OptionalParamHandler, ref OptionalParamHandler);
} }
139. dotNet Framework: Interoperation
Il Secondo aspetto da tenere presente è la Gestione delle Eccezioni, ovvero come catturare
e rispondere alle eccezioni generate da un oggetto COM – RCW.
A differenza di quanto accedeva con la versione 1.x del dotNet Framework, nella versione 2 è
stata introdotta la classe RuntimeWrappedException appartenente al namespace
System.Runtime.CompilerServices, che permette di gestire le eccezioni COM esattamente
come se fossero eccezioni standard del CLS.
Al verificarsi di un’eccezione COM, dotNet crea un oggetto di tipo
RuntimeWrappedException, ed assegna alla sua proprietà WrappedException l’oggetto
stesso che ha catturato l’eccezione per consentire di gestirlo in modo appropriato.
Se per qualsiasi motivi si dovesse avere la necessità di gestire eventuali eccezioni COM
attraverso il nuovo formato, è possibili disabilitarle attraverso appositi attributi:
' VB
Imports System.Runtime.CompilerServices
[assembly: runtimeCompatibility(WrapNonExceptionThrows=false)]
// C#
using System.Runtime.CompilerServices;
[assembly: RuntimeCompatibility(WrapNonExceptionThrows=false)]
140. dotNet Framework: Interoperation
Private Sub IllustrateExceptions()
Try
‘Something that throws an exception
Catch ex As Exception
' In the previous versions this will catch only CLS-Compliant
' In the current version both CLS and Non CLS-Compliant will be caught by this block.
End Try ' There is no equivalent for Catch without an exception
' because it's considered unreachable.
End Sub
private static void IllustrateExceptions()
{
try{
// Something that throws an exception
}
catch (Exception ex){
// In the previous versions this will catch only CLS-Compliant
// In the current version both CLS and Non CLS-Compliant will be caught by this block.
}}
Gestione Eccezioni COM “2.0”
141. dotNet Framework: Interoperation
Oltre ad avere la necessità di riutilizzare componenti COM, potremmo trovarci nella situazione
di doverne creare di nuovi, per poter aggiungere funzionalità ad applicazioni esistenti.
Anche i tal caso dotNet ci viene in aiuto con un wrapper apposito: COM Callable
Wrapper (CCW), attraverso il quale possiamo esportare il componente scritto in dotNet in
modo che sia compatibile con COM.
Per creare la nostra nuova libreria COM-Compatibile usando VS, dobbiamo :
1. Creare un nuovo progetto
2. Aprire la dialog box “Propietà del Progetto”, clicckando con il tasto destro sul
progetto e scegliendo proprietà.
3. Spuntare l’opzione Registra per COM
4. Compilare il tutto
Creato il componente sarà il compilatore a preoccuparsi
di aggiungere le necessarie informazioni per renderlo
compatibile con COM.
142. dotNet Framework: Interoperation
Per creare correttamente un componente esportabile in COM, bisogna seguire alcune regole:
• Tutte le classi devono avere un costrutto senza parametri esplicitamente
dichiarato;
• Tutti i tipi che si vuole rendere visibili devono essere pubblici;
• Tutti i metodi che si vuole rendere visibili devono essere pubblici;
• Le classi Astratte non possono essere esportate;
Public Class ComVisiblePerson
Private _firstName As String
Private _lastName As String
Public Property FirstName() As String
Get
Return Me._firstName
End Get
Set(ByVal value As String)
Me._firstName = value
End Set
End Property
Public Property LastName() As String
Get
Return Me._lastName
End Get
Set(ByVal value As String)
Me._lastName = value
End Set
End Property
End Class
namespace NetForComDemoCS
{
class ComVisiblePerson
{
private String firstName;
private String lastName;
public String FirstName
{
get { return firstName; }
set { firstName = value; }
}
public String LastName
{
get { return lastName; }
set { lastName = value; }
}
}
}
Attraverso appositi attributi è
poi possibile indicare quali
Assembly e Metodi devono
essere visibili o meno come
COM:
' VB
<Assembly: ComVisible(False)>
// C#
[assembly: ComVisible(false)]
' VB
<ComVisible(False)> _
Public Class ComVisiblePerson
// C#
[ComVisible(false)]
class ComVisiblePerson
143. dotNet Framework: Interoperation
Abbiamo parlato fin ora di oggetti COM, ma cosa accade se abbiamo bisogno di utilizzare una
libreria non conforme allo standard COM o API di sistema non supportate direttamente dal
framework?
Utilizziamo il cosiddetto Platform Invoke (P/Invoke), ovvero andiamo a wrappare
manualmente la nostra dll:
Imports System.Text
Imports System.Runtime.InteropServices
Public Class WindowExample
Private Const BufferSize As Int32 = 256
<DllImport("user32.dll")> _
Private Shared Function GetForegroundWindow()
As IntPtr
End Function
<DllImport("user32.dll")> _
Private Shared Function GetWindowText(ByVal _
hWnd As IntPtr, ByVal textValue As StringBuilder,
ByVal counter As Int32) As Int32
End Function
…
using System.Runtime.InteropServices;
namespace OptionalCS
{
class WindowExample
{
private const Int32 BufferSize = 256;
[DllImport("user32.dll")]
private static extern IntPtr
GetForegroundWindow();
[DllImport("user32.dll")]
private static extern Int32 GetWindowText(IntPtr
hWnd, StringBuilder textValue, Int32 counter);
….
144. dotNet Framework: Interoperation
Il modo migliore per lavoare con P/Invoke, è quello di creare una classe wrapper per ogni dll
che si vuole utilizzare (Encapsulating) e utilizzare solo quest’ultima all’interno del codice.
Questo “incapsulamento” porta a oggettivi vantaggi, quali:
• Gli utilizzatori della classe non si renderanno conto del fatto che sia una classe-
wrapper di una dll
• L’utilizzo nel proprio progetto della dll sarà nascosto, evitando di usare i
formalismi di P/Invoke che possono confondore
Nell’uso di P/Invoke bisogna fare molta attenzione alla tipologia dei dati trattati: infatti non è
detto che i vari tipi corrispondano tra loro. Bisogna quindi prendere le necessarie
contromisure (Data Type Converting e Marshaling) per ovviare ai problemi.
Inoltre bisogna ricordarsi che le CallBack (ovvero le chiamate a funzione) devono essere
mappate attraverso opportuni Delegati, da definire in base alla signature della callback
stessa.
146. dotNet Framework: Reflection
Attraverso la Reflection dotNET mette a disposizione un elaborato strumento che permette di
effettuare ad un programma l’auto analisi della sua struttura.
Attraverso la reflection è possibile, ad esempio, conoscere i tipi contenuti nell’assembly o il
Modulo a cui appartengono o ancora i suoi metadati.
Tutto questo è possibile attraverso le funzionalità messe a disposizione dal namespace
System.Reflection e dalla classe statica Assembly
147. dotNet Framework: Reflection
La domanda è: a cosa serve la Reflection?
Attraverso la Reflection è possibile ottenere informazioni da un assembly a runtime e quindi,
ad esempio, invocare un metodo costruendone dinamicamente la chiamata. Tutti noi
utilizziamo il completamento automatico dei metodi (e non solo) messo a disposizione di VS:
ebbene l’IDE Microsoft riesce a fare ciò proprio grazie alla reflection.
Questo strumento ha anche un’ulteriore potenzialità: creare un assembly runtime e addirittura
salvarlo su disco.
Per quanto riguarda la Reflection non andiamo oltre, essendo un argomento avanzato e che
necessita di approfondimenti ad-hoc.
149. dotNet Framework: Globalization
Il contesto geografico in cui un software verrà utilizzato è spesso un fattore sottovalutato
durante la sua realizzazione.
Immaginate però quanti problemi sorgono se il nostro prodotto inizialmente pensato solo per il
mercato italiano deve essere adattato a quello francese o a quello inglese.
dotNET viene incontro ai problemi di internazionalizzazione grazie al namespace
System.Globalization.
Partiamo però dal chiarire cosa intendiamo per Cultura (culture): per cultura si intendono un
insieme di elementi (calendario, valuta, lingua, ecc.) specifici di un determinata nazione o
area geografica.
Esistono tre tipologie di culture:
• Invariant Culture: è una cultura invariante (basata sul linguaggio inglese) da utilizzare
nel caso in cui si voglia garantire una sorta di consistenza internazionale degli elementi
culturali (es: data e ora di una trial application);
• Neutral Culture: una cultura è neutrale quando è associata solo ad una lingua
(Francese, Inglese, Tedesco) ma non alla rispettiva nazione o area geografica. Essa
viene indicata con le iniziali minuscole della lingua: fr, en, sp, it;
•Specific Culture: una cultura specifica è relativa ad una nazione e viene rappresentata
dalla cultura neutrale più l’abbreviazione maiuscola di quella specifica: fr-FR (francia)
en-US (stati uniti), en-EN (inghilterra);
150. dotNet Framework: Globalization
L’utilizzo della cultura appropriata è fondamentale quando, su sistemi internazionali, si
desidera che i valori analizzati siano interpretati allo stesso modo.
Prendiamo ad esempio una data italiana composta da gg/mm/aa:
05/11/2007 (5 novembre 2007).
Per noi è chiaro che le prime due cifre rappresentano il giorno, ma se la facciamo leggere ad
un americano la interpreterà come:
l’11 Maggio 2007 (mm/gg/aa).
Stesso problema relativo al significato del (.) e della (,) nei numeri.
La soluzione dotNET passa attraverso 2 classi principali CultureInfo e RegionInfo ed un
enumeratore CultureTypes.
151. dotNet Framework: Globalization
Vediamo alcune situazioni pratiche.
Per rilevare la cultura corrente possiamo procedere nel modo seguente:
Dim UsersCulture As CultureInfo = Thread.CurrentThread.CurrentCulture
Console.WriteLine("The currentculture of this application is : “ + UsersCulture.Name)
CultureInfo UsersCulture = Thread.CurrentThread.CurrentCulture;
Console.WriteLine("The current culture of this application is : “ + UsersCulture.Name);
allo stesso modo è semplice cambiare la cultura attuale:
' VB
tbSalary.Text = Format(100000, "Currency")
// C#
tbSalary.Text = (100000).ToString("C");
Thread.CurrentThread.CurrentCulture = new CultureInfo("es-VE");
Per formattare correttamente una stringa (ad esempio un valore monetario variando il punto
con la virgola, ecc.) dobbiamo usare appositi strumenti di formattazione:
152. dotNet Framework: Globalization
CurrentCulture rappresenta la cultura con cui vengono effettuate le manipolazioni durante
l’esecuzione del programma.
Dualmente esiste CurrentUICulture che rappresenta la cultura utilizzata per l’Interfaccia
Utente, il cui valore può essere letto/variato esattamente come CurrentCulture (sostituendo
ovviamente CurrentUICulture).
La cosa importante da evidenziare è che CurrentUICulture può essere settata solo nella fase
di start dell’applicazione (nel main), al contrario di CurrentCulture
Alla classe CultureInfo il framework affianca RegionInfo che fornisce informazioni relative
all’area geografica specifica:
' VB
Dim UsersCulture As CultureInfo = Thread.CurrentThread.CurrentCulture
Dim DemoRegion As RegionInfo = New RegionInfo(UsersCulture.LCID)
Or:
Dim DemoRegion As RegionInfo = New RegionInfo(UsersCulture.Name)
// C#
CultureInfo UsersCulture = Thread.CurrentThread.CurrentCulture;
RegionInfo DemoRegion = new RegionInfo(UsersCulture.LCID);
Or:
RegionInfo DemoRegion = new RegionInfo(UsersCulture.Name);
153. dotNet Framework: Globalization
Il risultato della
comparazione può essere
modificato passando alla
compare,come terzo
parametro, uno dei valori
di CompareOptions
' VB
Dim FirstString = "Coté"
Dim SecondString = "coté"
Dim DemoInfo As CompareInfo = New CultureInfo("fr-FR").CompareInfo
DemoInfo.Compare(FirstString, SecondString)
// C#
String FirstString = "Coté";
String SecondString = "coté";
CompareInfo DemoInfo = new CultureInfo("fr-FR").CompareInfo;
DemoInfo.Compare(FirstString, SecondString);
Quando abbiamo introdotto la globalization abbiamo fatto riferimento, come esempio, al
problema della rappresentazione delle date.
Per risolvere il relativo problema di internazionalizzazione si può far ricorso alla classe
DateTimeFormatInfo così come nel caso delle valute si può utilizzare la classe
NumberFormatInfo
L’ultima classe che evidenzieremo è la CompareInfo che unitariamente all’enumerator
CompareOptions permette di fare comparazioni tra stringhe.
La comparazione tra stringhe può avere esiti diversi in base alla cultura utilizzata. Ad esempio
alcune culture potrebbero ignorare la differenza di maiuscole e minuscole in una
stringa,mentre altre ritenerle fondamentali:
154. dotNet Framework: Globalization
Nel caso in cui avessimo bisogno di creare una nostra cultura (caso molto raro ma non
assurdo), possiamo far ricorso alla classe CultureRegionAndInfoBuilder e all’enumerator
CultureAndRegionModifiers.
In generale è possibile partire da una cultura esistente e variarne alcuni elementi di base:
' VB
Dim DemoBuilder As new CultureAndRegionInfoBuilder("en-US", CultureAndRegionModifiers.Neutral)
// C#
CultureAndRegionInfoBuilder DemoBuilder = new CultureAndRegionInfoBuilder("en-US",
CultureAndRegionModifiers.Neutral);