Windows Forms .NET - Gestione modulare applicazione

Per soddisfare la richiesta di qualche amico che mi ha chiesto delucidazioni in merito presento questa soluzione che esemplifica come ipoteticamente potrebbe essere realizzata la modularità nel caricamento delle forms su .NET.

Come è noto su .NET si possono utilizzare direttamente le librerie scritte in un linguaggio .NET.

Quest’ultima affermazione di solito si trova nelle prime pagine dei manuali, quelle che parlano del Framework .NET, pagine che di solito, grave errore di chi legge, vengono saltate.

 

Chiunque scrive una applicazione su .NET “sa” che è possibile introdurre nel proprio progetto il riferimento a librerie esterne create con uno qualsiasi degli altri linguaggi .NET, linguaggi in sostanza che producono tutti il medesimo codice “managed”.

 

Dopo l’inserimento dei riferimenti alla libreria, i tipi contenuti possono essere utilizzati direttamente dal progetto, ad esempio eventuali forms presenti nella libreria possono essere aperte allo stesso modo in cui sono attivate le forms del nostro progetto.

 

Esiste però un altro metodo di interfacciamento con le librerie esterne scritte per .NET, questo metodo prevede l’utilizzo della Reflection.

Troviamo quindi i due metodi di interfacciamento:
1) Inserimento della libreria tra i riferimenti con utilizzo diretto dei tipi
2) Caricamento dinamico della libreria utilizzando la Reflection

Il presente articolo riguarda il secondo metodo.

La tecnica di gestire dinamicamente il caricamento delle  Dll consente di avere molteplici benefici come ad esempio:

-        Avere sviluppatori diversi che si occupano ciascuno di una parte del progetto senza conflitti con gli altri progetti

-        Possibilità di utilizzare per lo sviluppo di ciascun modulo uno qualsiasi dei linguaggi .NET.

-        Gestire delle personalizzazioni in parole povere al posto della libreria standard utilizzare un’altra libreria modificando “ad hoc” la libreria standard in tutto o in parte.

-        Avere la possibilità di diversificare e specializzare l’applicativo (verticalizzazione), tenere riservate alcune librerie che vengono attivate solo in funzione di altri fattori

-        Avere la possibilità di rendere modulare il proprio applicativo, nel senso che è possibile “rilasciare” uno o più moduli in funzione del livello di licenza acquistata dal cliente.

-        Aggiornare e distribuire solo le librerie che per un qualsiasi motivo devono essere aggiornate e/o modificate

-        Realizzare una compilazione più veloce

-        Gestire tests e validazioni solo sugli oggetti modificati

Per la realizzazione si prevede di utilizzare un minimo di 3 progetti  (2 principali + 1 "satellite").

  1. Progetto principale compilato come EXE WinForm, predisposto per gestire i moduli dell'applicazione e per gestire il caricamento dinamico degli stessi
  2. Progetto librerie comuni, compilato come DLL con lo scopo di mantenere una interfaccia comune tra i moduli
  3. Progetto libreria "plugin" contenente la parte specifica dell'applicativo (ad esempio, libreria vendite, libreria aquisti, libreria co.ge., ecc.)

La mia implementazione per l'esempio prevede pertanto 3 moduli:

  1. Progetto principale, "FormsLoader"
  2. Progetto libreria comune, denominato "CommonObjects"
  3. Progetto libreria di prova denominato "ModuloProva"

Il progetto “FormsLoader” prevede l’inserimento di “CommonObjects” tra i riferimenti del progetto.

Il progetto “CommonObjects” non ha bisogno di riferimenti esterni (se non quelli necessari per la compilazione del progetto).

Il progetto “ModuloProva” ha “CommonObjects” tra i riferimenti.

FormsLoader

Per poter realizzare il caricamento dinamico delle librerie plugin è necessario che il progetto principale individui la cartella da cui caricare le Dll plugin, sono utilizzabili tutti i percorsi validi come ad esempio "StartupPath" e/o impostazioni parametrizzate sui settaggi che facciano riferimento a posizioni assolute o relative.

La posizione della cartella plugin per FormsLoader è definita in una variabile di Settings che si chiama PluginLocation, su questa cartella devono confluire tutte le dll che si vogliono attivare.

Questa variabile viene data in pasto ad un metodo che enumera tutti i file presenti nella cartella definita come origine dei plugin.

     For Each s As String In System.IO.Directory.GetFiles(sDir)

        CaricaAssembly(s)

     Next

 

Per  ogni elemento incontrato nella cartella viene richiamato il metodo “CaricaAssembly” passandogli come argomento il nome completo della dll, in produzione è opportuno che si facciano maggiori controlli sulle dll da lanciare.

 

