Condividi tramite


Semplificare la distribuzione e la risoluzione dell'errore DLL con .NET Framework

 

Steven Pratschner
Microsoft Corporation

Aggiornamento novembre 2001

Riepilogo: Questo articolo introduce il concetto di un assembly e descrive come .NET Framework usa assembly per risolvere i problemi di versione e distribuzione. (16 pagine stampate)

Contenuto

Introduzione
Definizione del problema
Caratteristiche della soluzione
Assembly: blocchi predefiniti
Controllo delle versioni e condivisione
Criteri di versione
Distribuzione
Riepilogo

Introduzione

Microsoft® .NET Framework introduce diverse nuove funzionalità destinate a semplificare la distribuzione dell'applicazione e la risoluzione di DLL Hell. Gli utenti finali e gli sviluppatori hanno familiarità con i problemi di versione e distribuzione che possono verificarsi con i sistemi basati su componenti di oggi. Ad esempio, praticamente ogni utente finale ha installato una nuova applicazione nel proprio computer, solo per trovare che un'applicazione esistente si arresta misteriosamente. La maggior parte degli sviluppatori ha anche trascorso tempo con Regedit, cercando di mantenere coerenti tutte le voci del Registro di sistema necessarie per attivare una classe COM.

Le linee guida di progettazione e le tecniche di implementazione usate in .NET Framework per risolvere DLL Hell sono basate sul lavoro svolto in Microsoft Windows® 2000, come descritto da Rick Anderson in The End of DLL Hell e da David D'Souza, BJ Whalen e Peter Wilson nell'implementazione della condivisione side-by-side dei componenti nelle applicazioni (espansa). .NET Framework estende questo lavoro precedente fornendo funzionalità incluse l'isolamento dell'applicazione e i componenti side-by-side per le applicazioni compilati con codice gestito nella piattaforma .NET. Si noti anche che Windows XP fornisce gli stessi elementi di isolamento e controllo delle versioni per il codice non gestito, incluse le classi COM e le DLL Win32 (vedere Come compilare e usare applicazioni isolate del servizio e assembly side-by-side per Windows XP per informazioni dettagliate).

Questo articolo introduce il concetto di un assembly e descrive come .NET usa assembly per risolvere i problemi di versione e distribuzione. In particolare, verrà illustrato come gli assembly sono strutturati, come vengono denominati e in che modo i compilatori e Common Language Runtime (CLR) usano assembly per registrare e applicare le dipendenze delle versioni tra parti di un'applicazione. Verranno inoltre illustrati come le applicazioni e gli amministratori possono personalizzare il comportamento di controllo delle versioni tramite i criteri di versione.

Dopo aver introdotto e descritto gli assembly, verranno presentati diversi scenari di distribuzione, fornendo un campionamento delle varie opzioni di creazione e distribuzione disponibili in .NET Framework.

Definizione del problema

Controllo delle versioni

Dal punto di vista del cliente, il problema di controllo delle versioni più comune è quello che chiamiamo DLL Hell. Semplicemente dichiarato, DLL Hell fa riferimento al set di problemi causati da più applicazioni quando più applicazioni tentano di condividere un componente comune come una libreria a collegamento dinamico (DLL) o una classe Component Object Model (COM). Nel caso più tipico, un'applicazione installerà una nuova versione del componente condiviso che non è compatibile con la versione precedente già nel computer. Anche se l'applicazione appena installata funziona correttamente, le applicazioni esistenti che dipendono da una versione precedente del componente condiviso potrebbero non funzionare più. In alcuni casi, la causa del problema è ancora più sottile. Si consideri ad esempio lo scenario in cui un utente scarica un controllo Microsoft ActiveX® come effetto collaterale della visita di un sito Web. Quando il controllo viene scaricato, sostituirà tutte le versioni esistenti del controllo presente nel computer. Se un'applicazione installata nel computer usa questo controllo, potrebbe anche interrompere il funzionamento.

In molti casi si verifica un ritardo significativo prima che un utente rilevi che un'applicazione ha interrotto il funzionamento. Di conseguenza, è spesso difficile ricordare quando è stata apportata una modifica al computer che potrebbe avere interessato l'app. Un utente può ricordare di installare qualcosa una settimana fa, ma non esiste alcuna correlazione evidente tra l'installazione e il comportamento che ora vengono visualizzati. Per peggiorare le cose, ci sono pochi strumenti di diagnostica disponibili oggi per aiutare l'utente (o la persona di supporto che li aiuta) determinare ciò che è sbagliato.

Il motivo di questi problemi è che le informazioni sulla versione sui diversi componenti di un'applicazione non vengono registrate o applicate dal sistema. Inoltre, le modifiche apportate al sistema per conto di un'applicazione influiscono in genere su tutte le applicazioni nel computer, creando un'applicazione oggi completamente isolata dalle modifiche non è facile.

Un motivo per cui è difficile creare un'applicazione isolata è che l'ambiente di runtime corrente consente in genere l'installazione di una sola versione di un componente o di un'applicazione. Questa restrizione significa che gli autori di componenti devono scrivere il codice in modo che rimanga compatibile con le versioni precedenti, altrimenti rischiano di interrompere le applicazioni esistenti quando installano un nuovo componente. In pratica, la scrittura di codice che è sempre compatibile con le versioni precedenti è estremamente difficile, se non impossibile. In .NET la nozione di lato è fondamentale per la storia di controllo delle versioni. Side by side è la possibilità di installare ed eseguire più versioni dello stesso componente nel computer contemporaneamente. Con i componenti che supportano side-by-side, gli autori non sono necessariamente associati a mantenere una rigorosa compatibilità con le versioni precedenti perché diverse applicazioni sono gratuite per l'uso di versioni diverse di un componente condiviso.

