27
apr
09

estendere un’applicazione asp.net con plugin/estensioni

Un’applicazione ben scritta è un’applicazione che piace, ma un’applicazione ben scritta e che può essere migliorata, è il massimo. In questo tutorial spiegherò come creare e mettere in produzione un completo framework per la creazione di plugin o estensioni (io preferisco chiamarle estensioni) che permetterà di estendere e/o modificare il comportamento delle proprie applicazioni scrivendo una decina di righe di codice.

Prima di cimentarci nella scrittura di codice è bene chiarire due concetti: che cosa si intende per estensione e come funziona il framework.

Per estensione si intende una libreria che viene inserita nella cartella Bin dell’applicazione. Per semplicità e per maggiori performance ho preferito caricare una sola estensione da ogni assembly, quindi ogni assembly può contenere solo un’estensione.

Il framework ha un funzionamento molto semplice: All’avvio dell’applicazione (Application_Start nel Global.asax), si caricano tutti gli assebly contenente estensioni e si caricano anche queste. Dopo di ché le si inizializzano ed il gioco è fatto.

Il framework richiede che ogni estensione implementi un’interfaccia: IExtension. L’interfaccia IExtension presenta un unico metodo: Initialize; Quest’ultimo viene chiamato dall’applicazione (di solito) nell’evento Application_Start e il suo unico scopo è quello di inizializzare le proprietà e oggetti correlati.

Il punto di incontro tra un’applicazione ed il framework sono gli eventi: Nell’evento Initialize ogni estensione aggiungerà un handler ad un evento statico di un oggetto che interessa all’estensione. Questo permette di creare un’interfaccia globale delle estensioni che poi provvederanno da sole al proprio ciclo di vita. Ad esempio supponiamo di avere un Blog con un oggetto Post e che quest’ultimo abbia un evento Show statico. Ogni volta che il Blog mostrerà il post, scatenerà l’evento Show, che, richiamerà a sua volta ogni handler associato e quindi anche le estensioni che vogliono modificare il comportamento per la visualizzazione dei post. Semplice no?

Il primo passo è la definizione del Framework, quindi dell’interfaccia IExtension e di una classe che gestisca le estensioni caricate. Creiamo quindi un nuovo progetto web di nome MyApplication, e aggiungiamo un’altro progetto C# Library che chiamiamo MyApplication.Extensibility.

Poi aggiungiamo un un’interfaccia, IExtension.cs nel profetto del framework (MyApplication.Extensibility) e scriviamo il seguente codice:

    1 namespace MyApplication.Extensibility

    2 {

    3     public interface IExtension

    4     {

    5         void Initialize();

    6     }

    7 }

L’interfaccia è molto semplice: espone solo il metodo Initialize. Ora passiamo alla creazione di una classe (ExtensionsManager) che gestirà tutte le estensioni caricate:

    1 namespace MyApplication.Extensibility

    2 {

    3     public class ExtensionManager

    4     {

    5 

    6         private static ExtensionManager m_Instance;

    7         private List<IExtension> m_Extensions = new List<IExtension>();

    8 

    9         public ExtensionManager() { }

   10 

   11         public void CollectExtensions(string configurationSection)

   12         {

   13             ExtensionsConfigurationSection section = (ExtensionsConfigurationSection)WebConfigurationManager.GetSection(configurationSection);

   14 

   15             if (!section.Enabled)

   16                 return;

   17 

   18             foreach (ExtensionElement extension in section.Extensions)

   19             {

   20                 if (extension.Enabled)

   21                     m_Extensions.Add((IExtension)Activator.CreateInstance(Type.GetType(extension.TypeName)));

   22             }

   23         }

   24 

   25         public void InitializeExtensions()

   26         {

   27             foreach (IExtension extension in m_Extensions)

   28             {

   29                 extension.Initialize();

   30             }

   31         }

   32 

   33         public List<IExtension> Extensions

   34         {

   35             get { return m_Extensions; }

   36         }

   37 

   38         public static ExtensionManager Instance

   39         {

   40             get

   41             {

   42                 if (m_Instance == null)

   43                     m_Instance = new ExtensionManager();

   44 

   45                 return m_Instance;

   46             }

   47         }

   48 

   49     }

   50 }

La prima cosa da notare dopo la lettura del codice è la presenza di un field di tipo ExtensionsManager, infatti questo field permette di rendere una classe Singleton, cioé una sola istanza della classe per tutto il ciclo dei vita dell’applicazione. La seconda cosa particolare è il riferimento alla classe ExtensionsConfigurationSection. Quest’ultima non è altro che una classe che definisce una sezione personalizzata all’interno di web.config. Dato che parlare dei files di configurazione è fuori dal tema del post, rimando ad un link dell’msdn: ConfigurationSection. Per il resto è molto semplice: Si utilizza la proprietà statica Instance per accedere all’istanza della classe e si richiama il metodo CollectExtensions che non fa altro che leggere tutte le estensioni definite del file web.config e aggiungerle alla collezione interna di estensioni (Extensions). Dopo aver caricato le estensioni, le si inizializzano utilizzando il metodo InitializeExtensions. Il tutto verrà eseguito nel file Global.asax.

