1. Code
  2. Coding Fundamentals

Extiende tus aplicaciones .NET con complementos

¿Tu aplicación carece de una funcionalidad impresionante? permite que otros desarrolladores interesados amplíen tu aplicación a través de complementos. Al abrir tu aplicación en su totalidad a funcionalidades adicionales, puedes construir una comunidad próspera alrededor de tu aplicación, ¡incluso potencialmente un mercado!.
Scroll to top

Spanish (Español) translation by Ana Paulina Figueroa (you can also view the original English article)

¿Tu aplicación carece de una funcionalidad impresionante? permite que otros desarrolladores interesados amplíen tu aplicación a través de complementos. Al abrir tu aplicación en su totalidad a funcionalidades adicionales, puedes construir una comunidad próspera alrededor de tu aplicación, ¡incluso potencialmente un mercado!.

¡En el tutorial de hoy vamos a aprender justamente eso!.


Paso 0 - Nuestro plan

Dependiendo de qué versión de Visual Studio estés usando, y de la versión del framework para el que estés desarrollando, algunas capturas de pantalla pueden verse ligeramente diferentes.

Vamos a construir una aplicación de prueba de concepto que, al arrancar, cargue complementos que encontrará en uno de sus subdirectorios. Estos complementos son ensamblados .NET que contienen al menos una clase que implementa una interfaz en particular según lo definamos nosotros. Los conceptos de este tutorial deben poder transferirse fácilmente a tus aplicaciones existentes sin mucho trabajo. Después veremos un ejemplo más complejo utilizando componentes de la interfaz de usuario cargados desde un complemento.

Este tutorial fue escrito usando Microsoft Visual Studio 2010, en VB.NET y desarrollando para el Framework 4.0 de .NET. Los conceptos principales de este tutorial deben funcionar en otros lenguajes CLR, desde el Framework 1.1 de .NET en adelante. Hay algunas características de menor importancia que no funcionarán en otros lenguajes CLR (pero que deberían poder adaptarse con mucha facilidad) y algunos conceptos como los genéricos que evidentemente no funcionarán en .NET 1.1, etcétera.

Nota: Este es un tutorial de nivel experto para las personas que no tienen inconvenientes con la conversión de código entre diferentes lenguajes CLR y versiones del Framework de .NET. Se trata más de "reemplazar el motor de tu automóvil" que de "cómo conducir".


Paso 1 - Implementación inicial a pequeña escala

Nota: Tus referencias pueden ser diferentes dependiendo de la versión del Framework de .NET para la que estés desarrollando.

Vamos a comenzar implementando una versión a pequeña escala de nuestra aplicación. Necesitaremos tres proyectos en nuestra solución:

  • ConsoleAppA: Una aplicación de consola
  • ClassLibA: Una biblioteca de clases
  • AddonA: Una biblioteca de clases que actuará como nuestro complemento

Agrega una referencia a ClassLibA desde ConsoleAppAAddonA. A continuación el explorador de soluciones se verá de esta manera:

Creando la interfaz del complemento

Para que una clase compilada sea considerada compatible, cada uno de los complementos de nuestra aplicación necesitará implementar una interfaz específica. Esta interfaz definirá las propiedades y operaciones necesarias que la clase debe tener para que nuestra aplicación pueda interactuar con el complemento sin problemas. También podemos usar una clase abstract/MustInherit como base para los complementos, pero en este ejemplo usaremos una interfaz.

Aquí está el código de nuestra interfaz, que debe ser colocado en un archivo llamado IApplicationModule dentro de la biblioteca de clases ClassLibA.

1
2
Public Interface IApplicationModule
3
4
    ReadOnly Property Id As Guid
5
    ReadOnly Property Name As String
6
7
    Sub Initialise()
8
9
End Interface

Las propiedades y métodos que defines en la interfaz son arbitrarios. En este caso he definido dos propiedades y un método, pero en la práctica puedes y debes cambiarlos según sea necesario.

No vamos a usar las propiedades Id ni Name en nuestro primer ejemplo, pero son propiedades útiles de implementar, y es probable que quieras que estén presentes si estás usando complementos en producción.

Creando un complemento

Ahora vamos a crear el complemento en sí. Reiterando, para que cualquier clase sea considerada un complemento para nuestra aplicación, se necesita que implemente nuestra interfaz - IApplicationModule.

Aquí está el código para nuestro complemento básico, que debe ser colocado en un archivo llamado MyAddonClass dentro de la biblioteca de clases AddonA.

