Build providers... sempre se li ho ben capiti...

di Andrea Zani, in .NET2,

La curiosità verso questo argomento mi era venuta leggendo tempo fa un blog di Daniele. Qualche tempo dopo ho avuto la fortuna di parlare dell'argomento con Cristian su un aereo diretto a Roma in cui l'ho bombardato di domande sull'argomento. Solo dopo molto tempo sono riuscito ad avere un po' di tempo e ritornare finalmente a casa dopo che per oltre un mese sono stato impegnato per un lavoro a Milano e solo ultimamente sono riuscito a installare Visual Studio 2005 beta 2, e tra le prime cose che ho provato è stato proprio l'argomento Build Providers (che poi tutta l'infrastruttura di asp.net si basa su di esso).

Forse sminuirò l'argomento all'inversosimile, ma i build providers permettono la scrittura automatica di classi per i nostri progetti, classi che saranno scritte e compilate automaticamente dal Framework e accessibili nel nostro codice e dall'intellisense di Visual Studio 2005 immediatamente. Per far questo dovremo scrivere una classe apposita che deriva da "BuildProvider". In più possiamo configurare la costruzione della classe da un file con una nostra estensione personalizzata. Infine, per unire il tutto, nel web.config dovremo aggiungere la nuova estensione che il Framework dovrà elaborare. Un casino quello che è scritto sinora? Non mi stupisce.

Vediamo un esempio il cui scopo sarà svelato solo alla fine:

<configuration>
 <system.web>
   <buildProviders>
                <add extension=".azx" appliesTo="Code" type="azBuildProvider.azBuildProvider" />
            </buildProviders>
  </compilation>
 </system.web>
</configuration>

In questa sezione nel web.config ho creato un nuovo build providers che cercherà nella directory "app_code" i file con estensioni ".azx" i quali saranno utilizzati dalla classe "azBuildProvider.azBuildProvider" per la costruzione delle classi.

<form title="Questionario">
<domanda id="cognome">Cognome</domanda>
<domanda id="nome">Nome</domanda>
<domanda id="eta">Eta'</domanda>
</form>

Questo è un ipotetico file "questionario.azx" - che è un banale file in formato xml - che grazie a quella impostazione nel web.config, sarà elaborato dalla classe "azBuildProvider". Vediamo ora questa classe:

namespace azBuildProvider
{
 using System;
 using System.CodeDom;
 using System.Web.Compilation;
 using System.Collections.Generic;
 using System.Text;
 using System.Xml;
 using System.IO;
 class azBuildProvider : BuildProvider
 {
  private XmlDocument AzDocument;
  private XmlDocument LoadDocument()
  {
   if (this.AzDocument == null)
   {
    using (Stream stream = this.OpenStream())
    {
     XmlDocument azDocument = new XmlDocument();
     azDocument.Load(stream);
     AzDocument = azDocument;
    }
   }
   
return this.AzDocument;
  }

  private string Title
  {
   get
   {
    XmlDocument azDocument = this.LoadDocument();
    XmlNode n = azDocument.SelectSingleNode("/form");
    return n.Attributes["title"].Value;
   }
  }
  public override void GenerateCode(AssemblyBuilder assemblyBuilder)
  {
   CodeCompileUnit buildUnit = new CodeCompileUnit();
   CodeNamespace buildNamespace = new CodeNamespace();
   CodeMemberProperty pprov = new CodeMemberProperty();
   pprov.Name = "OraAttuale";
   pprov.Type = new CodeTypeReference(typeof(DateTime));
   pprov.Attributes = MemberAttributes.Public;
   pprov.GetStatements.Add(new CodeSnippetExpression("return DateTime.Now;"));
   CodeMemberMethod pMethod = new CodeMemberMethod();
   pMethod.Name = "GetForm";
   pMethod.ReturnType = new CodeTypeReference(typeof(System.Web.UI.Control)); 
   pMethod.Attributes = MemberAttributes.Public;
   string code = "PlaceHolder ph = new PlaceHolder();" +
   "ph.Controls.Add(new LiteralControl(\"<h1>" + Title + "</h1>\"));" +
   "Table tb = new Table();"+
   "tb.BorderWidth = Unit.Pixel(3);"+
   "tb.CellSpacing = 3;TableRow trx;TableCell tc;TextBox t;";
   XmlNodeList nn = AzDocument.GetElementsByTagName("domanda");
   foreach (XmlNode nodo in nn)
   {
    code += "trx = new TableRow();" +
    "tc= new TableCell();" +
    "tc.Text = \"" + nodo.InnerText +
    "\";trx.Cells.Add(tc);" +
    "tc = new TableCell();" +
    "t = new TextBox();" +
    "t.ID=\"" + nodo.Attributes["id"].Value + "\"" +
    ";tc.Controls.Add(t);" +
    "trx.Cells.Add(tc);" +
    "tb.Rows.Add(trx);";
   }
   code += "ph.Controls.Add(tb);" +
   "return ph;";
   pMethod.Statements.Add(new CodeSnippetExpression(code));
   buildNamespace.Imports.Add(new CodeNamespaceImport("System"));
   buildNamespace.Imports.Add(new CodeNamespaceImport("System.Web"));
   buildNamespace.Imports.Add(new CodeNamespaceImport("System.Web.UI"));
   buildNamespace.Imports.Add(new CodeNamespaceImport("System.Web.UI.WebControls"));
   CodeTypeDeclaration pClass = new
      System.CodeDom.CodeTypeDeclaration(Title);
   pClass.Attributes = MemberAttributes.Public;
   pClass.Members.Add(pMethod);
   pClass.Members.Add(pprov);
   foreach (XmlNode nodo in nn)
   {
    CodeMemberProperty pprov2 = new CodeMemberProperty();
    pprov2.Name = nodo.Attributes["id"].Value;
    pprov2.Type = new CodeTypeReference(typeof(string));
    pprov2.Attributes = MemberAttributes.Public;
    pprov2.GetStatements.Add(new CodeSnippetExpression(
     string.Format("return System.Web.HttpContext.Current.Request[\"{0}\"]==null?\"\":System.Web.HttpContext.Current.Request[\"{0}\"].ToString();",
        nodo.Attributes["id"].Value)));
    pClass.Members.Add(pprov2);
   }
   buildNamespace.Types.Add(pClass);
   buildUnit.Namespaces.Add(buildNamespace);

assemblyBuilder.AddCodeCompileUnit(this, buildUnit);
  }
 }
}