Distribuzione e installazione

L'installazione di un'applicazione oggi è un processo in più passaggi. In genere, l'installazione di un'applicazione comporta la copia di un numero di componenti software nel disco e la creazione di una serie di voci del Registro di sistema che descrivono tali componenti nel sistema.

La separazione tra le voci del Registro di sistema e i file sul disco rende molto difficile replicare le applicazioni e disinstallarle. Inoltre, la relazione tra le varie voci necessarie per descrivere completamente una classe COM nel Registro di sistema è molto libera. Queste voci includono spesso voci per coclassi, interfacce, tipilibs e ID app DCOM, non per menzionare le voci effettuate per registrare estensioni o categorie di componenti del documento. Spesso si finisce per mantenere questi elementi manualmente sincronizzati.

Infine, questo footprint del Registro di sistema è necessario per attivare qualsiasi classe COM. Ciò complica drasticamente il processo di distribuzione delle applicazioni distribuite perché ogni computer client deve essere toccato per rendere le voci appropriate del Registro di sistema.

Questi problemi sono causati principalmente dalla descrizione di un componente mantenuto separato dal componente stesso. In altre parole, le applicazioni non descrivono né se stessi.

Caratteristiche della soluzione

.NET Framework deve fornire le funzionalità di base seguenti per risolvere i problemi appena descritti:

  • Le applicazioni devono essere autodescrizione. Le applicazioni che descrivono automaticamente la dipendenza dal Registro di sistema, abilitando l'installazione a impatto zero e semplificando la disinstallazione e la replica.
  • Le informazioni sulla versione devono essere registrate e applicate. Il supporto per il controllo delle versioni deve essere integrato nella piattaforma per garantire che la versione appropriata di una dipendenza venga caricata in fase di esecuzione.
  • Deve ricordare "ultima buona nota". Quando un'applicazione viene eseguita correttamente, la piattaforma deve ricordare il set di componenti, incluse le versioni, che hanno lavorato insieme. Inoltre, gli strumenti devono essere forniti che consentono agli amministratori di ripristinare facilmente le applicazioni in questo stato "ultimo bene noto".
  • Supporto per i componenti side-by-side. Consentendo l'installazione e l'esecuzione di più versioni di un componente nel computer contemporaneamente consente ai chiamanti di specificare la versione da caricare anziché una versione "forzata" in modo inconsapevolmente. .NET Framework prende parte a un passaggio più lontano consentendo a più versioni del framework stesso di coesistenza in un singolo computer. Questo semplifica notevolmente la storia dell'aggiornamento, perché un amministratore può scegliere di eseguire applicazioni diverse in versioni diverse di .NET Framework, se necessario.
  • Isolamento dell'applicazione. .NET Framework deve semplificare la scrittura di applicazioni che non possono essere interessate dalle modifiche apportate al computer per conto di altre applicazioni.

Assembly: blocchi predefiniti

Gli assembly sono i blocchi predefiniti usati da .NET Framework per risolvere i problemi di controllo delle versioni e distribuzione appena descritti. Gli assembly sono l'unità di distribuzione per tipi e risorse. In molti modi un assembly equivale a una DLL nel mondo odierno; in sostanza, gli assembly sono una "DLL logica".

Gli assembly sono autodescrizione tramite metadati denominati manifesto. Proprio come .NET usa i metadati per descrivere i tipi, usa anche i metadati per descrivere gli assembly che contengono i tipi.

Gli assembly sono molto più della distribuzione. Ad esempio, il controllo delle versioni in .NET viene eseguito a livello di assembly: non è più piccolo, ad esempio un modulo o un tipo, viene eseguito il controllo delle versioni. Inoltre, gli assembly vengono usati per condividere il codice tra applicazioni. L'assembly contenuto in un tipo fa parte dell'identità del tipo.

Il sistema di sicurezza di accesso al codice usa assembly al centro del modello di autorizzazioni. L'autore di un record di assembly nel manifesto il set di autorizzazioni necessarie per eseguire il codice e l'amministratore concede le autorizzazioni per il codice in base all'assembly in cui è contenuto il codice.

Infine, gli assembly sono anche fondamentali per il sistema di tipi e il sistema di runtime in cui stabiliscono un limite di visibilità per i tipi e fungono da ambito di runtime per la risoluzione dei riferimenti ai tipi.

Manifesti dell'assembly

In particolare, un manifesto include i dati seguenti sull'assembly:

  • Identità. L'identità di un assembly è costituita da quattro parti: un nome di testo semplice, un numero di versione, una cultura facoltativa e una chiave pubblica facoltativa se l'assembly è stato creato per la condivisione (vedere la sezione sugli assembly condivisi di seguito).
  • Elenco di file. Un manifesto include un elenco di tutti i file che costituiscono l'assembly. Per ogni file, il manifesto registra il nome e un hash crittografico del relativo contenuto al momento della compilazione del manifesto. Questo hash viene verificato in fase di esecuzione per assicurarsi che l'unità di distribuzione sia coerente.
  • Assembly a cui si fa riferimento. Le dipendenze tra assembly vengono archiviate nel manifesto dell'assembly chiamante. Le informazioni sulle dipendenze includono un numero di versione, usato in fase di esecuzione per assicurarsi che venga caricata la versione corretta della dipendenza.
  • Tipi e risorse esportati. Le opzioni di visibilità disponibili per i tipi e le risorse includono "visibili solo all'interno dell'assembly" e "visibili per i chiamanti all'esterno dell'assembly".
  • Richieste di autorizzazione. Le richieste di autorizzazione per un assembly sono raggruppate in tre set: 1) quelli necessari per l'esecuzione dell'assembly, 2) quelli desiderati, ma l'assembly avrà ancora alcune funzionalità anche se non sono concesse e 3) quelli che l'autore non vuole mai concedere l'assembly.

