in

DotNetSide

Dot Net South Italy Developers User Group

Articoli

Articoli pubblicati dagli iscritti a .netSide

AppConfigManager proteggi le sections del tuo app.config da sguardi indiscreti

di Leonardo Alario

Spesso, accade che, distribuendo ai clienti applicazioni con il relativo file app.config scritte con .NET 2.0 alcune informazioni sensibili vengano lasciate completamente in chiaro compromettendo la sicurezza di account di dominio o stringhe di connessione presenti nel file di configurazione. Questa leggerezza potrebbe permettere ad utenti malintenzionati, nella migliore delle ipotesi, di sbirciare nel database di produzione o peggio ancora, di prendere possesso del server o del client in cui gira il nostro applicativo.

Purtroppo i progettisti di .NET non hanno previsto nessun tool per le applicazioni WINDOWS FORMS che permetta di criptare tutte le section dell’app.config, che contegono dati sensibili.

A questo punto, per esigenze aziendali, mi è sembrato opportuno realizzare un tool che permettesse di gestire sia le informazioni dell’App.Config, che di criptare le section che contengono dati sensibili.

Il tool attualmente, consente di gestire due delle principali section presenti nel file App.config, che generalmente possono contenere informazioni “sensibili”:

  • AppSettingSection;
  • ConnectionStringsSection;

Nulla vieta però di permettere al tool, di utilizzare altre section già previste dal file di configurazione o di utilizzare section personalizzate, questo è possibile in quanto il tool è stato progettato secondo i canoni dell’architettura a PlugIn, rendendolo molto granulare ed estremamente estendibile .

Architettura del Tool

In generale l’AppConfigManager Tool è stato implementato secondo la seguente architettura :

Architettura Generale

Ogni singolo plugin è composto da una classe, denominata con il nome della section per una questione di leggibilità e di organizzazione del progetto, che implementa l’interfaccia IPlugin presente nel namespace PlugInInterface e da uno UserControl che costituisce la GUI del plugin per interfacciarsi con l’utente.

La classe funge sia da contenitore per far “viaggare” i dati dalla GUI del plugin all’applicazione host, che da marcatore che permette di identificare l’assembly come plugin disponibile per l’applicazione host.

La GUI invece permette di rappresentare graficamente le informazioni presenti nella section che si stà gestendo, inoltre nella GUI vengono implementati controlli per validare le informazioni, che successivamente verranno scritte nel file di configurazione ad esempio nella section appSettings viene verificato che l’utente non inserisca la stessa chiave più di una volta.

Il Plugin referenzia gli assembly CriptoProvider e PlugInInterface. L’assembly CriptoProvider contiene la logica di recupero dei provider di crittografia definiti nel file machine.config nella section configProtectedData ed implementati dal framework .NET. Questi provider sono molto importanti in quanto, gli stessi vengono utilizzati per crittografare le informazioni della section di competenza. Di default nel framework troviamo due provider RsaProtectedConfigurationProvider che è il provider di crittografia predefinito e il provider DataProtectionConfigurationProvider. Se si dispone di un'implementazione personalizzata di un algoritmo di crittografia, può essere utilizzata tale implementazione in alternativa a quella predefinita fornita con .NET Framework SDK.

Per ulteriori informazioni consultare Guida per gli sviluppatori di .NET Framework.

L’assembly CriptoProvider, inoltre implementa uno UserControl che consente di selezionare il provider desiderato per la crittografia della section utilizzato in tutti i plugin.

 

Architettura Plugin

 

Riassumendo ogni singolo plugin è conforme alla seguente architettura:

  • PluginInterface implementazione dell’interfaccia Iplugin;
  • UserControl Main Interface, elemento che implementa la GUI del plugin;
  • CriptoProvider UserControl agganciato al plugin e nello specifico allo UserControl MainInterface, che recupera e restituisce al plugin le informazioni relative ai provider di crittografia definiti nel Framework.

L’interfaccia iPlugIn

Come accennato in precedenza l’interfaccia Iplugin è fondamentale per l’architettura di questo tool, e qualsiasi plugin deve necessariamente implementare questa interfaccia.

Vediamo nello specifico quali proprietà, metodi, ed eventi espone l’interfaccia in questione:

