I have an ASP.NET MVC application which uses MEF to implement a plugin framework. Plugins are separate DLLs that exist in the application's bin directory. Plugins usually export one or more controllers like this...
<Export(GetType(IController))>
<MYAPP_Interfaces.Attributes.MVCPluginMetadata(
"SomePlugin",
"A Description for the plugin",
"A Readable Name",
{"ScriptsForThePlugin.js"},
{"StylesForThePlugin.css"},
Enumerations.MVCPluginType.DataView,
"DefaultActionName")>
<PartCreationPolicy(CreationPolicy.NonShared)>
Public Class MyPluginController
Inherits System.Web.Mvc.Controller
<Import()>
Private m_objHost As IWebHost
... and so on.
This all works fine, the host app includes all controllers in an ImportMany
property, and composes itself upon creation in the usual way. So m_objHost
is populated automagically and the controller has access to all the things the host application provides, like logging and information about the user and what they're currently working on and all that.
My question has to do with my models, and any DAL or utility classes that I have in a plugin. These classes usually have need of information from the IWebHost
object. However, the host doesn't need to know about these classes, so they are not included in composition. Since they are not composed, if they want an IWebHost
reference they each have to compose themselves upon instantiation, like this:
Public Class MyModel
<Import()>
Private m_objHost As IWebHost
<Import()>
Private m_objLog As ILog
Public Sub New()
Compose()
End Sub
...
Private Sub Compose()
Try
Dim objCatalog As New AggregateCatalog
objCatalog.Catalogs.Add(New DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory & "bin"))
Dim container As New CompositionContainer(objCatalog)
container.ComposeParts(Me)
Catch ex As Exception
If m_objLog IsNot Nothing Then
m_objLog.writeError(ex)
End If
End Try
End Sub
End Class
So my main question can be broken into two parts:
Is there any noticeable performance problem with having, say, 20 or so classes that perform composition whenever they are instantiated? I currently only have a few, and if there is a hit it's not noticeable. In other words, do I actually need to change this strategy? It violates DRY because I have a Compose
method in every class, but I can learn to live with it.
Is there a better way? How can I handle a single composition in the main application that takes care of populating all of the classes in the plugins, including those not imported in the class performing the main composition?
I've considered the following:
Having all models and utility classes and whatever implement a marker interface, export them all using that interface as a contract, and importing them in the host class, even though the host class doesn't need them. I think this is an even cruddier design than what I have, and I don't want to do it. I'm willing to listen to arguments in favor of this, though.
Having a class in each plugin that needs it that implements IWebHost
that acts as a wrapper for the class exported by the main app. I'd still have to do composition in each plugin, but at least it would only be once per plugin. This one seems okay to me.
Thanks in advance for any help you can give, and for reading this whole novel of a question.
I wound up adding a class like the one below to the plugins that need it. I have a project template for plugins, so I'll probably just add this class to that template.
Any class that needs something from the host can access it by calling PluginUtility.Host
.
Public Class PluginUtility
<Import()>
Private m_objHost As IWebHost
Private Shared m_objInstance As PluginUtility
Private Sub New()
Compose()
End Sub
Public Shared ReadOnly Property Host As IWebHost
Get
If m_objInstance Is Nothing Then
m_objInstance = New PluginUtility
End If
Return m_objInstance.m_objHost
End Get
End Property
Private Sub Compose()
Try
Dim objCatalog As New AggregateCatalog
objCatalog.Catalogs.Add(New DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory & "bin"))
Dim container As New CompositionContainer(objCatalog)
container.ComposeParts(Me)
Catch ex As Exception
Console.Write("Could not compose to get a reference to the host")
End Try
End Sub
End Class