Lo strumento IL Disassembler (Ildasm) SDK è utile per esaminare il codice e i metadati in un assembly. La figura 1 è un manifesto di esempio visualizzato da Ildasm. La direttiva .assembly identifica l'assembly e le direttive con estensione assembly extern contengono le informazioni su altri assembly su cui dipende questo.

Figura 1. Manifesto di esempio visualizzato dal disassembler IL

Struttura dell'assembly

Finora, gli assembly sono stati descritti principalmente come concetto logico. Questa sezione consente di rendere gli assembly più concreti descrivendo come vengono rappresentati fisicamente.

In generale, gli assembly sono costituiti da quattro elementi: i metadati dell'assembly (manifesto), i metadati che descrivono i tipi, il codice del linguaggio intermedio (IL) che implementa i tipi e un set di risorse. Non tutti questi sono presenti in ogni assembly. Solo il manifesto è strettamente obbligatorio, ma sono necessari tipi o risorse per assegnare all'assembly qualsiasi funzionalità significativa.

Esistono diverse opzioni per il modo in cui questi quattro elementi possono essere "in pacchetto". Ad esempio, la figura 2 mostra una singola DLL che contiene l'intero assembly: il manifesto, i metadati del tipo, il codice IL e le risorse.

Figura 2. DLL contenente tutti gli elementi dell'assembly

In alternativa, il contenuto di un assembly può essere distribuito in più file. Nella figura 3 l'autore ha scelto di separare un codice di utilità in una DLL diversa e di mantenere un file di risorse di grandi dimensioni (in questo caso un file JPEG) nel file originale. Un motivo per cui è possibile eseguire questa operazione consiste nell'ottimizzare il download del codice. .NET Framework scarica un file solo quando viene fatto riferimento, quindi se l'assembly contiene codice o risorse a cui si accede raramente, suddividerli in singoli file aumenterà l'efficienza del download. Un altro scenario comune in cui vengono usati più file consiste nel creare un assembly costituito da codice da più linguaggi. In questo caso, si creerà ogni file (modulo) separatamente, quindi raggrupparli in un assembly usando lo strumento Assembly Linker fornito in .NET Framework SDK (al.exe).

Figura 3. Elementi di assembly distribuiti tra più file

Controllo delle versioni e condivisione

Una delle cause principali di DLL Hell è il modello di condivisione attualmente usato nei sistemi basati su componenti. Per impostazione predefinita, i singoli componenti software vengono condivisi da più applicazioni nel computer. Ad esempio, ogni volta che un programma di installazione copia una DLL nella directory di sistema o registra una classe nel Registro COM, tale codice avrà potenzialmente un effetto su altre applicazioni in esecuzione nel computer. In particolare, se un'applicazione esistente ha usato una versione precedente di tale componente condiviso, tale applicazione inizierà automaticamente usando la nuova versione. Se il componente condiviso è strettamente compatibile con le versioni precedenti, questo può essere corretto, ma in molti casi mantenere la compatibilità con le versioni precedenti è difficile, se non impossibile. Se la compatibilità con le versioni precedenti non viene mantenuta o non può essere gestita, spesso le applicazioni interrotte vengono interrotte come effetto collaterale di altre applicazioni installate.

Una linea guida di progettazione di principio in .NET è quella di componenti isolati (o assembly). L'isolamento di un assembly significa che un assembly può essere accessibile solo da un'applicazione, non è condiviso da più applicazioni nel computer e non può essere interessato dalle modifiche apportate al sistema da altre applicazioni. L'isolamento fornisce un controllo assoluto dello sviluppatore sul codice usato dall'applicazione. Gli assembly isolati o privati dell'applicazione sono il valore predefinito nelle applicazioni .NET. La tendenza verso i componenti isolati è iniziata in Microsoft Windows 2000 con l'introduzione del file .local. Questo file è stato usato per causare l'aspetto del caricatore del sistema operativo e COM nella directory dell'applicazione prima quando si tenta di individuare il componente richiesto. Vedere l'articolo correlato in MSDN Library, implementazione della condivisione dei componenti side-by-side nelle applicazioni.

Tuttavia, esistono casi in cui è necessario condividere un assembly tra applicazioni. Chiaramente non avrebbe senso per ogni applicazione portare la propria copia di System.Windowns.Forms, System.Web o un controllo Web Forms comune.

In .NET la condivisione del codice tra applicazioni è una decisione esplicita. Gli assembly condivisi hanno alcuni requisiti aggiuntivi. In particolare, gli assembly condivisi devono supportare side-by-side in modo che più versioni dello stesso assembly possano essere installate ed eseguite nello stesso computer o anche all'interno dello stesso processo, contemporaneamente. Inoltre, gli assembly condivisi hanno requisiti di denominazione più rigorosi. Ad esempio, un assembly condiviso deve avere un nome univoco a livello globale.

La necessità di isolamento e condivisione ci porta a pensare a due "tipi" di assembly. Si tratta di una categorizzazione piuttosto libera in cui non ci sono differenze strutturali reali tra i due, ma piuttosto la differenza è in come verranno usate: sia privato a un'applicazione o condiviso tra molti.

assembly Application-Private

