Buon Anno e scripting :-)

Stavo per bloggare auguri di buon anno e felice anno nuovo a tutti quando ho pensato che tutto sommato non volevo "sprecare" un blog solo per gli auguri :-) e allora ho deciso di scrivere anche qualcosa di tecnico.
L '' idea mi ? venuta grazie ad una mail che mi chiedeva come fare ad aggiungere al proprio software un sistema di automatizzazione alla VBA.
In pratica, l''idea che il proprio programma potesse essere governato da un linguaggio che lo stesso cliente poteva scrivere come avviene con le macro di Excel.
La richiesta era quella che il cliente attraverso un editor qualunque (notepad) potesse modificare alcune regole che il programma invocava in maniera dinamica.

Allora ho pensato ad un vecchio progetto che avevo fatto e che ho convertito in VS 2005.

Ovviamente l'idea ? quella di utilizzare il CodeDom di .NET per fare in modo di compilare al volo delle classi C# alle quali passare eventualmente degli oggetti dell'applicazione. 

Ho creato una piccola applicazione che potete scaricare qui che mostra come si pu? ottenere con poche righe di codice quello che la mail richiedeva.
Partendo da un file di testo scritto in c# il programma deve:

1) Caricare in memoria il file
2) Compilare il file in un assembly in memoria tramite le classi messe a disposizione dal framework
3) Creare gli oggetti presenti nello script caricato e invocare i metodi al suo interno

Il passo uno ? semplice , usiamo un reader qualunque :-)

Il codice per il passo due ? il seguente

// Creo un oggetto di tipo CSharpProvider

Microsoft.CSharp.CSharpCodeProvider n=newCSharpCodeProvider();

// Creo un istanza di parametri da passare al compilatore

CompilerParameters p=newCompilerParameters();

p.GenerateInMemory =true ;

// Imposto le reference al framework ed eventualmente ad altre dll

p.ReferencedAssemblies.Add("System.dll" );

p.ReferencedAssemblies.Add("System.Windows.Forms.dll" );

p.ReferencedAssemblies.Add("System.Data.dll" );

p.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

p.GenerateExecutable=false ;

p.OutputAssembly="test" ;  

// Passo i parametri la metodo CompileAssembluFromSource dove in Script c'? il file cs caricato

// La versione 1.0 richiedeva di passare ad una interfaccia ICompile

// r ritorna una collezione di valori di ritorno

CompilerResults r= n.CompileAssemblyFromSource(p,Script);

// Rappresenta l'assembly compilato

CompiledAssembly = r.CompiledAssembly;

Il passo tre è più semplice

// Creo un istanza di una classe presente nel file cs che ha generato l'assembly

// All'interno di Class c'? il nome della classe da creare

objectobj=CompiledAssembly.CreateInstance(Class);

Type t=obj.GetType();

// Recupero un oggetto MethodInfo per invocare il metodo passato

// All'interno di Method c'? il nome del metodo da invocare

MethodInfo m=t.GetMethod(Method);

if(ascParams.Count > 0)

{

   object[] par = newobject[ascParams.Count];

   // Creo i parametri

   for(inti = 0; i < ascParams.Count; i++)

   {

      objectlobj = null ;

      stringtp = ((scParameters)ascParams[i]).Type;

      // L'interfaccia di esempio controlla di che tipo sia il parametro passato

      if(tp =="System.String")

         // Stringa , ok, la passiamo cos?

         lobj = ((scParameters)ascParams[i]).Value;

      elseif(tp == "System.Object" )

         // Oggetto, vuol dire creare un istanza della classe passata e usarla come parametro

         lobj = Assembly.GetExecutingAssembly().CreateInstance(((scParameters)ascParams[i]).Value,true);

      elseif(tp == "Object Reference" )

         // Creo un oggetto locale e lo passo all'assembly (come se passassi il mio OM)

         lobj = objref;

      else

      {

         // Se non ? stringa devo convertire da stringa al tipo inserito

         // per fare questa operazione utilizzo il metodo Parse di .NET

         // Invocando tutto dinamicamente :-)

         lobj = AppDomain.CurrentDomain.CreateInstanceAndUnwrap("mscorlib" , tp,trueBindingFlags.Default,null,null,null,null,null);

         MethodInfo mm = lobj.GetType().GetMethod("Parse",newType[] {typeof(string) });

        lobj = mm.Invoke(lobj,newObject[] { ((scParameters)ascParams[i]).Value });

      }

      par[i] = lobj;

   }

   try

   {

      // Invoco il metodo

      m.Invoke(obj, par);

   }

   catch(Exception exp)

   {

      // errore

   }

}

else

   m.Invoke(obj,null);

Se volete provare ad utilizzare il programma che trovate sul sito usate questa immagine come riferimento.

A, dimenticavo, Buon Natale e Buon Anno a tutti J