public interface Iplugin
{
IPluginHost Host {
get;set;}
string AppConfigSection { get;}
string Description { get;}
string ExecutablePath {set;}
StatoElaborazioneEnum StatoElaborazione {
get;set;}
string Messaggio { get;set;}
string AssemblyTitle { get;}
string AssemblyDescription { get;}
string AssemblyCompany { get;}
string AssemblyVersion { get;}
string AssemblyProduct { get;}
string AssemblyCopyright { get;}
UserControl MainInterface {
get;}
void Initialize();
void Dispose();
event GenerazioneDelegate GenerazioneEvent;
void Generazione();
}


La proprietà Host espone alcune informazioni verso l’applicativo host di tipo IPluginHost.
public interface IPluginHost
{
void Feedback(string Feedback, IPlugin Plugin);
}

Questo metodo serve a far viaggiare informazioni dai plugin verso l’applicazione host, attualmente è utilizzata per inviare all’applicazione host, informazioni sulla versione e sull’autore del plugin.

Avrei potuto utilizzare questo metodo per far viaggiare i dati dal plugin all’applicativo, ma ho preferito specializzare i plugin con le relative section da gestire, invece di centralizzare tutta la gestione nella form WizardMain.

La proprietà AppConfigSection indica quale section implementa il plugin (questa property è fondamentale). La proprietà Description indica la descrizione della section implementata (utilizzata nella GUI dell’applicazione host per "etichettare” la TabPage che fungerà da contenitore per la GUI del plugin). La proprietà ExecutablePath indica il path del file App.Config da processare. La proprietà StatoElaborazione indica lo stato dell’elaborazione della section indicata di tipo:

public enum StatoElaborazioneEnum
{
SectionNonElaborata
= 0, // Indica che la section non è stata elaborata
SectionElaborataConSuccesso = 1 , // Indica che la section è stata elaborata con successo
SectionElaborataConErrore = 2 // Si è verificato un erroe pertanto la section non è stata elaborata
}

La proprietà Messaggio notifica i messaggi relativi all’elaborazione. La proprietà MainInterface implementa la GUI per il Plugin. L’evento GenerazioneEvent implementa l’evento di generazione, salvataggio e/o aggiornamento della section nel relativo file App.Config, nel percorso dichiarato nella proprietà string ExecutablePath, della section dichiarata nella proprietà string AppConfigSection.

IPlugIn

Implementare un PlugIn, un caso pratico

Come accennato in precedenza, per implementare un plugin che rappresenta una section del file App.Config è necessario implementare l’interfaccia Iplugin.

In questo esempio descriveremo l’implementazione del plugin ConnectionStringsSection, anche se il procedimento che mi appresto a descrivere è valido, salvo alcuni particolari che vedremo in seguito, per tutte le section che vorremo implementare.

  1. Aggiungiamo un nuovo progetto e lo denominiamo ConnectionStringsSection;
  2. Aggiungiamo una nuova classe e la rinominiamo, per comodità, ConnectionStringsSection;
  3. Aggiungiamo un nuovo controllo utente e lo rinominiamo ctlMain;
  4. Aggiungiamo I seguenti riferimenti al progetto:

    Riferimenti

  5. Nelle Proprietà del progetto definiamo il nome dell’assembly “ConnectionStringsSection” e lo spazio dei nomi predefinito “ConnectionStringsSection”;

Config

a questo punto aggiungiamo alla classe ConnectionStringsSection le seguenti direttive using:

using System.Reflection;
using PlugInInterface;

ed implementiamo le proprietà, i metodi e gli eventi dichiarati nell’interfaccia Iplugin. Per questioni di spazio citerò solo quelli fondamentali :

private string _AppConfigSection = "connectionStrings";
private string _Description = "ConnectionStrings Section";
private string _ExecutablePath = string.Empty;
private string _Messaggio = "Non Elaborata !";
private StatoElaborazioneEnum _StatoElaborazione = StatoElaborazioneEnum.SectionNonElaborata;
private IPluginHost _Host = null;
private System.Windows.Forms.UserControl _MainInterface = new ctlMain();

public IPluginHost Host
{
get { return _Host; }
set { _Host = value; }
}

public string AppConfigSection
{
get { return _AppConfigSection; }
}

public System.Windows.Forms.UserControl MainInterface
{
get { return _MainInterface; }
}

public string Description
{
get { return _Description; }
}

public string ExecutablePath
{
get
{
return _ExecutablePath;
}
set
{
_ExecutablePath
= value;
}
}

public string Messaggio
{
get { return _Messaggio; }
set { _Messaggio = value; }
}

public StatoElaborazioneEnum StatoElaborazione
{
get { return _StatoElaborazione; }
set { _StatoElaborazione = value; }
}

public event GenerazioneDelegate GenerazioneEvent;

public void Generazione()
{
if (GenerazioneEvent != null)
{
GenerazioneEvent();
}
}

public void Initialize()
{
_Plugin
= this;
}

public void Dispose()
{
_MainInterface.Dispose();
_MainInterface
= null;
GC.Collect();
}

Aggiungiamo la logica necessaria allo usercontrol per poter gestire la section Connection String:

aggiungiamo allo User Control ctlMain la seguente direttiva using:

using System.Configuration;

rinominiamo il namespace nel seguente modo:

namespace ConnectionStringsSection
{
...
}

quindi definiamo i seguenti oggetti:

private string _ExecutablePath = string.Empty;

public string ExecutablePath
{
set
{
_ExecutablePath
= value;
}
}

private ConnectionStringSettingsCollection ConnectionStrings = new ConnectionStringSettingsCollection();

La classe ConnectionStringSettingsCollection contenuta nel namespace System.Configuration contiene un insieme di oggetti ConnectionStringSettings, ed ogni singolo oggetto rappresenta una singola stringa di connessione denominata contenuta nella sezione del file di configurazione relativa alle stringhe di connessione.

Utilizziamo il plugin

Nel metodo di caricamento dello user control ctlMain ctlmain_load andremo a gestire il recupero delle eventuali connectionstrings definite nel file app.config, quindi aggiungeremo:

// Aggiungo il delegato per la generazione della section nel file App.Config
Plugin.Instance.GenerazioneEvent += new PlugInInterface.GenerazioneDelegate(plugin_GenerazioneEvent);

// Apro il file di configurazione passato come parametro dall’applicazione host
System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(Plugin.Instance.ExecutablePath);

// Carico le connectionstring disponibili se il file nn esiste verrà caricata
// la connection string di default del machine.config
ConnectionStrings.Clear();

for (int i = 0; i <= config.ConnectionStrings.ConnectionStrings.Count - 1; i++)
{
ConnectionStringSettings c
= new ConnectionStringSettings
(
config.ConnectionStrings.ConnectionStrings.Name,
config.ConnectionStrings.ConnectionStrings.ConnectionString,
config.ConnectionStrings.ConnectionStrings.ProviderName
);

c.LockItem
= config.ConnectionStrings.ConnectionStrings.LockItem;

// Aggungo la connection string alla collection delle collection string disponibili
ConnectionStrings.Add(c);
}

A questo segmento di codice dobbiamo prestare molta attenzione, poiché fondamentale per il recupero delle informazioni sulla section. La proprietà IsProtected,di tipo bool, della classe SectionInformation, infatti,, permette di verificare se la section corrente è crittografata o meno. La proprietà SectionInformation.ProtectionProvider.Name, invece, restituisce il nome dell’eventuale provider utilizzato per la crittazione della section. Questi due elementi vengono notificati allo User Control Cripto provider per essere utilizzati successivamente durante il salvataggio delle informazioni.

System.Configuration.ConnectionStringsSection section = config.GetSection(Plugin.Instance.AppConfigSection) as System.Configuration.ConnectionStringsSection;

if (section != null)
{
criptoProviderCtl.Checked
= section.SectionInformation.IsProtected;
if (section.SectionInformation.IsProtected)
criptoProviderCtl.Provider
= section.SectionInformation.ProtectionProvider.Name;
else
criptoProviderCtl.Provider
= string.Empty;
}

BindCmbConnectionString();
if (ConnectionStrings.Count > 0)
CmbConnectionString.SelectedIndex
= 0;

Successivamente andremo ad’implementare il metodo privato BindCmbConnectionString() che si occupa di visualizzare le informazioni sulle connection strings presenti nel file di configurazione :

private void BindCmbConnectionString()
{
CmbConnectionString.Items.Clear();
foreach (ConnectionStringSettings c in ConnectionStrings)
{
CmbConnectionString.Items.Add(c.Name);
}
}

Se il file di configurazione selezionato non contiene la section ConnectionStringSettings, o il file di configurazione non esiste, il metodo popola la collection con un solo item di tipo ConnectionStringSettings denominato LocalSqlServer, questo avviene poiché come ben sappiamo, il file di configurazione App.Config deriva e/o estende il file di configurazione machine.config e nello stesso file é anche definita la connection string di default per accedere ad un eventuale instanza locale di Sql Server 2005 Express collegata ad Asp.Net.

A questo punto, non resta che implementare il delegato plugin_GenerazioneEvent dichiarato nell’evento ctlmain_load; questo delegato verrà invocato dall’applicazione host nel momento in cui bisognerà, secondo i settaggi dell’utente, criptare e salvare i dati della section :

private void plugin_GenerazioneEvent()
{
try
{
Plugin.Instance.StatoElaborazione
= PlugInInterface.StatoElaborazioneEnum.SectionNonElaborata;
System.Configuration.Configuration config
= ConfigurationManager.OpenExeConfiguration(Plugin.Instance.ExecutablePath);
System.Configuration.ConnectionStringsSection cnnsection
= config.GetSection(Plugin.Instance.AppConfigSection) as System.Configuration.ConnectionStringsSection;

if (cnnsection == null)
throw new System.Exception("La section " + Plugin.Instance.AppConfigSection + " non é stata trovata nell'appconfig selezionato !");

cnnsection.SectionInformation.UnprotectSection();

// Sblocco tutti gli elementi presenti prima di salvare
foreach (ConnectionStringSettings cs in cnnsection.ConnectionStrings)
{
cs.LockItem
= false;
}
cnnsection.ConnectionStrings.Clear();

foreach (ConnectionStringSettings c in ConnectionStrings)
{
cnnsection.ConnectionStrings.Add(c);
}

if (criptoProviderCtl.Checked)
{
cnnsection.SectionInformation.ProtectSection(criptoProviderCtl.Provider);
}
config.Save(ConfigurationSaveMode.Full);

Plugin.Instance.StatoElaborazione
= PlugInInterface.StatoElaborazioneEnum.SectionElaborataConSuccesso;
Plugin.Instance.Messaggio
= "Section Elaborata Con Successo !";
}
catch (Exception Ex)
{
Plugin.Instance.StatoElaborazione
= PlugInInterface.StatoElaborazioneEnum.SectionElaborataConErrore;
Plugin.Instance.Messaggio
= "Elaborazione Fallita ! " + Ex.Message;
}
}

In questo delegato, tramite il metodo getsection, recupereremo la section da modificare. Dopo di che, prima di fare qulasiasi altra operazione, eliminiamo un eventuale protezione precendemente applicata alla section, tramite l’struzione cnnsection.SectionInformation.UnprotectSection(), verifichiamo se l’utente desidera criptare la section e con quale provider desidera farlo. Infine salviamo i cambiamenti al file app.config e notifichiamo lo stato dell’operazione.

Per Aggiungere, eliminare connection string o validare le informazioni sarà sufficiente lavorare sulla collection ConnectionStrings. Per questione di spazio non includo il codice nel presente articolo, fare riferimento al progetto allegato.

Per implementare un plugin in grado di lavorare con una Custom Section, presente nel file App.Config, è necessario aggiungere al progetto il riferimento all’assembly che rappresenta la custom section e gestirla successivamente come nei passaggi precedentemente descritti. Per ulteriori informazioni su come implementare una section personalizzata visitare il seguente link http://msdn2.microsoft.com/it-it/library/system.configuration.configurationsection(VS.80).aspx


L’applicazione Host

L’applicazione host contiene la logica per utilizzare i plugin disponibili e gestire, modificare ed elaborare le varie informazioni contenute nelle section definite nei relativi plugin.

Anche l’applicazione host referenzia l’assembly PlugInInterface, e tramite l’interfaccia IPlugin identifica come plugin disponibili i plugin contenuti nella directory “Plugins” nel percorso dove sta girando l’applicazione.

L’applicazione caricherà dinamicamente tutti gli assembly presenti nella suddetta cartella (anche le dipendenze) utilizzando la Reflection, quindi assembly per assembly recupererà tutti i tipi presenti, e per ogni tipo cercherà l'interfaccia specificata IPlugin :

Type typeInterface = pluginType.GetInterface("IPlugin", true);

Se il tipo sarà diverso da null l’assembly corrente verrà riconosciuto come plugin supportato, quindi verrà creato un nuovo oggetto :

Types.AvailablePlugin newPlugin = new Types.AvailablePlugin();

del nuovo oggetto verrà create una nuova instanza :

newPlugin.Instance =
(IPlugin)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));