Un assembly privato dell'applicazione è un assembly visibile solo a un'applicazione. Si prevede che questo sia il caso più comune in .NET. I requisiti di denominazione per gli assembly privati sono semplici: i nomi degli assembly devono essere univoci solo all'interno dell'applicazione. Non è necessario un nome univoco globale. Mantenere i nomi univoci non è un problema perché lo sviluppatore dell'applicazione ha il controllo completo su quali assembly sono isolati all'applicazione.

Gli assembly privati dell'applicazione vengono distribuiti all'interno della struttura di directory dell'applicazione in cui vengono usati. Gli assembly privati possono essere inseriti direttamente nella directory dell'applicazione o in una sottodirectory. CLR individua questi assembly tramite un processo denominato probing. La verifica è semplicemente un mapping del nome dell'assembly al nome del file che contiene il manifesto.

In particolare, CLR accetta il nome dell'assembly registrato nel riferimento all'assembly, aggiunge ".dll" e cerca tale file nella directory dell'applicazione. In questo schema sono presenti alcune varianti in cui runtime esaminerà le sottodirectory denominate dall'assembly o nelle sottodirectory denominate dalle impostazioni cultura dell'assembly. Ad esempio, uno sviluppatore può scegliere di distribuire l'assembly contenente risorse localizzate in tedesco in una sottodirectory denominata "de" e in spagnolo in una directory denominata "es". Per informazioni dettagliate, vedere la Guida di .NET Framework SDK.

Come descritto, ogni manifesto dell'assembly include informazioni sulla versione sulle relative dipendenze. Queste informazioni sulla versione non vengono applicate per gli assembly privati perché lo sviluppatore ha il controllo completo sugli assembly distribuiti nella directory dell'applicazione.

Assembly condivisi

.NET Framework supporta anche il concetto di un assembly condiviso. Un assembly condiviso è uno usato da più applicazioni nel computer. Con .NET, la condivisione del codice tra applicazioni è una decisione esplicita. Gli assembly condivisi hanno alcuni requisiti aggiuntivi per evitare i problemi di condivisione che si verificano oggi. Oltre al supporto per side by side descrivere in precedenza, gli assembly condivisi hanno requisiti di denominazione molto più rigorosi. Ad esempio, un assembly condiviso deve avere un nome univoco globale. Inoltre, il sistema deve fornire la "protezione del nome", ovvero impedire a un utente di riutilizzare il nome dell'assembly di un altro. Ad esempio, si supponga di essere un fornitore di un controllo griglia e che sia stata rilasciata la versione 1 dell'assembly. Come autore è necessario assicurarsi che nessun altro possa rilasciare un assembly che dichiara di essere la versione 2 o il controllo griglia. .NET Framework supporta questi requisiti di denominazione tramite una tecnica denominata nomi sicuri (descritti in dettaglio nella sezione successiva).

In genere, un autore dell'applicazione non ha lo stesso grado di controllo sugli assembly condivisi usati dall'applicazione. Di conseguenza, le informazioni sulla versione vengono controllate su ogni riferimento a un assembly condiviso. .NET Framework consente inoltre alle applicazioni e agli amministratori di eseguire l'override della versione di un assembly usato dall'applicazione specificando i criteri di versione.

