Reputation: 5724
so I have a rather stupid question, but I just can't think of a really good way to solve this "dilemma".
I'm developing an application with MEF and trying to come up with a good way to handle optional dependencies.
Random example, assume there are two plugins; "MyPlugin", and a StatusBar plugin which may or may not be loaded by the main application. MyPlugin is supposed to have an optional dependency to the Status Bar Manager; if that plugin is loaded by MEF, it'll use it to output something to the status bar; if not, it simply won't.
[ModuleExport(typeof(MyPlugin))]
public class MyPlugin : IModule
{
[ImportingConstructor]
public MyPlugin([Import(AllowDefault = true)] StatusBarManager optionalStatusBarManager OptionalStatusBar)
{
if(optionalStatusBarManager != null)
optionalStatusBarManager.Display("Hi from MyPlugin!");
}
}
In order to be aware of the type "StatusBarManager", the MyPlugin-project would need a reference to the StatusBarManager project / its dll.
Now from my gut feeling, it makes little sense to have an optional dependency, if MyPlugin needs the StatusBarManager's dll to be accessible anyway because it relies on its exposed types.
But what are my options? Is there any decent way to have such optional dependencies without requiring the main application to have access to the optional dependency's dlls?
So far, I could only come up with importing these by contract name using the "dynamic" type. But then I'd lose any and all compile time safety obviously in MyPlugin.
Or I could create another project, such as StatusBarManagerAbstractions which contains only interface definitions. Then at least MyPlugin would only have a hard dependency to the relatively small interface project, without requiring the (potentially large) implementation libraries to be there. Better than nothing, but still doesn't strike me as a fully "optional" dependency.
Am I just overthinking this?! Is there any best practice to handle such an issue?
Upvotes: 1
Views: 1143
Reputation: 38335
tldr; you're probably over-thinking this :)
Long Version
I'm developing an application with MEF and trying to come up with a good way to handle optional dependencies.
As the dependencies are optional, the current sample code should suffice. However, as a general best practice, all dependencies will be referenced / coded against by interface, not the concrete type. During the dependency resolution in MainModule, the implementation of interfaces defined in CommonModule are resolved by the MEF container through assembly references, file directory scanning, etc... and builds up the dependency graphs as needed.
Is there any decent way to have such optional dependencies without requiring the main application to have access to the optional dependency's dlls?
The MainModule does not need to have a reference to the StatusBarModule as long as the dll is deployed to the MainModule bin; a DirectoryCatalog can then be built up to compose the MEF container. As you're using "plugins", you'd want to use a DirectoryCatalog anyhow so that the dll's can be discovered at runtime.
For example, based on the given scenario, the solution structure should look something like the following which removes all references except the CommonModule to the MainModule:
Solution
- MainModule
- starts the app (console, winform, wpf, etc...)
- references: CommonModule (no other project references)
- CommonModule
- references: no other modules
- purpose: provides a set of common interfaces all projects can reference
- note: the actual implementations will be in other projects
- MyPluginModule
- references: CommonModule
- purpose: provides plugins for the app
- Implements interfaces from CommonModule
- note: uses dependencies defined by interfaces in CommonModule and implemented by other modules
- build: build step should copy the dll to the MainModule's bin
- StatusBarModule
- references: CommonModule
- purpose: provides plugin dependencies required by the application
- note: uses dependencies defined by interfaces in CommonModule and implemented by other modules
- build: build step should copy the dll to the MainModule's bin
The MyPluginModule and StatusBarModule are almost identical where neither references each other; rather, they share interfaces through the CommonModule interface definitions. The dependencies/concrete implementations are resolved at runtime through a DirectoryCatalog.
The underlying code/implementations would be as follows. Note, that there are 2 options for the optional dependency where one (MyPlugin) is using .ctor injection while the other (MyOtherPlugin) is using property injection:
MainModule
class Program
{
static void Main()
{
// will output 'Hi from MyPlugin!' when resolved.
var myPlugin = MefFactory.Create<IMyPlugin>().Value;
// will output 'Hi from MyOtherPlugin!' when resolved.
var myOtherPlugin = MefFactory.Create<IMyOtherPlugin>().Value;
Console.ReadLine();
}
}
public static class MefFactory
{
private static readonly CompositionContainer Container = CreateContainer();
public static Lazy<T> Create<T>()
{
return Container.GetExport<T>();
}
private static CompositionContainer CreateContainer()
{
// directory where all the dll's reside.
string directory = AppDomain.CurrentDomain.BaseDirectory;
var container = new CompositionContainer( new DirectoryCatalog( directory ) );
return container;
}
}
CommonModule
public interface IModule { }
public interface IMyPlugin { }
public interface IMyOtherPlugin { }
public interface IStatusBarManager
{
void Display( string msg );
}
MyPluginModule
[Export( typeof( IMyPlugin ) )]
internal class MyPlugin : IModule, IMyPlugin
{
private readonly IStatusBarManager _manager;
[ImportingConstructor]
public MyPlugin( [Import( AllowDefault = true )] IStatusBarManager manager )
{
_manager = manager;
if( _manager != null )
{
_manager.Display( "Hi from MyPlugin!" );
}
}
}
[Export( typeof( IMyOtherPlugin ) )]
internal class MyOtherPlugin : IModule, IMyOtherPlugin
{
private IStatusBarManager _statusManager;
[Import( AllowDefault = true )]
public IStatusBarManager StatusManager
{
get { return _statusManager; }
private set
{
_statusManager = value;
_statusManager.Display( "Hi from MyOtherPlugin!" );
}
}
}
StatusBarModule
[Export( typeof( IStatusBarManager ) )]
internal class StatusBarManager : IStatusBarManager
{
public void Display( string msg )
{
Console.WriteLine( msg );
}
}
Upvotes: 3