al nuovo oggetto viene assegnata l’applicazione host di appartenenza :

newPlugin.Instance.Host = this;

verrà chiamato il metodo che inizializza il Plugin :

newPlugin.Instance.Initialize();

infine il Plugin viene aggiunto alla collection AvailablePlugins che rende dispobinibili all’applicazione host tutti i plugin caricati.

Il cuore dell’applicazione Host

Questo è il cuore dell’applicazione, che permette di effettuare molte operazioni sui plugin come vedremo in seguito.

Ma entriamo nel dettaglio dei vari passaggi che l’applicazione host deve fare per elaborare le informazioni del file App.Config

La modalità di elaborazione del file App.Config è suddivisa in step e per ogni step viene effettuata un’attività specifica :

1. Il primo step permette all’applicazione host di selezionare il componente o l’applicativo di cui si vuole creare e/o aggiornare il file App.Config.

Step1

Dopo aver selezionato l’assembly del componente o dell’applicativo si accede al secondo step, che visualizza tutte le informazioni sull’assembly selezionato, premendo il bottone contrassegnato con la caption “Avanti >” si accede allo step successivo.

Step1.1

2. Vengono caricati i plugin disponibili presenti nella sottodirectory \Plugins

FileSystem2

Questa operazione viene effettuata dal metodo privato dell’applicazione host LoadPlugIn():

private void LoadPlugIn()
{
try
{
// Aggiunge i plug in disponibili al tab control
tabControl.TabPages.Clear();

Global.Plugins.FindPlugins(Application.StartupPath
+ @"\Plugins");
int plugincount = 0;

// cicla sulla collection dei plugin disponibili
foreach (Host.Types.AvailablePlugin pluginOn in Global.Plugins.AvailablePlugins)
{
// assegna il percorso ed il nome del file config che si stà gestendo al plugin corrente
pluginOn.Instance.ExecutablePath = AppConfigFile;
pluginOn.Instance.MainInterface.Dock
= DockStyle.Fill;

// crea una nuova tab page e ne inizializza la caption con la propietà description del
// plugin corrente
TabPage Tab = new TabPage(pluginOn.Instance.Description);

// aggiunge la maininterface del plugin al contenitore tab page
Tab.Controls.Add(pluginOn.Instance.MainInterface);

// in fine rende disponibile la tab page al tab control
tabControl.TabPages.Add(Tab);
}
}
catch (Exception Ex)
{
MessageBox.Show(Ex.Message, Application.ProductName
+ ".LoadPlugIn()", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Quindi secondo il file di configurazione individuato dopo il primo step, verranno caricate le section implementate nei singoli plugin con le relative informazioni.

Step2

Step2.1

A questo punto è possibile gestire le informazioni, nella relativa section del file App.config, cliccando sul checkbox contrassegnato con la dicitura “Cripta questa Section utilizzando il provider” e, successivamente, selezionando il provider di crittografia la section corrente verrà criptata nel passaggio successivo.

Premendo il bottone contrassegnato con la caption “Avanti >” si accede allo step successivo.

Step3

3. Secondo le impostazioni selezionate, plugin per plugin, le informazioni verranno salvate nel file app.config precedentemente selezionato.

L’applicazione host :

Ciclerà sui plugin disponibili individuati precedentemente;

foreach (Host.Types.AvailablePlugin pluginOn in Global.Plugins.AvailablePlugins)
{
// per ogni instanza di plugin invocherà l’evento Generazione,
// che realizzerà il relativo evento nel singolo plugin.
pluginOn.Instance.Generazione();
}

Al termine dell’operazione verrà mostrato un report riepilogativo , che section per section notificherà all’utente la riuscita o meno dell’operazione.

Step4

Per Ogni Plugin Elaborato, l’istanza del plugin stesso restituirà un codice di stato dell’operazione derivante dall’enumerazione StatoElaborazioneEnum ed un messaggio di notifica.

// Ciclo sui plugin disponibili

foreach (Host.Types.AvailablePlugin pluginOn in Global.Plugins.AvailablePlugins)
{
ListViewItem lvitem
= new ListViewItem(pluginOn.Instance.Description);
lvitem.SubItems.Add(pluginOn.Instance.Messaggio);
switch (pluginOn.Instance.StatoElaborazione)
{
case PlugInInterface.StatoElaborazioneEnum.SectionNonElaborata:
lvitem.StateImageIndex
= 0;
break;
case PlugInInterface.StatoElaborazioneEnum.SectionElaborataConSuccesso:
lvitem.StateImageIndex
= 0;
break;
case PlugInInterface.StatoElaborazioneEnum.SectionElaborataConErrore:
lvitem.StateImageIndex
= 1;
break;
}
listViewStatusElaborazione.Items.Add(lvitem);
pluginOn.Instance.Dispose();
}

Infine andando a verificare l’app.config elaborato vedremo che le section selezionate sono state criptate :

Result

Lettura del File Config

Dopo aver criptato il file App.Config, continueremo ad accedere alle sue informazioni senza scrivere una sola linea di codice in più, infatti utilizzando le classi di accesso al file app.config, contenute nel namespace System.Configuration i valori criptati verrano letti in chiaro dai nostri applicativi che utilizzando il file.

Ad esempio, possiamo recuperare una connection string criptata, contenuta nella section ConnectionStrings :

ConnectionStringSettings settings;

settings
= ConfigurationManager.ConnectionStrings["ConnectionString"];

o recuperare un valore criptato contenuto nella section AppSettings:

string cmdtimeout = ConfigurationManager.AppSettings["CommandTimeOut"];

Ci sono alcune precisazioni da fare sul tool AppconfigManager; quando si criptano le informazioni dell’app.config, é necessario farlo, se abbiamo lo stesso prodotto installato su più macchine, macchina per macchina, se provassimo a criptare delle section e poi distribuire il file app.config sulle altre macchine, otterremmo il seguente errore:

Errore

Questo poiché i provider di crittografia per effettuare il loro lavoro utilizzano informazioni legate alla singola macchina, rendendo il file di configurazione impossibile da decriptare su di un’altra macchina.

Il Tool AppConfigManager attualmente, viene utilizzato in produzione per configurare alcuni prodotti dai clienti dell’azienda per cui lavoro, per eventuali segnalazioni di anomalie o chiarimenti sul tool in questione e perché no per eventuali feedback anche negativi, potete scrivere a questo indirizzo leo.alario.software@email.it .

Spero che questo tool possa essere utile, alla comunità di sviluppatori che eventualmente lo utilizzeranno, aumentando in modo notevole la sicurezza dei nostri applicativi e contestualmente rendendo più semplice, l’attività di configurazione degli stessi.

Note 

Nel progetto sono stati utilizzati diversi controlli gratuiti di terze parti, il progetto contiene nella cartella \..\Bin gli assembly per far funzionare correttamente il tutto.

Chiunque fosse interessato può scaricare i controlli o avere maggiori informazioni sugli stessi ai seguente link:

Ascend.Net controls http://www.codeplex.com/Tagging/TagDetail.aspx?TagName=Ascent.Net

Magic Library 1.7.4 http://www.dotnetmagic.com/magic_download.html controllo che consente di trasformare le nostre Windows Forms in Wizard con poche operazioni, inoltre il controllo contiene varie altre funzionalità non utilizzate in questo progetto.

SQL Server 2005 Circular Progress Bar http://www.codeproject.com/vb/net/sql2005circularprogress.asp controllo che riproduce la barra di avanzamento circolare di Sql Server 2005.

Only published comments... Oct 10 2007, 01:38 AM by Fabio.Cozzolino
Filed under: ,

Comments

 

Nikitto46 said:

Salve..non riesco a scaricare questo tool

November 12, 2009 3:32 AM
 

leo.alario said:

Ciao, puoi scaricarlo da questo indirizzo:

cid-2d0742d8f39d96e3.skydrive.live.com/.../AppConfigManager.zip

November 12, 2009 1:04 PM

About Fabio.Cozzolino

Fabio Cozzolino inizia la sua carriera da sviluppatore quando riscopre quello strano libricino blu con la scritta "BASIC" dato a corredo del suo Commodore 64. Da allora un susseguirsi di linguaggi, tra cui COBOL e Visual Basic, lo ha accompagnato fino alla conoscenza di .NET Framework. Dopo un periodo passato a dedicarsi allo sviluppo di applicazioni Windows, si è poi concentrato e appassionato al Web, approdando infine alla progettazione e realizzazione di applicazioni distribuite. E' MCAD.net e scrive regolarmente per la rivista italiana ioProgrammo. E' co-fondatore e Presidente di .netSide e ha scritto il libro Windows Communication Foundation uscito nel mese di novembre 2007 in allegato alla rivista ioProgrammo.

Powered by Community Server (Commercial Edition), by Telligent Systems