Gli assembly condivisi non vengono necessariamente distribuiti privatamente in un'applicazione, anche se tale approccio è ancora valida, soprattutto se la distribuzione di xcopy è un requisito. Oltre a una directory dell'applicazione privata, un assembly condiviso può anche essere distribuito nella Global Assembly Cache o in qualsiasi URL, purché una codebase che descrive il percorso dell'assembly venga fornito nel file di configurazione dell'applicazione. La global assembly cache è un archivio a livello di computer per gli assembly usati da più di un'applicazione. Come descritto, la distribuzione nella cache non è un requisito, ma esistono alcuni vantaggi per farlo. Ad esempio, l'archiviazione side-by-side di più versioni di un assembly viene fornita automaticamente. Inoltre, gli amministratori possono usare l'archivio per distribuire correzioni di bug o patch di sicurezza che desiderano che ogni applicazione nel computer usi. Infine, sono disponibili alcuni miglioramenti delle prestazioni associati alla distribuzione nella global assembly cache. Il primo comporta la verifica delle firme di nome sicuro, come descritto nella sezione Nome sicuro seguente. Il secondo miglioramento delle prestazioni comporta il lavoro impostato. Se diverse applicazioni usano lo stesso assembly contemporaneamente, il caricamento dell'assembly dalla stessa posizione sul disco sfrutta il comportamento di condivisione del codice fornito dal sistema operativo. Al contrario, il caricamento dello stesso assembly da più posizioni diverse (directory dell'applicazione) comporterà il caricamento di molte copie dello stesso codice. L'aggiunta di un assembly alla cache nel computer dell'utente finale viene in genere eseguita usando un programma di installazione basato su Windows Installer o su altre tecnologie di installazione. Gli assembly non finiscono mai nella cache come effetto collaterale dell'esecuzione di un'applicazione o dell'esplorazione a una pagina Web. L'installazione di un assembly nella cache richiede invece un'azione esplicita nella parte dell'utente. Windows Installer 2.0, che viene fornito con Windows XP e Visual Studio .NET, è stato migliorato per comprendere completamente il concetto di assembly, la cache degli assembly e le applicazioni isolate. Ciò significa che sarà possibile usare tutte le funzionalità di Windows Installer, ad esempio l'installazione su richiesta e il ripristino dell'applicazione, con le applicazioni .NET.

Spesso non è pratico compilare un pacchetto di installazione ogni volta che si vuole aggiungere un assembly alla cache nei computer di sviluppo e test. Di conseguenza, .NET SDK include alcuni strumenti per l'uso della cache degli assembly. Il primo è uno strumento denominato gacutil che consente di aggiungere assembly alla cache e rimuoverli in un secondo momento. Usare l'opzione /i per aggiungere un assembly alla cache:

gacutil /i:myassembly.dll 
See the .NET Framework SDK documentation for a full description of the 
      options supported by gacutil.

Gli altri strumenti sono un'estensione di Windows Shell che consente di modificare la cache usando Esplora risorse e lo strumento di configurazione di .NET Framework. L'estensione shell può essere accessibile passando alla sottodirectory "assembly" nella directory di Windows. Lo strumento di configurazione di .NET Framework è disponibile nella sezione Strumenti di amministrazione della Pannello di controllo.

La figura 4 mostra una visualizzazione della Global Assembly Cache usando l'estensione shell.

Figura 4. Global Assembly Cache

Nomi sicuri

I nomi sicuri vengono usati per abilitare i requisiti di denominazione più rigorosi associati agli assembly condivisi. I nomi sicuri hanno tre obiettivi:

  • Univocità dei nomi. Gli assembly condivisi devono avere nomi univoci a livello globale.
  • Impedire lo spoofing dei nomi. Gli sviluppatori non vogliono che altri utenti rilascino una versione successiva di uno degli assembly e sostengono erroneamente che provenga dall'utente, per caso o intenzionalmente.
  • Specificare l'identità di riferimento. Quando si risolve un riferimento a un assembly, i nomi sicuri vengono usati per garantire che l'assembly caricato provenisse dal server di pubblicazione previsto.

I nomi sicuri vengono implementati usando la crittografia a chiave pubblica standard. In generale, il processo funziona come segue: l'autore di un assembly genera una coppia di chiavi (o ne usa una esistente), firma il file contenente il manifesto con la chiave privata e rende disponibile la chiave pubblica per i chiamanti. Quando vengono eseguiti riferimenti all'assembly, il chiamante registra la chiave pubblica corrispondente alla chiave privata usata per generare il nome sicuro. La figura 5 illustra il funzionamento di questo processo in fase di sviluppo, inclusa la modalità di archiviazione delle chiavi nei metadati e la modalità di generazione della firma.

Lo scenario è un assembly denominato "Main", che fa riferimento a un assembly denominato "MyLib". MyLib ha un nome condiviso. I passaggi importanti sono descritti come segue.

Figura 5. Processo per l'implementazione di nomi condivisi

  1. Lo sviluppatore richiama un compilatore passando una coppia di chiavi e il set di file di origine per l'assembly. La coppia di chiavi viene generata con uno strumento SDK denominato SN. Ad esempio, il comando seguente genera una nuova coppia di chiavi e la salva in un file:

    Sn –k MyKey.snk
    The key pair is passed to the compiler using the custom attribute 
            System.Reflection.AssemblyKeyFileAttribute as follows:
    
       <assembly:AssemblyKeyFileAttribute("TestKey.snk")>
    
  2. Quando il compilatore genera l'assembly, la chiave pubblica viene registrata nel manifesto come parte dell'identità dell'assembly. L'inclusione della chiave pubblica come parte dell'identità è ciò che fornisce all'assembly un nome univoco globale.

  3. Dopo aver generato l'assembly, il file contenente il manifesto viene firmato con la chiave privata. La firma risultante viene archiviata nel file .

  4. Quando Main viene generato dal compilatore, la chiave pubblica di MyLib viene archiviata nel manifesto principale come parte del riferimento a MyLib.

In fase di esecuzione, sono necessari due passaggi per .NET Framework per garantire che i nomi sicuri stiano offrendo allo sviluppatore i vantaggi necessari. Prima di tutto, la firma del nome sicuro di MyLib viene verificata solo quando l'assembly viene installato nella Global Assembly Cache. La firma non viene verificata di nuovo quando il file viene caricato da un'applicazione. Se l'assembly condiviso non viene distribuito nella Global Assembly Cache, la firma viene verificata ogni volta che il file viene caricato. La verifica della firma garantisce che il contenuto di MyLib non sia stato modificato dopo la compilazione dell'assembly. Il secondo passaggio consiste nel verificare che la chiave pubblica archiviata come parte del riferimento di Main a MyLib corrisponda alla chiave pubblica che fa parte dell'identità di MyLib. Se queste chiavi sono identiche, l'autore di Main può essere sicuro che la versione di MyLib caricata provenisse dallo stesso editore che ha creato la versione di MyLib con cui è stato compilato Main. Questo controllo di equivalenza chiave viene eseguito in fase di esecuzione, quando il riferimento da Main a MyLib viene risolto.

Il termine "firma" spesso porta Microsoft Authenticode® a mente. È importante comprendere che i nomi sicuri e Authenticode non sono correlati in alcun modo. Le due tecniche hanno obiettivi diversi. In particolare, Authenticode implica un livello di attendibilità associato a un editore, mentre i nomi sicuri non lo fanno. Non sono presenti certificati o autorità di firma di terze parti associate a nomi sicuri. Inoltre, la firma con nome sicuro viene spesso eseguita dal compilatore stesso come parte del processo di compilazione.

Un'altra considerazione da considerare è il processo di "ritardo della firma". Spesso l'autore di un assembly non ha accesso alla chiave privata necessaria per eseguire la firma completa. La maggior parte delle aziende mantiene queste chiavi in negozi ben protetti a cui è possibile accedere solo da poche persone. Di conseguenza, .NET Framework fornisce una tecnica denominata "firma ritardata" che consente a uno sviluppatore di compilare un assembly con solo la chiave pubblica. In questa modalità, il file non è effettivamente firmato perché la chiave privata non viene fornita. Il file viene invece firmato in un secondo momento usando l'utilità SN. Per informazioni dettagliate su come usare la firma ritardata, vedere Ritardare la firma di un assembly in .NET Framework SDK.

Criteri di versione

Come appena descritto, ogni manifesto dell'assembly registra informazioni sulla versione di ogni dipendenza rispetto alla quale è stata compilata. Esistono tuttavia alcuni scenari in cui l'autore o l'amministratore dell'applicazione potrebbero voler essere eseguiti con una versione diversa di una dipendenza in fase di esecuzione. Ad esempio, gli amministratori devono essere in grado di distribuire le versioni di correzione di bug senza richiedere che ogni applicazione venga ricompilata per recuperare la correzione. Inoltre, gli amministratori devono essere in grado di specificare che una determinata versione di un assembly non venga mai usata se viene trovato un foro di sicurezza o un altro bug grave. .NET Framework consente questa flessibilità nell'associazione delle versioni tramite i criteri di versione.

Numeri di versione assembly

Ogni assembly ha un numero di versione in quattro parti come parte dell'identità, ovvero la versione 1.0.0.0 di alcuni assembly e la versione 2.1.0.2 sono identità completamente diverse per quanto riguarda il caricatore di classi. L'inclusione della versione come parte dell'identità è essenziale per distinguere versioni diverse di un assembly ai fini di side-by-side.

Le parti del numero di versione sono principali, secondarie, build e revisioni. Non esistono semantiche applicate alle parti del numero di versione. Ovvero, CLR non deduce la compatibilità o nessun'altra caratteristica di un assembly in base alla modalità di assegnazione del numero di versione. Gli sviluppatori sono liberi di modificare qualsiasi parte di questo numero come si ritiene adatta. Anche se al formato del numero di versione non è applicata alcuna semantica, è probabile che le singole organizzazioni trovino utile stabilire convenzioni relative alla modifica del numero di versione. Ciò consente di mantenere la coerenza in un'organizzazione e di determinare più facilmente elementi come la compilazione di un assembly specifico. Una convenzione tipica è la seguente:

Maggiore o minore. Le modifiche apportate alla parte principale o secondaria del numero di versione indicano una modifica incompatibile. In base a questa convenzione, la versione 2.0.0.0 sarebbe considerata incompatibile con la versione 1.0.0.0. Esempi di una modifica incompatibile sono una modifica ai tipi di alcuni parametri del metodo o la rimozione del tutto di un tipo o di un metodo.

Genera. Il numero di build viene in genere usato per distinguere tra build giornaliere o versioni compatibili più piccole.

Revisione. Le modifiche apportate al numero di revisione sono in genere riservate per una compilazione incrementale necessaria per correggere un particolare bug. A volte si riceverà questo avviso come numero di "correzione di bug di emergenza" in quanto la revisione è ciò che viene spesso modificato quando viene inviata una correzione a un bug specifico a un cliente.

Criteri di versione predefiniti

Quando si risolvono i riferimenti agli assembly condivisi, CLR determina la versione della dipendenza da caricare quando si verifica un riferimento a tale assembly nel codice. I criteri di versione predefiniti in .NET sono estremamente semplici. Quando si risolve un riferimento, CLR accetta la versione dal manifesto dell'assembly chiamante e carica la versione della dipendenza con lo stesso numero di versione. In questo modo, il chiamante ottiene la versione esatta che è stato costruito e testato contro. Questo criterio predefinito ha la proprietà che protegge le applicazioni dallo scenario in cui un'applicazione diversa installa una nuova versione di un assembly condiviso da cui dipende un'applicazione esistente. Tenere presente che prima di .NET, le applicazioni esistenti inizieranno a usare il nuovo componente condiviso per impostazione predefinita. In .NET, tuttavia, l'installazione di una nuova versione di un assembly condiviso non influisce sulle applicazioni esistenti.

Criteri di versione personalizzati

In alcuni casi l'associazione alla versione esatta con cui l'applicazione è stata fornita non è quella desiderata. Ad esempio, un amministratore può distribuire una correzione di bug critica in un assembly condiviso e vuole che tutte le applicazioni usino questa nuova versione indipendentemente dalla versione con cui sono state compilate. Inoltre, il fornitore di un assembly condiviso potrebbe aver fornito una versione di servizio a un assembly esistente e vorrebbe che tutte le applicazioni inizino a usare la versione del servizio anziché la versione originale. Questi scenari e altri sono supportati in .NET Framework tramite i criteri di versione.

I criteri di versione sono indicati nei file XML e sono semplicemente una richiesta di caricare una versione di un assembly anziché un'altra. Ad esempio, il criterio di versione seguente indirizza CLR al caricamento della versione 5.0.0.1 anziché alla versione 5.0.0.0 di un assembly denominato MarineCtrl:

 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

Oltre a reindirizzare da un numero di versione specifico a un altro, è anche possibile reindirizzare da un intervallo di versioni a un'altra versione. Ad esempio, il criterio seguente reindirizza tutte le versioni da 0.0.0.0 a 5.0.0.0 di MarineCtrl alla versione 5.0.0.1:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

Livelli dei criteri di versione

Esistono tre livelli a cui è possibile applicare i criteri di versione in .NET: criteri specifici dell'applicazione, criteri di pubblicazione e criteri a livello di computer.

Criteri specifici dell'applicazione. Ogni applicazione ha un file di configurazione facoltativo in grado di specificare il desiderio dell'applicazione di eseguire il binding a una versione diversa di un assembly dipendente. Il nome del file di configurazione varia in base al tipo di applicazione. Per i file eseguibili, il nome del file di configurazione è il nome dell'eseguibile + un'estensione ".config". Ad esempio, il file di configurazione per "myapp.exe" sarà "myapp.exe.config". I file di configurazione per le applicazioni ASP.NET sono sempre "web.config".

Criteri di pubblicazione. Anche se i criteri specifici dell'applicazione vengono impostati dallo sviluppatore o dall'amministratore dell'applicazione, i criteri dell'editore vengono impostati dal fornitore dell'assembly condiviso. I criteri dell'editore sono la dichiarazione di compatibilità del fornitore relativa a versioni diverse dell'assembly. Ad esempio, si supponga che il fornitore di un controllo condiviso Windows Forms contenga una versione del servizio che contiene una serie di correzioni di bug al controllo. Il controllo originale era la versione 2.0.0.0 e la versione della versione del servizio è 2.0.0.1. Poiché la nuova versione contiene solo correzioni di bug (nessuna modifica dell'API di interruzione) il fornitore del controllo potrebbe rilasciare criteri di pubblicazione con la nuova versione che causa le applicazioni esistenti usate 2.0.0.0.0 per iniziare a usare 2.0.0.1. I criteri di pubblicazione vengono espressi in XML come i criteri a livello di applicazione e di computer, ma a differenza di quelli di altri livelli di criteri, i criteri di pubblicazione vengono distribuiti come assembly stesso. Il motivo principale per questo è garantire che l'organizzazione rilascia i criteri per un determinato assembly sia la stessa organizzazione che ha rilasciato l'assembly stesso. Questa operazione viene eseguita richiedendo che sia l'assembly originale che l'assembly dei criteri vengano assegnati un nome sicuro con la stessa coppia di chiavi.