Nel file di codice è presente la classe ExtensionsConfigurationSection.

Ora che si è creato il framework per l’estensibilità si può passare al ritrovamento e caricamento delle estensioni definite: andiamo nel file Global.asax del progetto Web e scriviamo quanto segue

    1 void Application_Start(object sender, EventArgs e)

    2         {

    3             ExtensionManager manager = ExtensionManager.Instance;

    4             manager.CollectExtensions(“MyApplication.Extensibility”);

    5             manager.InitializeExtensions();

    6         }

Le estensioni vengono caricate all’avvio del programma, così si evita di caricarle ogni volta che se ne ha bisogno. Non dovrebbero creare troppo overhead, anzi per niente se si utilizza il metodo descritto in questo post. Il metodo CollectExtensions accetta un parametro che indica il nome della sezione del file web.config.

Ora che il framework è pronto, le estensioni vengono caricate, mancano solo due cose: le estensioni e la parte ‘Core’ del programma, cioé tutte le classi che il nostro programma vuole esporre alle estensioni.

Il Core dell’applicazione d’esempio che ho creato è molto semplice: essendo il mio applicativo un editor di codici BBCode (quella sintassi usata spesso nei forum per sostituire l’HTML, ad esempio il tag [B] sostituisce il tag <strong> dell’HTML. BBCode è stato creato semplicemente per non permettere all’utente di scrivere tag HTML, evitando code injections ecc..) ho definito una classe Document molto semplice ed una classe DocumentParser un po piu’ interessante:

    1 public delegate void DocumentEventHandler(object sender, DocumentEventArgs e);

    2 

    3 public class DocumentEventArgs : EventArgs

    4 {

    5 

    6     public DocumentEventArgs(string documentText)

    7     {

    8         this.DocumentText = documentText;

    9     }

   10 

   11     public string DocumentText { get; set; }

   12 

   13 }

   14 

   15 public class Document

   16 {

   17 

   18     public Document(string name, string author, string text)

   19     {

   20         Name = name;

   21         Author = author;

   22         Text = text;

   23     }

   24 

   25     public string Name { get; set; }

   26 

   27     public string Text { get; set; }

   28 

   29     public string Author { get; set; }

   30 

   31     public static void OnServing(object sender, DocumentEventArgs e)

   32     {

   33         if (Serving != null)

   34             Serving(sender, e);

   35     }

   36 

   37     public static void OnParsing(object sender, DocumentEventArgs e)

   38     {

   39         if (Parsing != null)

   40             Parsing(sender, e);

   41     }

   42 

   43     public static event DocumentEventHandler Serving;

   44 

   45     public static event DocumentEventHandler Parsing;

   46 

   47 }