Per comprendere appieno i tests fatti nel caricamento è opportuno ricordare che su .NET esistono degli attributi con i quali si può “decorare” ogni classe e ogni metodo, per ciascuna classe / metodo sono possibili più attributi diversi.

 

Su CommonObjects ho definito appunto una classe che eredita da Attribute con la quale ho previsto di “decorare” le classi/forms da interfacciare nei plugins.

 

CaricaAssembly cicla sui tipi (Type) presenti sull’assembly, la funzione GetTypes() restituisce tutti i tipi contenuti.

Ogni form (oggetto di tipo form) viene inserita come riferimento nel  “Tag” di un oggetto di tipo  ListViewItem, ListViewItem che a sua volta alimenta una ListView per la presentazione nel menù utente.

Naturalmente sono possibili a questo livello le più ampie impostazioni e scelte, sia stilistiche che tecniche, solo a titolo di esempio una treeview con una distribuzione “ad albero” delle varie gerarchie di moduli e una listview con tutti i figli contenuti nella treeview su cui si è posizionati.

 

     Private Sub CaricaAssembly(ByVal pAssemblyName As String)

        If System.IO.File.Exists(pAssemblyName) Then

            Try

                Dim asmb As Assembly = Assembly.LoadFrom(pAssemblyName)

                For Each t As Type In asmb.GetTypes()

                    If t.BaseType.Equals(GetType(Form)) Then

                        Dim atr() As Attribute = t.GetCustomAttributes(GetType(ProgramsAttribute), True)

                        If atr.Length <> 0 Then

                            Dim pgm As ProgramsAttribute = CType(atr(0), ProgramsAttribute)

                            Dim li As New ListViewItem

                            IndexBitmap += 1

                            li.Tag = t

                            li.Text = pgm.Descrizione

                            li.ImageIndex = CType(IndexBitmap Mod ImagePgm.Images.Count, Integer)

                            ListaPgm.Items.Add(li)

                        End If

                    End If

                Next

            Catch ex As Exception

                MessageBox.Show("Impossibile caricare " + pAssemblyName + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace)

            End Try

        End If

    End Sub

Ho impostato un evento "DoubleClick" (doppio click) sul  ListView presente nella form principale (MainForm) sul gestore dell'evento tramite un semplice controllo è definita e richiamata la form memorizzata sull'elemento della ListView.

   Private Sub StartProgram(ByVal pObj As Object, ByVal pItem As ListViewItem)

        Dim found As Boolean = False

        Dim tipoForm As Type = DirectCast(pObj, Type)

        Dim frm As Form = Nothing

        For Each f As Form In Me.OwnedForms

            If f.[GetType]().Equals(tipoForm) Then

                found = True

                frm = f

                Exit For

            End If

        Next

        If Not found Then

            Dim img As Image = DirectCast(ImagePgm.Images(pItem.ImageIndex), Image)

            frm = DirectCast(Activator.CreateInstance(tipoForm), Form)

            Dim oBitmap As Bitmap = DirectCast(ImagePgm.Images(pItem.ImageIndex), Bitmap)

            frm.Icon = System.Drawing.Icon.FromHandle(oBitmap.GetHicon())

            oBitmap.Dispose()

            frm.Owner = Me

        Else

            frm.Activate()

            frm.WindowState = FormWindowState.Normal

        End If

        frm.Show()

    End Sub

CommonObjects

Per ogni form collegata durante il caricamento dell'assembly è previsto siano definiti alcuni parametri impostati tramite l'utilizzo di una classe apposita che eredita da Attribute che definisce 3 campi: Gruppo, Modulo e Descrizione.

Gruppo e Modulo possono essere utilizzati come indicatori del livello su cui appendere la form indirizzata, ad esempio se si genera un menu con sottomenu oppure una treeview con nodi padre e figlio.

Or AttributeTargets.Struct, AllowMultiple:=True)> _

Public Class ProgramsAttribute

    Inherits Attribute

    Private mGruppo As String

    Private mModulo As String

    Private mDescrizione As String

    Public Sub New(ByVal pGruppo As String, ByVal pModulo As String, ByVal pDescrizione As String)

        Me.Gruppo = pGruppo

        Me.Descrizione = pDescrizione

        Me.Modulo = pModulo

    End Sub

    Private Sub New()

    End Sub

    Public Property Gruppo() As String

        Get

            Return mGruppo

        End Get

        Set(ByVal Value As String)

            mGruppo = Value

        End Set

    End Property

    Public Property Modulo() As String

        Get

            Return mModulo

        End Get

        Set(ByVal Value As String)

            mModulo = Value

        End Set

    End Property

    Public Property Descrizione() As String

        Get

            Return mDescrizione

        End Get

        Set(ByVal Value As String)

            mDescrizione = Value

        End Set

    End Property

End Class

