Shared members between interfaces

Nov 14, 2011 at 1:23 PM

Hi,

I'm new to MEF but have my basic plugin manager working (sort of) in my app.

I'm trying to structure such that small pieces of functionality are included in seperate interfaces and then the plugin implements one or mor of these interfaces depending on what they need to support.

Also overtime I can then introduce more interfaces for new plugin points in my host app without breaking existing plugins.

That's the plan anyway.

However stuck on one thing.   

In my host app I load all plugins supporting the particular interface into a list of the Interface type.    So

PluginslistA = all plugins in dir supporting InterfaceA

PluginslistB= all plugins in dir supporting InterfaceB

So now I can loop around each of these lists at my host plugin points to call functionality from the Plugin.  That all works fine.

OK So from my Plugin Side I have:

Class Plugin

  Property ABC

  end Property

   Inplements InterfaceA

   Implements InterfaceB

   Sub xxxx implements InterfaceA.Sub1

   Sub yyyy implements InterfaceB.Sub2

end class

So to my question.   I'd liek to be able to have some common properties/variables in my plugin that are shared between both Intefaces;  what I mean by this is if i loop around all PluginslistA or PluginslistB I's like to be able to reference some common data.

So if i'm calling Sub yyyy from my PluginslistB and I set property ABC  then I want Property ABC same data to be visible when I loop around PluginslistA (ie from Sub xxxx);  howver at the moment they seem to be different instances of the data.

I tried to create at the Interface level and have a "core" interface where I add the properties and then let the other Interfaces inheret ie

Interface Core

Prop ABC

InterfaceA

Inherits Core

Subxxxx

InterfaceB

Inherits Core

Sub yyyy

This exposes the same properties of the core interface but the data instances are different. 

Class Plugin

  Property ABC implements InterfaceCore.ABC

<i want this same data value to be accesible from InterfaceA and InterfaceB>

  end Property

   Inplements InterfaceA

   Implements InterfaceB

   Sub xxxx implements InterfaceA.Sub1

   Sub yyyy implements InterfaceB.Sub2

end class

I can loop around all PluginslistA or PluginslistB individually from the host app and set prop ABC to a value but there must be an easier way to share data.

Can any guru out there advise how i can accomplish common data in a plugin that can be accessible from subs impleting different interfaces?

So I need a shared member class or something and if so an example merged in with above gratefully received so i can see how I should fit together

Hope that makes sense especially my shorthand code..

Regards

Julian

 

 

 

 

 

 

 

 

 

 

 

 

Coordinator
Nov 14, 2011 at 6:29 PM

Hi,

Here is my stab at paraphrasing your requirements. Please let me know if I am completely off base.

What you seem to want is a set of Parts, which share data among each other ?

Basically PluginListA is an IEnumberable AllPluginsOfTypeA

PluginnLisB is an IEnumberable AllPluginOfTypeB

SharedClass , which has shared data that is shared between the above two classes.

I have a condensed version of what I think your scenario is. In the below code you can throw all your shared data into SharedData, which will be shared amongst all the instance of IFoo and IBar that are created.

Hopefully this should get you what you need . Please let me know if I have completely misunderstood your scenario . J

static void Main(string[] args)

{

AssemblyCatalog cat = new AssemblyCatalog(Assembly.GetExecutingAssembly());

CompositionContainer contatiner = new CompositionContainer(cat);

var h = contatiner.GetExportedValue<AllApp>();

}

[Export]

public class AllApp

{

[ImportingConstructor]

public AllApp(IFoo foo, IBar bar)

{

}

public void DoWork()

{

}

}

[Export]

public class SharedData

{

public int count;

}

[Export(typeof(IFoo))]

public class FooA : IFoo

{

[ImportingConstructor]

public FooA(SharedData data)

{

Console.WriteLine(data.GetHashCode());

}

}

[Export(typeof(IBar))]

public class BarA : IBar

{

[ImportingConstructor]

public BarA(SharedData data)

{

Console.WriteLine(data.GetHashCode());

}

}

public interface IFoo

{}

public interface IBar

{}

HTH

-alok

From: juwi_uk [email removed]
Sent: Monday, November 14, 2011 5:24 AM
To: Alok Shriram
Subject: Shared members between interfaces [MEF:279357]

From: juwi_uk

Hi,

I'm new to MEF but have my basic plugin manager working (sort of) in my app.

I'm trying to structure such that small pieces of functionality are included in seperate interfaces and then the plugin implements one or mor of these interfaces depending on what they need to support.

Also overtime I can then introduce more interfaces for new plugin points in my host app without breaking existing plugins.

That's the plan anyway.

However stuck on one thing.

In my host app I load all plugins supporting the particular interface into a list of the Interface type. So

PluginslistA = all plugins in dir supporting InterfaceA

PluginslistB= all plugins in dir supporting InterfaceB

So now I can loop around each of these lists at my host plugin points to call functionality from the Plugin. That all works fine.

OK So from my Plugin Side I have:

Class Plugin

Property ABC

end Property

Inplements InterfaceA

Implements InterfaceB

Sub xxxx implements InterfaceA.Sub1

Sub yyyy implements InterfaceB.Sub2

end class

So to my question. I'd liek to be able to have some common properties/variables in my plugin that are shared between both Intefaces; what I mean by this is if i loop around all PluginslistA or PluginslistB I's like to be able to reference some common data.

So if i'm calling Sub yyyy from my PluginslistB and I set property ABC then I want Property ABC same data to be visible when I loop around PluginslistA (ie from Sub xxxx); howver at the moment they seem to be different instances of the data.

I tried to create at the Interface level and have a "core" interface where I add the properties and then let the other Interfaces inheret ie

Interface Core

Prop ABC

InterfaceA

Inherits Core

Subxxxx

InterfaceB

Inherits Core

Sub yyyy

This exposes the same properties of the core interface but the data instances are different.

Class Plugin

Property ABC implements InterfaceCore.ABC

end Property

Inplements InterfaceA

Implements InterfaceB

Sub xxxx implements InterfaceA.Sub1

Sub yyyy implements InterfaceB.Sub2

end class

I can loop around all PluginslistA or PluginslistB individually from the host app and set prop ABC to a value but there must be an easier way to share data.

Can any guru out there advise how i can accomplish common data in a plugin that can be accessible from subs impleting different interfaces?

So I need a shared member class or something and if so an example merged in with above gratefully received so i can see how I should fit together

Hope that makes sense especially my shorthand code..

Regards

Julian

Nov 14, 2011 at 7:44 PM

Hi alokshriram

Thank you for the quick reply.

Yes you are close to the issue I'm describing.  Reading your code though shows I still have a lot to learn about MEF!!

Being more VB-centric doesnt help either.

Am I reading the code right that the data is only shared on instantiation of teh plugin (ie in New()).  If so I was trying to get whereby once instatiated then the shared data could also be updated.

Regards

Julian

 

 

 

 

Coordinator
Nov 14, 2011 at 9:07 PM

Julian, I am not sure I completly understand your question. 

In case you need to update the SharedState, you should have some method/ property exposed in your FooA an BarA classes that lets you change the "shared" SharedState variable. This update would then be visible to all the other users of that SharedState instance. I hope that this helps.. let me know if that does not answer your question .

 

Nov 14, 2011 at 10:32 PM

OK, many thanks.  Can you explain a bit more what you mean.  How would i update that "count" variable then from FooA say?

Also where would I place the shareddata class so that it is visible to the host application and plugin?  I have seperate projects for Interface, Host and Plugin ...

Regards

Julian

Coordinator
Nov 14, 2011 at 11:02 PM

something like this should work

public class SharedData

{

             public int count {get;set;}

}

[Export(typeof(IFoo))]

public class FooA : IFoo

{

[ImportingConstructor]

public FooA(SharedData data)

{

     data.count++;

}

}

//In case you need to do this updating in another method, you can store data in a private field of the class and do the whatever you need to there.

shared data class would need to be in the same catalog as all the other classes.  Where you put it in terms of the proeject should not make a difference to MEF as long as all the types that are required to do a composition are present in the catalog.

Nov 15, 2011 at 8:12 PM

Hi

You've been great coming back to me but try as I might I cant get this to work.  Yep it sets the value in the New() constructor in VB but then whenever I update the variable in the methods then the values dont get updated.  I guess my knowledge here is now advanced enough to string snippets together into something theat works.    My brain is fried on this!

Regards

Julian

Nov 20, 2011 at 10:18 AM
Edited Nov 20, 2011 at 10:22 AM

Can you advise any good books on MEF as I feel the need to understand it better.

In my current app I load the plugins like this (rightly or wrongly at the moment based on examples I've seen on the net)

    Private Sub LoadAvailablePlugins()
        ' Plugins Loaded using Managed Extensibility Framework (MEF)
        Try
            Dim catalog As New AggregateCatalog
            ' Look for Plugins in the ./Plugins directory.
            catalog.Catalogs.Add(New DirectoryCatalog("Plugins"))
            ' Look for Plugins in subdirectories under ./Plugins.
            For Each currentDirPath As String In Directory.GetDirectories("Plugins")
                catalog.Catalogs.Add(New DirectoryCatalog(currentDirPath))
            Next
            ' Load all found plugin Interface Types
            PluginRepository.ClientPluginCore = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginCore)()
            PluginRepository.ClientPluginMain = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginMain)()
            PluginRepository.ClientPluginMenus = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginMenus)()
            PluginRepository.ClientPluginStatusMenus = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginStatusMenus)()
        Catch ex As Exception
        End Try
        ' Load Startup Components
        InitialisePluginComponents()
    End Sub

I'm storing my plugin objects in a seperate class ie

Imports System.Collections.Generic
Imports System.ComponentModel.Composition
Imports ComfortClient.Plugin.Framework

Public Class PluginRepository
    <ImportMany("IClientPluginCore")> _
    Public Property ClientPluginCore() As IEnumerable(Of IClientPluginCore)
        Get
            Return _ClientPluginCore
        End Get
        Set(value As IEnumerable(Of IClientPluginCore))
            _ClientPluginCore = value
        End Set
    End Property
    Private _ClientPluginCore As IEnumerable(Of IClientPluginCore)
    <ImportMany("IClientPluginMain")> _
    Public Property ClientPluginMain() As IEnumerable(Of IClientPluginMain)
        Get
            Return _ClientPluginMain
        End Get
        Set(value As IEnumerable(Of IClientPluginMain))
            _ClientPluginMain = value
        End Set
    End Property
    Private _ClientPluginMain As IEnumerable(Of IClientPluginMain)
    <ImportMany("IClientPluginMenus")> _
    Public Property ClientPluginMenus() As IEnumerable(Of IClientPluginMenus)
        Get
            Return _ClientPluginMenus
        End Get
        Set(value As IEnumerable(Of IClientPluginMenus))
            _ClientPluginMenus = value
        End Set
    End Property
    Private _ClientPluginMenus As IEnumerable(Of IClientPluginMenus)
    <ImportMany("IClientPluginStatusMenus")> _
    Public Property ClientPluginStatusMenus() As IEnumerable(Of IClientPluginStatusMenus)
        Get
            Return _ClientPluginStatusMenus
        End Get
        Set(value As IEnumerable(Of IClientPluginStatusMenus))
            _ClientPluginStatusMenus = value
        End Set
    End Property
    Private _ClientPluginStatusMenus As IEnumerable(Of IClientPluginStatusMenus)
End Class

and then my interface file looks like this (so far)

<System.ComponentModel.Composition.InheritedExport(GetType(IClientPluginCore))> _
Public Interface IClientPluginCore
    ReadOnly Property Name() As String
    ReadOnly Property UniqueID() As String
    ReadOnly Property Image() As Image
 End Interface
<System.ComponentModel.Composition.InheritedExport(GetType(IClientPluginMain))> _
Public Interface IClientPluginMain
    Inherits IClientPluginCore
    Sub Initialise()
    Sub SetAboutMenu(ByRef PluginMenuItem As ToolStripMenuItem)
    Sub About(ByVal sender As Object, ByVal e As System.EventArgs)
  End Interface
<System.ComponentModel.Composition.InheritedExport(GetType(IClientPluginMenus))> _
Public Interface IClientPluginMenus
    Inherits IClientPluginMain
    Sub SetPluginMenu(ByRef PluginMenuItems As List(Of ToolStripItem))
End Interface
<System.ComponentModel.Composition.InheritedExport(GetType(IClientPluginStatusMenus))> _
Public Interface IClientPluginStatusMenus
    Inherits IClientPluginMain
    Sub GetStatusMenu(ByRef PluginMenu As ToolStripDropDownButton)
End Interface

Then at any points in my main app where I support a particular plus point I'd do on/both of:

  For Each ClientPluginMain In PluginRepository.ClientPluginMain
            ....call a sub or set a property
   next
  For Each ClientPluginMenus In PluginRepository.ClientPluginMenus
            ....call a sub or set a property
   next

All of this works OK so far but even with your pointers I cant see how to share data. In the snippet below for example

PluginRepository.ClientPluginMain = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginMain)()
PluginRepository.ClientPluginMenus = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginMenus)()

It seems to me that if I have a plugin that supports both of the above interfaces then the above 2 lines create two parallel instances of teh pllugin that then cant talk to eachother.  It's like the run in a parallel isolated universe.

So if I then put myself in a plugin contributor, they write something like..

Public Class Sample_Plugin
    ' API Contracts Supported
    Implements IClientPluginMain
    Implements IClientPluginMenus

#Region "Properties"
    Public CommonAttribute as String
#End Region

#Region "Properties"

    Public ReadOnly Property Name() As String Implements IClientPluginMain.Name
        Get
            Return _Name
        End Get
    End Property
    Private _Name As String

    Public ReadOnly Property UniqueID() As String Implements IClientPluginMain.UniqueID
        Get
            Return _UniqueID
        End Get
    End Property
    Private _UniqueID As String

    Public ReadOnly Property Image() As Image Implements IClientPluginMain.Image
        Get
            Return _Image
        End Get
    End Property
    Private _Image As Image

    Public Property Host() As IClientPluginHost Implements IClientPluginMain.Host
        Set(value As IClientPluginHost)
            _Host = value
        End Set
        Get
            Return _Host
        End Get
    End Property
    Private _Host As IClientPluginHost
#End Region

#Region "Instantiation"

    Private Sub New()
        _Name = "SamplePlugin1"
        _UniqueID = "Plugin-1"
        _Image = My.Resources.PluginResources.SamplePlugin1
    End Sub

    Private Sub Initialise() Implements IClientPluginMain.Initialise
          CommonAttribute = "Look I can set a value"
    End Sub
#End Region

#Region "Methods - IClientPluginMain"

    Public Sub About(ByVal sender As Object, ByVal e As System.EventArgs) Implements IClientPluginMain.About
           ....
           msgbox(CommonAttribute)
           CommonAttribute = "Look I expect I can update the Value"
           msgbox(CommonAttribute)
    End Sub
   
    Sub SetAboutMenu(ByRef MenuAbout As ToolStripMenuItem) Implements IClientPluginMain.SetAboutMenu
           ....
    End ub

#End Region

#Region "Methods - IClientPluginMenus"

    Sub SetPluginMenu(ByRef PluginMenu As List(Of ToolStripItem)) Implements IClientPluginMenus.SetPluginMenu
           msgbox(CommonAttribute)
           CommonAttribute = "Look I expect I can update the Value"
           msgbox(CommonAttribute)
    End Sub

#End Region

End Class

Now because they've declared a common variable in thier code then the would expect to be able to access it from any subroutine but in fact because of my "parallel universe" scenario as the plugins get loaded the data isnt what you expect. I think then that might confuse plugin contributors.

The only way it seems to me that you can set the variable is dyuring instantiation (new()sub)  or by looping around all objects in the Reporsitory and trying to sync them by some method.

I coded this way based on examples and discussions on the Net because I want to be able to introduce new functionality in my API over time without breaking existing plugins.  Also not all plugins then need to implement the same Interfaces.  That's my (maybe misguided) logic here.

I know I'm doing something wrong here and welcome any help or will even buy a book to try and understand further but I feel I'm missing something and need to sort out before I go much further.  What I've done so far works OK but am I on the right path.

Help.......!  :0)

Julian

 

 

 

Nov 21, 2011 at 5:47 PM

Hi,

Your 'parts' look fine, I definitely think you're on the right track.

However, it is very unusual to create more than one container in an application:

PluginRepository.ClientPluginMain = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginMain)()
PluginRepository.ClientPluginMenus = New CompositionContainer(catalog).GetExportedValues(Of IClientPluginMenus)()

This is where I believe you're going wrong. On the surface, the way to approach this should look more like:

Dim Container = New CompositionContainer(catalog)

PluginRepository.ClientPluginMain = Container.GetExportedValues(Of IClientPluginMain)()
PluginRepository.ClientPluginMenus = Container.GetExportedValues(Of IClientPluginMenus)()

I.e. there is only one application-wide container.

However, even in this snippet there are problems. The container shouldn't be used to 'retrieve' things on demand like this most of the time - the dependencies on the two sets of plugins should be an import on yet another part.

This is a pretty tricky topic. The MEF examples in it are fast becoming outdated, however for the general concepts I'd strongly recommend Mark Seemann's book "Dependency Injection in .NET" - http://manning.com/seemann

I've also tried to write on this topic at one point - may be useful: http://blogs.msdn.com/b/nblumhardt/archive/2008/12/27/container-managed-application-design-prelude-where-does-the-container-belong.aspx

Sorry if I've missed some context, there's quite a bit of code in this discussion so I may also be off track.

Cheers,
Nick

Nov 21, 2011 at 6:02 PM

Hey Nick,

If I could buy you a beer I would.

Your suggestion about the container seems to have moved me on a great deal.  I think (needs more testing) variables can see eachother now.

Could you expand a tad on "The container shouldn't be used to 'retrieve' things on demand like this most of the time - the dependencies on the two sets of plugins should be an import on yet another part."

You're right though in that it seems to be fast moving;  let's hope it doesnt totally pass me by..!  :0)

I'll take a look at that book.

Again many thanks for your help.

Regards

Julian

Nov 21, 2011 at 6:16 PM

Hi Julian,

Whenever you retrieve two or more parts from the container using “GetExport()” or a similar method, you could equivalently retrieve a single additional part that [Import]s the other two parts.

E.g. rather than:

a = Container.GetExport();

b = Container.GetExport();

You can write (forgive my C#, my VB is rusty J):

[Export]

class C

{

[ImportingConstructor]

public C(A a, B b)

{

// Assign a and b to some private fields

}

}

Then:

c = Container.GetExport<C>();

Now, the point isn’t to cut down on calls to the container, and you would not then go and access “A” and “B” properties on “C” – rather, C should be a meaningful class that encapsulates A and B to do some kind of work. Following the above statement you’d usually see something like:

c.DoWork();

The primary reason for designing your application this way is to make the dependency structure of your application explicit – things declare what they need as imports, rather than helping themselves via the container.

Hopefully this will help you clarify some of the other materials on the subject, best of luck with it!

Nick

From: juwi_uk [email removed]
Sent: Monday, November 21, 2011 10:03 AM
To: Nicholas Blumhardt
Subject: Re: Shared members between interfaces [MEF:279357]

From: juwi_uk

Hey Nick,

If I could buy you a beer I would.

Your suggestion about the container seems to have moved me on a great deal. I think (needs more testing) variables can see eachother now.

Could you expand a tad on "The container shouldn't be used to 'retrieve' things on demand like this most of the time - the dependencies on the two sets of plugins should be an import on yet another part."

You're right though in that it seems to be fast moving; let's hope it doesnt totally pass me by..! :0)

I'll take a look at that book.

Again many thanks for your help.

Regards

Julian

Nov 21, 2011 at 8:02 PM

Hi Nick,

Thanks for this.  I'm not sure I fully understand as my C isnt too good either.  I ran though a converter to piece together but code is to sparse for my simple mind to place into context fully around what I already have.  I tried but it didnt do anything.

So onto trying to uncover some of this in a book or something.

Regards

Julian