Criteri a livello di computer. Il livello di criteri finale è un criterio a livello di computer (a volte definito criteri di amministratore). I criteri a livello di computer vengono archiviati in machine.config che si trova nella sottodirectory "config" nella directory di installazione di .NET Framework. La directory di installazione è %windir%\microsoft.net\framework\%runtimeversion%. Le istruzioni dei criteri effettuate in machine.config influiscono su tutte le applicazioni in esecuzione nel computer. I criteri a livello di computer vengono usati dagli amministratori per forzare tutte le applicazioni in un determinato computer per usare una determinata versione di un assembly. Lo scenario più commento in cui viene usato è quando è stata distribuita una sicurezza o un'altra correzione di bug critica nella global assembly cache. Dopo aver distribuito l'assembly fisso, l'amministratore userà criteri di versione a livello di computer per assicurarsi che le applicazioni non usino la versione precedente e interrotta dell'assembly.

Valutazione dei criteri

La prima operazione eseguita da CLR quando l'associazione a un assembly fortemente denominato è determinare la versione dell'assembly da associare. Il processo inizia leggendo il numero di versione dell'assembly desiderato registrato nel manifesto dell'assembly che effettua il riferimento. I criteri vengono quindi valutati per determinare se uno dei livelli di criteri contiene un reindirizzamento a una versione diversa. I livelli di criteri vengono valutati in ordine a partire dai criteri dell'applicazione, seguiti dal server di pubblicazione e infine dall'amministratore.

Un reindirizzamento trovato a qualsiasi livello esegue l'override di qualsiasi istruzione effettuata da un livello precedente. Ad esempio, si supponga che l'assembly A faccia riferimento all'assembly B. Il riferimento a B nel manifesto di A consiste nella versione 1.0.0.0.0. Inoltre, i criteri di pubblicazione forniti con assembly B reindirizza il riferimento da 1.0.0.0 a 2.0.0.0.0. I criteri di versione sono inoltre il file di configurazione a livello di computer che indirizza il riferimento alla versione 3.0.0.0. In questo caso, l'istruzione effettuata a livello di computer eseguirà l'override dell'istruzione effettuata a livello di editore.

Bypass dei criteri di pubblicazione

A causa dell'ordine in cui vengono applicati i tre tipi di criteri, il reindirizzamento della versione del server di pubblicazione (criteri di pubblicazione) può eseguire l'override della versione registrata nei metadati dell'assembly chiamante e di qualsiasi criterio specifico dell'applicazione impostato. Tuttavia, forzare un'applicazione a accettare sempre la raccomandazione di un editore sul controllo delle versioni può portare a DLL Hell. Dopo tutto, DLL Hell è principalmente causato dalla difficoltà di mantenere la compatibilità con le versioni precedenti nei componenti condivisi. Per evitare ulteriormente lo scenario in cui un'applicazione viene interrotta dall'installazione di una nuova versione di un componente condiviso, il sistema dei criteri di versione in .NET consente a un'applicazione singola di ignorare i criteri di pubblicazione. In altre parole, un'applicazione può rifiutare la raccomandazione dell'editore su quale versione usare. Un'applicazione può ignorare i criteri di pubblicazione usando l'elemento "publisherPolicy" nel file di configurazione dell'applicazione:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<publisherPolicy apply="no"/>

</assemblyBinding>

Impostazione dei criteri di versione con lo strumento di configurazione .NET

Fortunatamente, .NET Framework viene fornito con uno strumento di amministrazione grafica in modo che non sia necessario preoccuparsi della modifica dei file di criteri XML. Lo strumento supporta sia i criteri di versione a livello di applicazione che di computer. Si troverà lo strumento nella sezione Strumenti di amministrazione di Pannello di controllo. La schermata iniziale dello strumento di amministrazione è simile alla figura 6:

Figura 6. Strumento Amministrazione