classe DocumentParser:

    1 public class DocumentParser

    2 {

    3 

    4     private Dictionary<string, string> BasicTags;

    5 

    6     public DocumentParser()

    7     {

    8         BasicTags = new Dictionary<string, string>()

    9         {

   10             { “[B]“, “<strong>” },

   11             { “[/B]“, “</strong>” },

   12             { “[I]“, “<i>” },

   13             { “[/I]“, “</i>” },

   14             { “[A]“, “<a href=\”" },

   15             { “[/A]“, “\” >” }

   16         };

   17     }

   18 

   19     public string Parse(Document document, bool raiseEvents)

   20     {

   21         string parsed = document.Text;

   22 

   23         foreach (KeyValuePair<string, string> tag in BasicTags)

   24         {

   25             parsed = parsed.Replace(tag.Key, tag.Value);

   26         }

   27 

   28         if (raiseEvents)

   29         {

   30             DocumentEventArgs args = new DocumentEventArgs(parsed);

   31             Document.OnParsing(this, args);

   32             parsed = args.DocumentText;

   33         }

   34 

   35         return parsed;

   36     }

   37 

   38     public string Show(Document document, bool raiseEvents)

   39     {

   40         string newText = document.Text;

   41 

   42         newText = newText.Insert(0, “<pre>”);

   43         newText += “</pre>”;

   44 

   45         if (raiseEvents)

   46         {

   47             DocumentEventArgs args = new DocumentEventArgs(newText);

   48             Document.OnServing(this, args);

   49             newText = args.DocumentText;

   50         }

   51 

   52         return newText;

   53     }

   54 

   55 }

La classe Document è molto semplice: espone le proprietà Name (Nome), Text (Corpo), Author (Autore) e due eventi, ai quali le estensioni andranno ad aggiungere un handler nel caso il documento venga mostrato (Serving) o durante la fase di parsing del BBCode (Parsing).

La classe DocumentParser non è altro che una classe che sostituisce i tag BB con quelli HTML nel metodo Parse ed aggiunge il testo tra due tag ‘pre’ nel metodo Show. Entrambi i metodi accettano un parametro boolean chiamato raiseEvents che permette di ignorare l’azione delle estensioni, in quanto gli eventi ai quali sono registrati, non vengono scatenati.

E’ meglio provare subito il codice. Quindi si crea una semplice pagina con una TextBox che contiene il testo, un pulsante ‘Transform’ che effettua il parsing e mostra il risultato in una Label. Inoltre è presente anche una CheckBox che indica se gli eventi devono essere scatenati o meno.

PluginFramework - Example Page

Come si può notare, la pagina è molto semplice. Passiamo al codice:

    1 protected void Page_Load(object sender, EventArgs e)

    2 {

    3 }

    4 

    5 protected void ButtonTransform_Click(object sender, EventArgs e)

    6 {

    7     Document document = new Document(“Semplice Documento”, “Umar Jamil”, TextBoxDocument.Text);

    8     DocumentParser parser = new DocumentParser();

    9 

   10     string parsed = parser.Parse(document, CheckBoxRaiseEvents.Checked);

   11     document.Text = parsed;

   12     LabelTransformed.Text = parser.Show(document, CheckBoxRaiseEvents.Checked);

   13 }

Si crea un documento, si specifica il nome, l’autore ed il contenuto. Poi si crea un’istanza della classe DocumentParser, si effettua il parsing e si mostra il risultato nella label LabelTransformed. Da notare l’uso del metodo Show della classe DocumentParser per mostrare il documento.

Proviamo a lanciare il programma scrivendo un testo che contiene tag del tipo [B], [I]  e notiamo l’effetto: l’output verrà formattato usando tag HTML.

PluginFramework - Running Example Page

Ma l’obiettivo dell’articolo non è mica creare un editor di testo, ma è quello di creare un editor di testo estendibile. Quindi cominciamo la fase di scrittura dell’estensione: creiamo un nuovo progetto, di tipo class library e chiamiamolo ParsingExtensions, dopodiché aggiungiamo una classe DateExtension con questo codice:

    1 namespace ParsingExtensions

    2 {

    3     public class DateExtension : IExtension

    4     {

    5 

    6         #region IExtension Members

    7 

    8         public void Initialize()

    9         {

   10             Document.Parsing += new DocumentEventHandler(Document_Parsing);

   11             Document.Serving += new DocumentEventHandler(Document_Serving);

   12         }

   13 

   14         void Document_Serving(object sender, DocumentEventArgs e)

   15         {

   16             e.DocumentText += “<pre>Modified by Date Extension 1.0</pre>”;

   17         }

   18 

   19         void Document_Parsing(object sender, DocumentEventArgs e)

   20         {

   21             e.DocumentText = e.DocumentText.Replace(“[DATE]“, DateTime.Now.ToShortDateString());

   22         }

   23 

   24         #endregion

   25 

   26     }

   27 }

Ovviamente bisogna aggiungere i vari riferimenti: Business Layer, Data Layer e ovviamente al framework… cioé tutti quelli di cui l’estensione ha bisogno per funzionare.

Analizziamo l’estensione: non fa altro che aggiungere una data ogni volta che incontra il tag [DATE] e ogni volta che il documento è mostrato, aggiunge del testo finale indicando che il testo è stato modificato dall’estensione. Notiamo che nel metodo Initialize l’estensione aggiunge due handler ed il gioco è fatto. Ogni volta che un documento verrà mostrato o verrà effettuato il parsing, la nostra estensione entrerà in azione.

Non è ancora finita, in quanto l’estensione è pronta, ma l’applicazione web non la conosce. Quindi andiamo a modificare la sezione del web.config che contiene le estensioni:

    1 <MyApplication.Extensibility enabled=true>

    2   <extensions>

    3     <add enabled=true typeName=ParsingExtensions.DateExtension, ParsingExtensions/>

    4   </extensions>

    5 </MyApplication.Extensibility>

Ora basta aggiungere l’assembly che contiene l’estensione nella cartella Bin dell’applicazione web ed il gioco è fatto. Vediamo il risultato:

PluginFramework - Running Example Page 2

Evviva! finalmente, dopo ore di lavoro a sbattere la testa, il framework funziona. Per disabilitarlo basterà impostare l’attributo ‘enabled’ a ‘false’ per la sezione delle estensioni o su ciascuna estensione da disabilitare.

Vorrei concludere dicendo che il mio è uno dei tanti metodi che è possibile utilizzare per creare estensioni, magari il mio non è neanche molto bello, però funziona. Ogni persona è libera di proporre, creare e condividere le propie invenzioni.

Ovviamente è possibile riutilizzare il mio framework senza alcuna modifica. Mi raccomando, se lo utilizzate, specificate l’autore ed il link al blog.

Ecco infine il codice che ho scritto: Download.

Ringrazio per la lettura, Umar J.


0 Risposte a “estendere un’applicazione asp.net con plugin/estensioni”



  1. Lascia un commento

Lascia un Commento

Fill in your details below or click an icon to log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Log Out / Modifica )

Foto Twitter

You are commenting using your Twitter account. Log Out / Modifica )

Foto di Facebook

You are commenting using your Facebook account. Log Out / Modifica )

Connecting to %s


aprile: 2009
L M M G V S D
« mar    
 12345
6789101112
13141516171819
20212223242526
27282930  

Follow

Get every new post delivered to your Inbox.