La funzione interessante è "GenerateCode". Questa funzione costruisce una classe con il nome preso dall'attributo "title" in "form", con una funzione di nome "GetForm" che restituisce un interfaccia garfica con label e textbox con i dati presi dal file ".azx": per ogni nodo "domanda" viene creata una label con la descrizione e un textbox (si noti come viene creato il codice vero e proprio in c#). Infine vengono aggiunte anche delle proprietà, tra cui "OraAttuale" che ritorna la data e ora del sistema, e tante proprietà quanti sono i nodi "domanda" prensenti nel file.

Fine. Nell'editor di visual Studio 2005 è possibile scrivere:

<script runat="server">
  void Page_Load(object sender, EventArgs e)
  {
      Questionario q = new Questionario();
      aa.Controls.Add(q.GetForm());
  }
</script>
<form id="form1" runat="server">
<asp:PlaceHolder id="aa" runat="server"></asp:PlaceHolder>
<asp:Button id="pp" runat="server" Text="Invia" OnClick="pp_Click"></asp:Button></form>

Per avere:

Interfaccia grafica

Ma l'inserimento di un form senza la possibilità di riprendere i dati immessi dall'utente è inutile, vediamo cosa possiamo fare ora con il nostro codice dopo che l'utente ha cliccato su "Invia" nell'evento del button:

Questionario q = new Questionario();
Response.Write(q.OraAttuale + "<br />");
Response.Write(q.cognome + "<br />");
Response.Write(q.nome + "<br />");
Response.Write(q.eta + "<br />");

Nella classe "Questionario" abbiamo le proprietà di tutti i campi inseriti nella form, e possiamo riferirci ad esse direttamente con intellisense compreso:

Intellisense

Se aggiungessimo un altro file ".azx" di questo tipo:

<form title="Articoli">
<domanda id="cod">Codice articolo</domanda>
<domanda id="descrizione">Descrizione</domanda>
<domanda id="prezzo">Prezzo unitario</domanda>
<domanda id="sconto">Sconti</domanda>
</form>

Avremo a disposizione immediatamente una nuova classe di nomi "Articoli" in grado di costruirsi un modo autonomo una propria interfaccia grafica e con le proprietà corrette per il nuovo contenuto:

<script runat="server">
void Page_Load(object sender, EventArgs e)
{
     Articoli q = new Articoli();
     aa.Controls.Add(q.GetForm());
}

Che visualizzerà questo:

Preview

Naturalmente anche i campi all'intero saranno inseriti tra le proprietà della classe:

ancora intellisense

Tutto questo (tranne per le proprietà dei campi nel form) era facilmente realizzabile leggendo lo stesso file "xml" e costruendo al volo il form. Ma in questo modo la pesante costruzione dell'interfaccia grafica sarà fatta ad ogni richiesta di pagina; invece, grazie ai build providers, la classe per la costruzione del form sarà eseguita solo una volta alla compilazione del progetto, e tutte le volte che sarà richiamata tale pagina avrà già la classe pronta per il massimo delle prestazioni. Il bello è che è sufficiente modificare, in questo caso, un file ".azx" per avere la ricompilazione automatica della classe. L'utilità di una simile tecnica è innegabile: chi non ha mai scritto un generatore di codice per funzioni ripetitive nei propri progetto (per esempio per l'accesso al database)?

Questo è quanto ho capito dai build providers. Spero che Daniele e Cristian non stiano ridendo in caso ho scritto fesserie.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese