0
\$\begingroup\$

I mostly develop AddIns for Office. In the example of MS Word, I have an AddIn which allows to review drafting style, another AddIn which allows to monitor cross-references, another Addin which allows to import graphics from the web, etc. What the AddIns do is irrelevant here, but you should now they all work independently from each other.

I now would like to centralise all these AddIn 'Extensions' into one executable, so that the end users only deal with only one setup for Word, instead of 3. Due to some internal restriction rules, some users may not be allowed to use some AddIn extensions. Hence the need for some verifications at runTime.

At the moment I came up with the following arquitecture, but I am not 100% satisfied. I set below a listing of strenghts and weaknesses. Feel free to suggest aditionnal weaknesses, and/or fixes.

Strengths

  1. The flow between the UI (button callbacks) and the execution is centralized and standardized.
  2. The feature availability validation is centralised and easy to update if needs be.
  3. Dealing with an new AddIn Extension is "easy": build a new class which inherits AddInExtension, populate the AddInExtensions Collection, populate the newly built Class with relevant callback names in the Overrides Sub FinallyExecute(), and add their corresponding features (Subs, Classes) into the same Class.
  4. Encapsulation is total and there is no risk for interference between two extensions.

Weaknesses

  1. I am worried I will have to include the entire code of an AddIn Extension into a single .vb file (the AddInExtensionABCServices Class).
  2. I am also worried I will have to nest all AddIn Extension Sub-Classes into the Main-Class. I feel this can get messy.
  3. Architecture is not 100% Hard Typed, in the sens that the callback Sub uses text strings twice to run the relevant Sub, whilst I would have loved to be able to do something like MyExtensions.ABCServices.GetChocolate.

Re point 7, my intuition tells me this could be fixed through (i) Shared Class or (ii) a combination of Module and Namespace. But (i) a MustInherit Class cannot be Shared, and (ii) Modules are hard to keep Private (default is Public, hence prone to errors).


Module Module1

    Private AddInExtensions As New Collections.Hashtable()

    Sub Main()

        AddInExtensions.Add("OnlineServices", New AddInExtensionOnlineServices)
        AddInExtensions.Add("LocalServices", New AddInExtensionLocalServices)
        AddInExtensions.Add("SpaceServices", New AddInExtensionSpaceServices)

        '... rest of initialization code

    End Sub

    'Example of a CallBack
    Sub MyCallBackButtonClicked()
        Dim MyExtension As IAddInExtension = AddInExtensions("LocalServices")
        MyExtension.TryExecute("GetTea")
    End Sub

End Module

'# Interface required for Intelisense when calling AddInExtensions As Collections.Hashtable()
Interface IAddInExtension
    Sub TryExecute(ByVal ActionName As String)
End Interface

'# Base Class from which will derive each AddIn Extension class
Friend MustInherit Class AddInExtension

    Implements IAddInExtension

    Protected Property Name As String
    Protected Property StatusIsOk As Boolean

    'This sub verifies the services are available
    Public Sub TryExecute(ByVal ActionName As String) Implements IAddInExtension.TryExecute
        If StatusIsOk Then
            Me.FinallyExecute(ActionName)
        Else
            MsgBox("Sorry, you cannot use this service. Please contact your IT department.", vbOKOnly)
        End If
    End Sub

    'The overriden version of this Sub will contain all the features related to a given Extension
    Protected MustOverride Sub FinallyExecute(ByVal ActionName As String)

End Class

'# AddIn Extension Class for ONLINE SERVICES
Friend Class AddInExtensionOnlineServices
    Inherits AddInExtension

    Public Sub New()
        MyBase.Name = "OnlineServices"
        MyBase.StatusIsOk = True 'Let's say this AddIn Extension is PERMITED (typically would run a function here)
    End Sub

    'Actions available through the Button Callback for this AddIn Extension
    Protected Overrides Sub FinallyExecute(ActionName As String)
        Select Case ActionName
            Case "GetTime"
                Call Me.GetTime()
            Case "GetWeather"
                Call Me.GetWeather()
        End Select
    End Sub

    Private Sub GetTime()
        '...
    End Sub

    Private Sub GetWeather()
        '...
    End Sub

End Class

'# AddIn Extension Class for LOCAL SERVICES
Friend Class AddInExtensionLocalServices
    Inherits AddInExtension

    Public Sub New()
        MyBase.Name = "LocalServices"
        MyBase.StatusIsOk = True 'Let's say this AddIn Extension is PERMITED (typically would run a function here)
    End Sub

    'Actions available through the Button Callback for this AddIn Extension
    Protected Overrides Sub FinallyExecute(ActionName As String)
        Select Case ActionName
            Case "GetCoffee"
                Call Me.GetCoffee()
            Case "GetTea"
                Call Me.GetTea()
        End Select
    End Sub

    Private Sub GetCoffee()
        '...
    End Sub

    Private Sub GetTea()
        '...
    End Sub

End Class

'# AddIn Extension Class for SPACE SERVICES
Friend Class AddInExtensionSpaceServices
    Inherits AddInExtension

    Public Sub New()
        MyBase.Name = "SpaceServices"
        MyBase.StatusIsOk = False 'Let's say this AddIn Extension is FORBIDDEN (typically would run a function here)
    End Sub

    'Actions available through the Button Callback for this AddIn Extension
    Protected Overrides Sub FinallyExecute(ActionName As String)
        Select Case ActionName
            Case "GetOxygen"
                Call Me.GetOxygen()
            Case "GetSun"
                Call Me.GetSun()
        End Select
    End Sub

    Private Sub GetOxygen()
        '...
    End Sub

    Private Sub GetSun()
        '...
    End Sub

End Class

Also one question aside: I would like to change

Dim MyExtension As IAddInExtension = AddInExtensions("LocalServices")
MyExtension.Execute("GetTea")

by

AddInExtensions("LocalServices").Execute("GetTea")

but Intelisense does not pick up the type so .Execute does not show in the dropwodn list. Other than minorly inconvenient, would that cause any problem?

I was thinking of typing the Hashtable with something in the like of Private AddInExtensions As New Collections.Hashtable(Of IAddInExtension) but the compiler does not like it.

\$\endgroup\$
1
\$\begingroup\$

Use generics

Private AddInExtensions As New Generic.Dictionary(Of String, IAddInExtension)

Later you can do

AddInExtensions("LocalServices").TryExecute("GetTea")
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.