1
2
Imports ClassLibA
3
4
Public Class MyAddonClass
5
    Implements IApplicationModule
6
7
    Public ReadOnly Property Id As System.Guid Implements ClassLibA.IApplicationModule.Id
8
        Get
9
            Return New Guid("adb86b53-2207-488e-b0f3-ecd13eae4042")
10
        End Get
11
    End Property
12
13
    Public Sub Initialise() Implements ClassLibA.IApplicationModule.Initialise
14
        Console.WriteLine("MyAddonClass is starting up ...")
15
        'Perform start-up initialisation here ...

16
    End Sub
17
18
    Public ReadOnly Property Name As String Implements ClassLibA.IApplicationModule.Name
19
        Get
20
            Return "My first test add-on"
21
        End Get
22
    End Property
23
End Class

Paso 2 - Encontrando complementos en tiempo de ejecución

A continuación necesitamos una manera de encontrar complementos para nuestra aplicación. En este ejemplo vamos a asumir que ha sido creada una carpeta Addons en el directorio del archivo ejecutable. Si estás haciendo pruebas dentro de Visual Studio, ten en cuenta el directorio predeterminado para la salida de los proyectos, es decir ./bin/debug/, por lo tanto necesitarás un directorio ./bin/debug/Addons/.

Tiempo para reflexionar

Coloca el método TryLoadAssemblyReference mostrado más adelante dentro de Module1 de ConsoleAppA. Investigamos el ensamblado cargado mediante el uso de Reflection. Un tutorial en pseudocódigo sobre su funcionalidad es el siguiente:

  • Intenta cargar el dllFilePath dado como un ensamblado de .NET
  • Si hemos cargado el ensamblado con éxito, continúa
  • Por cada módulo en el ensamblado cargado
  • Por cada tipo en ese módulo
  • Por cada interfaz implementada por ese tipo
  • Si esa interfaz es la interfaz de nuestro complemento (IApplicationModule), entonces
  • Guarda un registro de ese tipo. Termina con la búsqueda.
  • Finalmente, devuelve todos los tipos válidos encontrados
1
2
    Private Function TryLoadAssemblyReference(ByVal dllFilePath As String) As List(Of System.Type)
3
        Dim loadedAssembly As Assembly
4
        Dim listOfModules As New List(Of System.Type)
5
        Try
6
            loadedAssembly = Assembly.LoadFile(dllFilePath)
7
        Catch ex As Exception
8
        End Try
9
        If loadedAssembly IsNot Nothing Then
10
            For Each assemblyModule In loadedAssembly.GetModules
11
                For Each moduleType In assemblyModule.GetTypes()
12
                    For Each interfaceImplemented In moduleType.GetInterfaces()
13
                        If interfaceImplemented.FullName = "ClassLibA.IApplicationModule" Then
14
                            listOfModules.Add(moduleType)
15
                        End If
16
                    Next
17
                Next
18
            Next
19
        End If
20
        Return listOfModules
21
    End Function

Arrancando nuestros complementos

Finalmente, ahora podemos cargar en memoria una clase compilada que implemente nuestra interfaz. Pero no tenemos código que podamos encontrar, instanciar ni podemos hacer llamadas a métodos en esas clases. A continuación crearemos código que haga justamente eso.

La primera parte es encontrar todos los archivos que pudieran ser complementos, como se muestra abajo. El código lleva a cabo una búsqueda de archivos relativamente sencilla, y por cada archivo DLL encontrado intenta cargar todos los tipos válidos de ese ensamblado.  Los archivos DLL descubiertos dentro de la carpeta Addons no son necesariamente complementos. Estos simplemente podrían contener funcionalidad adicional requerida por algún complemento pero no ser complementos por sí mismos.

1
2
        Dim currentApplicationDirectory As String = My.Application.Info.DirectoryPath
3
        Dim addonsRootDirectory As String = currentApplicationDirectory & "\Addons\"
4
        Dim addonsLoaded As New List(Of System.Type)
5
6
        If My.Computer.FileSystem.DirectoryExists(addonsRootDirectory) Then
7
            Dim dllFilesFound = My.Computer.FileSystem.GetFiles(addonsRootDirectory, Microsoft.VisualBasic.FileIO.SearchOption.SearchAllSubDirectories, "*.dll")
8
            For Each dllFile In dllFilesFound
9
                Dim modulesFound = TryLoadAssemblyReference(dllFile)
10
                addonsLoaded.AddRange(modulesFound)
11
            Next
12
        End If

A continuación necesitamos hacer algo con cada tipo válido que hemos encontrado. El siguiente código creará una nueva instancia del tipo, le asignará un tipo de dato, invocará al método Initialise() (que es simplemente un método arbitrario que definimos en nuestra interfaz) y luego mantendrá una referencia a ese tipo instanciado en una lista a nivel de módulo.

1
2
        If addonsLoaded.Count > 0 Then
3
            For Each addonToInstantiate In addonsLoaded
4
                Dim thisInstance = Activator.CreateInstance(addonToInstantiate)
5
                Dim thisTypedInstance = CType(thisInstance, ClassLibA.IApplicationModule)
6
                thisTypedInstance.Initialise()
7
                m_addonInstances.Add(thisInstance)
8
            Next
9
        End If

Poniéndolo todo junto, nuestra aplicación de consola debería comenzar a verse algo así:

1
2
Imports System.Reflection
3
4
Module Module1
5
6
    Private m_addonInstances As New List(Of ClassLibA.IApplicationModule)
7
8
    Sub Main()
9
        LoadAdditionalModules()
10
11
        Console.WriteLine('Finished loading modules ...')

12
        Console.ReadLine()
13
    End Sub
14
15
    Private Sub LoadAdditionalModules()
16
        Dim currentApplicationDirectory As String = My.Application.Info.DirectoryPath
17
        Dim addonsRootDirectory As String = currentApplicationDirectory & '\Addons\'

18
        Dim addonsLoaded As New List(Of System.Type)
19
20
        If My.Computer.FileSystem.DirectoryExists(addonsRootDirectory) Then
21
            Dim dllFilesFound = My.Computer.FileSystem.GetFiles(addonsRootDirectory, Microsoft.VisualBasic.FileIO.SearchOption.SearchAllSubDirectories, "*.dll")
22
            For Each dllFile In dllFilesFound
23
                Dim modulesFound = TryLoadAssemblyReference(dllFile)
24
                addonsLoaded.AddRange(modulesFound)
25
            Next
26
        End If
27
28
        If addonsLoaded.Count > 0 Then
29
            For Each addonToInstantiate In addonsLoaded
30
                Dim thisInstance = Activator.CreateInstance(addonToInstantiate)
31
                Dim thisTypedInstance = CType(thisInstance, ClassLibA.IApplicationModule)
32
                thisTypedInstance.Initialise()
33
                m_addonInstances.Add(thisInstance)
34
            Next
35
        End If
36
    End Sub
37
38
    Private Function TryLoadAssemblyReference(ByVal dllFilePath As String) As List(Of System.Type)
39
        Dim loadedAssembly As Assembly
40
        Dim listOfModules As New List(Of System.Type)
41
        Try
42
            loadedAssembly = Assembly.LoadFile(dllFilePath)            
43
        Catch ex As Exception
44
        End Try
45
        If loadedAssembly IsNot Nothing Then
46
            For Each assemblyModule In loadedAssembly.GetModules
47
                For Each moduleType In assemblyModule.GetTypes()
48
                    For Each interfaceImplemented In moduleType.GetInterfaces()
49
                        If interfaceImplemented.FullName = 'ClassLibA.IApplicationModule' Then

50
                            listOfModules.Add(moduleType)
51
                        End If
52
                    Next
53
                Next
54
            Next
55
        End If
56
        Return listOfModules
57
    End Function
58
End Module

Ejecución de prueba

En este punto deberíamos poder llevar a cabo una compilación completa de nuestra solución. Cuando tus archivos hayan sido compilados, copia el resultado del ensamblado compilado del proyecto AddonA en la carpeta Addons de ConsoleAppA. La carpeta Addons debe crearse dentro del directorio /bin/debug/. En mi computadora usando Visual Studio 2010, con las ubicaciones predeterminadas del proyecto y una solución llamada "DotNetAddons", la carpeta se encuentra aquí:

C:\Users\jplenderleith\Documents\Visual Studio 2010\Projects\DotNetAddons\ConsoleAppA\bin\Debug\Addons

Deberemos ver la siguiente salida al ejecutar el código:

1
2
MyAddonClass is starting up ...
3
Finished loading modules ...

Tal como está, el resultado no es llamativo ni impresionante, pero demuestra una pieza clave de la funcionalidad, es decir, en tiempo de ejecución podemos tomar un ensamblado y ejecutar el código contenido en el mismo sin tener ningún conocimiento existente sobre ese ensamblado. Esta es la base para la construcción de complementos más complejos.


Paso 3 - Integración con una interfaz de usuario

A continuación veremos cómo crear un par de complementos para proporcionar funcionalidad adicional dentro de una aplicación de Windows Forms.

De manera similar a como lo hicimos con la solución existente, crea una nueva solución con los siguientes proyectos:

  • WinFormsAppA: Una aplicación de Windows Forms
  • ClassLibA: Una biblioteca de clases
  • UIAddonA: Una biblioteca de clases que albergará un par de complementos con componentes de la interfaz de usuario.

Similar a nuestra solución anterior, tanto el proyecto WinFormsAppA como UIAddonA deben tener referencias a ClassLibA. El proyecto UIAddonA también necesitará una referencia a System.Windows.Forms para acceder a su funcionalidad.

La aplicación Windows Forms

Crearemos una interfaz de usuario rápida y simple para nuestra aplicación. Estará compuesta por un MenuStrip y un DataGridView. El control MenuStrip debe tener tres MenuItems

  • File (Archivo)
  • Add-ons (Complementos)
  • Help (Ayuda)

Acopla el DataGridView a su contenedor padre, que es el formulario en sí. Vamos a crear código para mostrar algunos datos de ejemplo en nuestra tabla. El MenuItem Add-ons (Complementos) será rellenado con todos los complementos que hayan sido cargados durante el arranque de la aplicación.

Esta es una captura de pantalla de la aplicación en este punto:

Vamos a escribir un poco de código para darle algo de funcionalidad al formulario principal de WinFormsAppA. Cuando el formulario cargue, éste invocará al método LoadAddons() que actualmente está vacío (lo llenaremos más adelante). Después generamos algunos datos de empleados de ejemplo que enlazaremos a nuestro DataGridView.

1
2
Public Class Form1
3
4
    Private m_testDataSource As DataSet
5
    Private m_graphicalAddons As List(Of System.Type)
6
7
    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
8
        LoadAddons()
9
        m_testDataSource = GenerateTestDataSet()
10
        With DataGridView1
11
            .AutoGenerateColumns = True
12
            .AutoResizeColumns()
13
            .DataSource = m_testDataSource.Tables(0)
14
        End With
15
    End Sub
16
17
    Private Sub LoadAddons()        
18
19
    End Sub
20
21
    Private Function GenerateTestDataSet() As DataSet
22
        Dim newDataSet As New DataSet
23
        Dim newDataTable As New DataTable
24
        Dim firstNames = {"Mary", "John", "Paul", "Justyna", "Michelle", "Andrew", "Michael"}
25
        Dim lastNames = {"O'Reilly", "Murphy", "Simons", "Kelly", "Gates", "Power"}
26
        Dim deptNames = {"Marketing", "Sales", "Technical", "Secretarial", "Security"}
27
28
        With newDataTable
29
            .Columns.Add("EmployeeId", GetType(Integer))
30
            .Columns.Add("Name", GetType(String))
31
            .Columns.Add("IsManager", GetType(Boolean))
32
            .Columns.Add("Department", GetType(String))
33
        End With
34
35
        For i = 1 To 100
36
            Dim newDataRow As DataRow = newDataTable.NewRow()
37
            With newDataRow
38
                .Item("EmployeeId") = i
39
                .Item("Name") = firstNames(i Mod firstNames.Count) & " " & lastNames(i Mod lastNames.Count)
40
                .Item("IsManager") = ((i Mod 20) = 0)
41
                .Item("Department") = deptNames(i Mod deptNames.Count)
42
            End With
43
            newDataTable.Rows.Add(newDataRow)
44
        Next
45
46
        newDataSet.Tables.Add(newDataTable)
47
        Return newDataSet
48
    End Function
49
50
End Class

Cuando ejecutemos la aplicación deberá verse así:

Regresaremos a nuestro LoadAddons() vacío después de que definamos las interfaces del complemento.

La interfaz del complemento

Vamos a definir la interfaz de nuestro complemento. Los componentes de interfaz de usuario estarán listados dentro del MenuStrip Add-ons (Complementos), y abrirán un formulario de Windows que tiene acceso a los datos a los que está vinculado nuestro DataGrid. Agrega un nuevo archivo de interfaz llamado IGraphicalAddon al proyecto ClassLibA y pega el siguiente código dentro de dicho archivo:

1
2
Public Interface IGraphicalAddon
3
4
    ReadOnly Property Name As String
5
    WriteOnly Property DataSource As DataSet
6
    Sub OnClick(ByVal sender As Object, ByVal e As System.EventArgs)
7
8
End Interface
  • Tenemos una propiedad Name (Nombre), que se explica por sí misma.
  • La propiedad DataSource se usa para "alimentar" datos a este complemento.
  • El método OnClick, que será usado como manejador cuando los usuarios hagan clic en la entrada de nuestro complemento en el menú Add-ons (Complementos).

Cargando los complementos

Agrega una biblioteca de clases llamada AddonLoader al proyecto de ensamblado ClassLibA con el siguiente código:

1
2
Imports System.Reflection
3
4
Public Class AddonLoader
5
    Public Enum AddonType
6
        IGraphicalAddon = 10
7
        ISomeOtherAddonType2 = 20
8
        ISomeOtherAddonType3 = 30
9
    End Enum
10
11
    Public Function GetAddonsByType(ByVal addonType As AddonType) As List(Of System.Type)
12
        Dim currentApplicationDirectory As String = My.Application.Info.DirectoryPath
13
        Dim addonsRootDirectory As String = currentApplicationDirectory & "\Addons\"
14
        Dim loadedAssembly As Assembly
15
        Dim listOfModules As New List(Of System.Type)
16
17
        If My.Computer.FileSystem.DirectoryExists(addonsRootDirectory) Then
18
            Dim dllFilesFound = My.Computer.FileSystem.GetFiles(addonsRootDirectory, Microsoft.VisualBasic.FileIO.SearchOption.SearchAllSubDirectories, "*.dll")
19
            For Each dllFile In dllFilesFound
20
21
                Try
22
                    loadedAssembly = Assembly.LoadFile(dllFile)
23
                Catch ex As Exception
24
                End Try
25
                If loadedAssembly IsNot Nothing Then
26
                    For Each assemblyModule In loadedAssembly.GetModules
27
                        For Each moduleType In assemblyModule.GetTypes()
28
                            For Each interfaceImplemented In moduleType.GetInterfaces()
29
                                If interfaceImplemented.Name = addonType.ToString Then
30
                                    listOfModules.Add(moduleType)
31
                                End If
32
                            Next
33
                        Next
34
                    Next
35
                End If
36
37
            Next
38
        End If
39
40
        Return listOfModules
41
    End Function
42
End Class

Puedes usar código similar a éste para restringir a los desarrolladores de complementos, de manera que solamente generen tipos de complementos específicos.

En esta clase hemos proporcionado una manera de cargar diferentes tipos de complementos. Puedes usar código similar a éste para restringir a los desarrolladores de complementos, de manera que solamente generen tipos de complementos específicos o para que al menos los categoricen. En nuestro ejemplo solamente usaremos el primer tipo de complemento declarado, es decir IGraphicalAddon.

Si tuviéramos otros tipos de complementos en nuestro proyecto, por ejemplo para mejorar la funcionalidad de generación de reportes o proporcionar útiles atajos de teclado, entonces no queremos que aparezcan listados en nuestro menú Add-ons (Complementos), ya que no tiene sentido que alguien pueda hacer clic en ese complemento.

Sin embargo, una vez dicho esto puede ser conveniente agregar una referencia a todos los complementos que estén bajo alguna barra de menú y que al hacer clic en ellos aparezca el formulario de opciones del complemento. No obstante, esto va mas allá del alcance de este tutorial.

El complemento gráfico

Vamos a construir el verdadero complemento de interfaz de usuario. Agrega los siguientes dos archivos al proyecto de ensamblado UIAddonA; una clase llamada UIReportAddon1 y un formulario de windows llamado UIReportAddon1Form. La clase UIReportAddon1 contendrá el siguiente código:

1
2
Imports ClassLibA
3
4
Public Class UIReportAddon1
5
    Implements ClassLibA.IGraphicalAddon
6
7
    Private _dataSource As DataSet
8
    Public WriteOnly Property DataSource As System.Data.DataSet Implements IGraphicalAddon.DataSource
9
        Set(ByVal value As System.Data.DataSet)
10
            _dataSource = value
11
        End Set
12
    End Property
13
14
    Public ReadOnly Property Name As String Implements IGraphicalAddon.Name