Questo modulo "ProgramsAttribute" è presente in un progetto DLL (CommonObjects) che è richiamato anche dalle forms "satelliti", ogni form (o meglio la classe form) è "decorata" con gli attributi


"Gruppo1", "Prima Form", "Descrizione Prima Form")> _

 secondo la normale sintassi relativa agli attributi personalizzati.

ModuloProva

Il terzo progetto: "ModuloProva" è un progetto DLL contiene le forms, naturalmente è possibile definire più progetti e più dll per le forms.

"Gruppo1", "Prima Form", "Descrizione Prima Form")> _
Public Class PrimaForm
End Class

Il risultato è che un progetto che evidenzia come definire e implementare il caricamento dinamico delle forms.

Print | posted on mercoledì 23 maggio 2007 17:20

Feedback

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Chick at 05/06/2006 19:49
Gravatar Ti segnalo che il link per il download non funziona..
Ciao

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Luciano Bastianello at 05/06/2006 19:54
Gravatar Prova con click con il pulsante destro e "salva destinazione come" (Firefox) oppure "salva oggetto con nome" per Internet Explorer

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Stefano at 05/06/2006 20:21
Gravatar Ottima Mini_Guida. Oltre ad insegnarmi ciò che spiega, ho capito come si lavora con le DLL. Grazie mille

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Carlo at 27/11/2006 13:32
Gravatar Mi aggiungo a chick il link non va.
In tutti i modi da te citati risponde sempre altervista con una pagina di default.
Ciao

PS: Bella la guida m'ha chiarito un paio di dubbi.

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Ruggiero Caporusso at 10/01/2007 21:04
Gravatar Complimenti per il blog e per l'articolo in oggetto che trovo ottimo.
Allo scopo di impadronirmi meglio della tecnica ho cercato di ampliare le funzionalita del tuo lavoro per esempio cercando di trasferire dai vari moduli le icone della proprietà ICON da rappresentare nei vari nodi del FormsLoader, ma non ci riesco.
Ho provato a passare come parametro al CommonObject uno degli oggetti SystemIcons, ma ottengo un errore "non è un valore costante".
Potresti darmi delle dritte ?

Grazie

Ruggiero

# MultiForm - MasterDrive.it - Information Technology Developers Community

Left by Pingback/TrackBack at 06/10/2007 14:58
Gravatar MultiForm - MasterDrive.it - Information Technology Developers Community

# come strutturare il programma.... - MasterDrive.it - Information Technology Developers Community

Left by Pingback/TrackBack at 06/12/2007 16:56
Gravatar come strutturare il programma.... - MasterDrive.it - Information Technology Developers Community

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Windows Forms .NET - Gestione mo at 28/06/2008 13:23
Gravatar Grande, devo ancora provare tutto ma l'articolo è chiarificante.

Grazie

#  Riferimenti a runtime - MasterDrive.it - Information Technology Developers Community

Left by Pingback/TrackBack at 07/08/2009 16:33
Gravatar Riferimenti a runtime - MasterDrive.it - Information Technology Developers Community

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Giorgio Brausi at 19/09/2009 05:41
Gravatar Complimenti davvero! Ottimo esempio.
Il migliore che ho trovato in rete, perchè consente di utilizzare moduli-plugin senza dover creare alcun riferimento nel programma principale.
L'ho preso come base di partenza per elaborare una soluzione in FatturaFast NET.
Giorgio

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Mirko at 14/01/2010 19:06
Gravatar Ciao e complimenti per l'articolo.Ho una domanda però:
se creo una libreria che implementa una Interfaccia, asmb.GetTypes() ritorna questo errore: "Impossibile caricare uno o più tipi richiesti. Per ulteriori informazioni, recuperare la proprietà LoaderExceptions." . Nell'altro caso funziona correttamente. Perchè?

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Mirko at 14/01/2010 19:25
Gravatar Voglio specificare che in C# funziona alla perferzione! solo con il Vb.net non va...

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Luciano Bastianello at 14/01/2010 20:02
Gravatar Non ho possibilià a breve di verificare il bug sulla versione vb.net.
Me lo sono appuntato.
Grazie comunque della segnalazione.

# re: Windows Forms .NET - Gestione modulare applicazione

Left by Ciro at 25/01/2010 20:29
Gravatar Ciao Luciano, ho letto molti dei tuoi articoli e devo dire che sono impressionanti. Complimenti !! Ho sfruttato ed ampliato la tua tecnologia per rendere modulare un sw gestionale creato tempo fa e devo dire che funziona alla grande.

GRAZIE !!

Your comment:





 
Please add 3 and 6 and type the answer here:

Copyright © Luciano Bastianello

Design by Bartosz Brzezinski

Design by Phil Haack Based On A Design By Bartosz Brzezinski