I passaggi seguenti descrivono come impostare criteri specifici dell'applicazione:

  1. Aggiungere l'applicazione al nodo Applicazioni nella visualizzazione albero. Fare clic con il pulsante destro del mouse sul nodo Applicazioni e scegliere Aggiungi. La finestra di dialogo Aggiungi mostra un elenco di applicazioni .NET da scegliere. Se l'applicazione non è presente nell'elenco, è possibile aggiungerla facendo clic su Altro.

  2. Scegliere l'assembly per cui si desidera impostare i criteri. Fare clic con il pulsante destro del mouse sul nodo Assembly configurati e scegliere Aggiungi. Una delle opzioni consiste nel selezionare un assembly dall'elenco di assembly a cui fa riferimento l'applicazione e viene visualizzata la finestra di dialogo seguente, come illustrato nella figura 7. Selezionare un assembly e fare clic su Seleziona.

    Figura 7. Scelta di un assembly

  3. Nella finestra di dialogo Proprietà immettere le informazioni sui criteri di versione. Fare clic sulla scheda Criteri di associazione e immettere i numeri di versione desiderati nella tabella, come illustrato nella figura 8.

    Figura 8. Scheda Criteri di associazione

Distribuzione

La distribuzione prevede almeno due aspetti diversi: creazione del pacchetto del codice e distribuzione dei pacchetti nei vari client e server in cui verrà eseguita l'applicazione. Un obiettivo principale di .NET Framework consiste nel semplificare la distribuzione (soprattutto l'aspetto della distribuzione) rendendo possibile l'installazione e la distribuzione xcopy a impatto zero. La natura autodescrizione degli assembly consente di rimuovere la dipendenza dal Registro di sistema, rendendo così più semplice l'installazione, la disinstallazione e la replica. Tuttavia, esistono scenari in cui xcopy non è sufficiente o appropriato come meccanismo di distribuzione. Per questi casi, .NET Framework offre servizi di download di codice completi e integrazione con Windows Installer.

Packaging

Nella prima versione di .NET Framework sono disponibili tre opzioni di creazione di pacchetti:

  • Come compilato (DLL e EXEs). In molti scenari non è necessario alcun pacchetto speciale. Un'applicazione può essere distribuita nel formato prodotto dallo strumento di sviluppo. Vale a dire una raccolta di DLL e EXEs.
  • File cab. I file cab possono essere usati per comprimere l'applicazione per download più efficienti.
  • Pacchetti di Windows Installer. Microsoft Visual Studio .NET e altri strumenti di installazione consentono di creare pacchetti di Windows Installer (.msi file). Windows Installer consente di sfruttare il vantaggio del ripristino dell'applicazione, dell'installazione su richiesta e di altre funzionalità di gestione delle applicazioni di Microsoft Windows.

Scenari di distribuzione

Le applicazioni .NET possono essere distribuite in diversi modi, tra cui xcopy, download del codice e tramite Windows Installer.

Per molte applicazioni, incluse applicazioni Web e Servizi Web, la distribuzione è semplice come copiare un set di file su disco ed eseguirli. La disinstallazione e la replica sono semplici, è sufficiente eliminare i file o copiarli.

.NET Framework offre supporto completo per il download del codice usando un Web browser. Sono stati apportati diversi miglioramenti in questo settore, tra cui:

  • Impatto zero. Nessuna voce del Registro di sistema viene eseguita nel computer.
  • Download incrementale. Le parti di un assembly vengono scaricate solo quando fanno riferimento.
  • Scaricare isolato nell'applicazione. Il codice scaricato per conto di un'applicazione non può influire sugli altri nel computer. L'obiettivo principale del supporto per il download del codice consiste nel impedire lo scenario in cui un utente scarica una nuova versione di un componente condiviso come effetto collaterale dell'esplorazione in un sito Web specifico e la presenza di tale nuova versione influisce negativamente sulle altre applicazioni.
  • Nessuna finestra di dialogo Authenticode. Il sistema di sicurezza di accesso al codice viene usato per consentire l'esecuzione del codice per dispositivi mobili con un livello parziale di attendibilità. Gli utenti non verranno mai presentati con finestre di dialogo che chiedono loro di prendere una decisione su se considerano attendibile il codice.

Infine, .NET è completamente integrato con Windows Installer e le funzionalità di gestione delle applicazioni di Windows.

Riepilogo

.NET Framework abilita l'installazione a impatto zero e risolve l'inferno della DLL. Gli assembly sono le unità di distribuzione autoscrivibili e versionebili usate per abilitare queste funzionalità.

La possibilità di creare applicazioni isolate è fondamentale perché consente la compilazione di applicazioni che non saranno interessate dalle modifiche apportate al sistema da altre applicazioni. .NET Framework incoraggia questo tipo di applicazione tramite assembly privati dell'applicazione distribuiti nella struttura di directory dell'applicazione.

Side by side è una parte principale della condivisione e della storia di controllo delle versioni in .NET. Side by side consente l'installazione e l'esecuzione di più versioni di un assembly nel computer contemporaneamente e consente a ogni applicazione di richiedere una versione specifica di tale assembly.

ClR registra le informazioni sulla versione tra parti di un'applicazione e usa tali informazioni in fase di esecuzione per assicurarsi che venga caricata la versione corretta di una dipendenza. I criteri di versione possono essere usati dagli sviluppatori di applicazioni e dagli amministratori per offrire una certa flessibilità nella scelta della versione di un determinato assembly.

Esistono diverse opzioni di creazione di pacchetti e distribuzione fornite da .NET Framework, tra cui Windows Installer, download del codice e xcopy semplice.