15
        Get
16
            Return "Managers Report"
17
        End Get
18
    End Property
19
20
    Public Sub OnClick(ByVal sender As Object, ByVal e As System.EventArgs) Implements IGraphicalAddon.OnClick
21
        Dim newUiReportingAddonForm As New UIReportAddon1Form
22
        newUiReportingAddonForm.SetData(_dataSource)
23
        newUiReportingAddonForm.ShowDialog()
24
    End Sub
25
End Class

Agrega un DataGridView a UIReportAddon1Form que esté acoplado dentro de su contenedor padre. Luego agrega el siguiente código:

1
2
Public Class UIReportAddon1Form
3
4
    Private m_providedDataSource As DataSet
5
6
    Public Sub SetData(ByVal DataSource As System.Data.DataSet)
7
        m_providedDataSource = DataSource
8
        FilterAndShowData()
9
    End Sub
10
11
    Private Sub FilterAndShowData()
12
        Dim managers = m_providedDataSource.Tables(0).Select("IsManager = True")
13
        Dim newDataTable = m_providedDataSource.Tables(0).Clone
14
        For Each dr In managers
15
            newDataTable.ImportRow(dr)
16
        Next
17
        DataGridView1.DataSource = newDataTable
18
    End Sub
19
20
    Private Sub UIReportAddon1Form_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
21
        Text = "List of managers"
22
    End Sub
23
End Class

Simplemente estamos proporcionando los medios para enviar datos al formulario, y luego lo dejamos decidir qué hacer con dicha información. En este caso, vamos a llevar a cabo un Select() en una DataTable, obteniendo una lista de todos los empleados que son gerentes.

De vuelta a nuestra interfaz de usuario principal

Ahora es momento de terminar nuestro método LoadAddons() inconcluso dentro de nuestro proyecto WinFormsAppA. Este código instruirá al método GetAddonsByType de ClassLibA.AddonLoader para que encuentre un tipo de complemento en particular, en este caso complementos IGraphicalAddon.

Para cada complemento encontrado se llevarán a cabo las siguientes acciones

  • Crear una nueva instancia del complemento
  • Asignar un tipo IGraphicalAddon a la instancia del complemento
  • Rellenar su propiedad DataSource
  • Agrega el complemento al elemento del menú Add-ons (Complementos)
  • Agrega un manejador para el evento clic de ese elemento del menú
1
2
    Private Sub LoadAddons()
3
        Dim loader As New ClassLibA.AddonLoader
4
        Dim addonsLoaded = loader.GetAddonsByType(ClassLibA.AddonLoader.AddonType.IGraphicalAddon)
5
6
        For Each graphicalAddon In addonsLoaded
7
            Dim thisInstance = Activator.CreateInstance(graphicalAddon)
8
            Dim typedAddon = CType(thisInstance, ClassLibA.IGraphicalAddon)
9
            typedAddon.DataSource = m_testDataSource
10
            Dim newlyAddedToolstripItem = AddonsToolStripMenuItem.DropDownItems.Add(typedAddon.Name)
11
            AddHandler newlyAddedToolstripItem.Click, AddressOf typedAddon.OnClick
12
        Next
13
    End Sub

Probando nuestra interfaz de usuario

Asegúrate de poder llevar a cabo una compilación completa de la solución con éxito. Copia el archivo UIAddonA.dll de la carpeta de depuración de salida del proyecto UIAddonA en la carpeta /bin/debug/Addons/ del proyecto WinFormsAppA. Cuando ejecutes el proyecto deberás ver una tabla con información, como antes. Sin embargo, si haces clic en el menú Add-ons (Complementos) ahora deberás ver una referencia al complemento recientemente creado.

Al hacer clic en el menú Managers Report (Reporte de gerentes), esto ocasionará una llamada al método OnClick implementado en nuestro complemento, lo que causará que UIReportAddon1Form aparezca como se muestra a continuación.


Conclusión

Hemos visto dos ejemplos de cómo construir una aplicación que puede obtener y ejecutar complementos según sea necesario. Como mencioné anteriormente, debería ser lo suficientemente sencillo integrar este tipo de funcionalidad en aplicaciones existentes. En cuanto a la cantidad de control que necesitarás (o querrás) darle a desarrolladores de terceros, ese es otro tema por completo.

¡Espero que te hayas divertido con este tutorial! ¡Muchas gracias por leerlo!.